diff --git a/.github/config-allowlist.txt b/.github/config-allowlist.txt index 21f9b86c9e7..e677f612453 100644 --- a/.github/config-allowlist.txt +++ b/.github/config-allowlist.txt @@ -34,6 +34,7 @@ examples/showcases/banking/next.config.mjs examples/showcases/chatkit-studio/apps/playground/next.config.ts examples/showcases/chatkit-studio/apps/studio/next.config.ts examples/showcases/chatkit-studio/apps/world/next.config.ts +examples/showcases/daytona-runcode/next.config.ts examples/showcases/deep-agents-finance-erp/next.config.ts examples/showcases/deep-agents-job-search/next.config.ts examples/showcases/deep-agents/next.config.ts diff --git a/.github/workflows/showcase_build.yml b/.github/workflows/showcase_build.yml index fa0eb5051a3..cde352e827c 100644 --- a/.github/workflows/showcase_build.yml +++ b/.github/workflows/showcase_build.yml @@ -51,13 +51,15 @@ on: - shell-docs - showcase-harness - showcase-aimock + - webhooks # No top-level concurrency group. Every build run completes. This is the # whole point of the decoupling: rapid pushes to main no longer cancel # in-flight builds. -env: - RAILWAY_ENV_ID: "b14919f4-6417-429f-848d-c6ae2201e04f" +# Top-level env intentionally empty: env IDs are resolved inside +# showcase/scripts/redeploy-env.ts from the showcase/scripts/railway-envs.ts +# SSOT (and its emitted railway-envs.generated.json), not by this workflow. permissions: contents: read @@ -156,6 +158,14 @@ jobs: - 'showcase/integrations/*/manifest.yaml' showcase_aimock: - 'showcase/aimock/**' + webhooks: + # Sentinel pattern that cannot match any in-tree path. + # The webhooks GHCR image is built by the showcase-eval-webhook + # repo's own release workflow, so we never want a push-driven + # build run to include it. workflow_dispatch can still target + # webhooks explicitly via the service input — that path skips + # paths-filter entirely. + - 'showcase/__no_match_webhooks_built_out_of_band__' - name: Build service matrix id: build-matrix @@ -166,12 +176,15 @@ jobs: FILTER_CHANGES: ${{ steps.filter.outputs.changes }} run: | # Full service config as JSON - # Fields: dispatch_name, filter_key, context, image, railway_id, timeout, lfs, build_args, build_args_sha, build_args_branch, dockerfile, health_path - # health_path: explicit endpoint the verify step probes. Required - # for every service — no fallback. An unscoped fallback could mask - # a broken endpoint when an unrelated catch-all/CDN/actuator happens - # to 200 at the other path. Misconfigured paths are a config bug to - # fix in the matrix, not runtime behavior to hide. + # Fields: dispatch_name, filter_key, context, image, railway_id, timeout, lfs, build_args, build_args_sha, build_args_branch, dockerfile, health_path, skip_build + # skip_build (optional, boolean): when true, the Docker build step + # is skipped for this slot (image is built out-of-band by another + # workflow/repo). Currently used by `webhooks` (built by the + # showcase-eval-webhook repo's own release workflow). + # health_path: historical field retained in the matrix for human + # reference. The actual verify probe is driven by per-service + # drivers in verify-deploy.ts (showcase/scripts/verify-deploy.ts), + # NOT by this field — it is informational only at this layer. ALL_SERVICES='[ {"dispatch_name":"shell","filter_key":"shell","context":".","image":"showcase-shell","railway_id":"40eea0da-6071-4ea8-bdb9-39afb19225ec","timeout":10,"lfs":true,"build_args_sha":"__GH_SHA__","build_args_branch":"__GH_REF_NAME__","dockerfile":"showcase/shell/Dockerfile","health_path":"/"}, {"dispatch_name":"langgraph-python","filter_key":"langgraph_python","context":"showcase/integrations/langgraph-python","image":"showcase-langgraph-python","railway_id":"90d03214-4569-41b0-b4c1-6438a8a7b203","timeout":15,"lfs":false,"build_args":"","dockerfile":"","health_path":"/api/health"}, @@ -194,10 +207,11 @@ jobs: {"dispatch_name":"claude-sdk-python","filter_key":"claude_sdk_python","context":"showcase/integrations/claude-sdk-python","image":"showcase-claude-sdk-python","railway_id":"b122ab65-9854-4cb2-a68e-b50ff13f7481","timeout":15,"lfs":false,"build_args":"","dockerfile":"","health_path":"/api/health"}, {"dispatch_name":"built-in-agent","filter_key":"built_in_agent","context":"showcase/integrations/built-in-agent","image":"showcase-built-in-agent","railway_id":"f4f8371a-bc46-45b2-b6d4-9c9af608bdbf","timeout":15,"lfs":false,"build_args":"","dockerfile":"","health_path":"/api/health"}, {"dispatch_name":"shell-dojo","filter_key":"shell_dojo","context":".","image":"showcase-shell-dojo","railway_id":"7ad1ece7-2228-49cd-8a78-bddf30322907","timeout":10,"lfs":false,"build_args":"","dockerfile":"showcase/shell-dojo/Dockerfile","health_path":"/"}, - {"dispatch_name":"shell-dashboard","filter_key":"shell_dashboard","context":".","image":"showcase-shell-dashboard","railway_id":"4d5dfd74-be61-40b2-8564-b53b7dd4c15b","timeout":10,"lfs":true,"build_args_sha":"__GH_SHA__","build_args_branch":"__GH_REF_NAME__","build_args_pb_url":"https://showcase-pocketbase-production.up.railway.app","build_args_shell_url":"https://showcase.copilotkit.ai","build_args_ops_url":"https://showcase-harness-production.up.railway.app","dockerfile":"showcase/shell-dashboard/Dockerfile","health_path":"/"}, - {"dispatch_name":"shell-docs","filter_key":"shell_docs","context":".","image":"showcase-shell-docs","railway_id":"7badfb8d-4228-414c-9145-b4026803714f","timeout":10,"lfs":true,"build_args_sha":"__GH_SHA__","build_args_branch":"__GH_REF_NAME__","build_args_base_url":"https://docs.copilotkit.ai","build_args_shell_url":"https://showcase.copilotkit.ai","build_args_analytics":"yes","dockerfile":"showcase/shell-docs/Dockerfile","health_path":"/"}, + {"dispatch_name":"shell-dashboard","filter_key":"shell_dashboard","context":".","image":"showcase-shell-dashboard","railway_id":"4d5dfd74-be61-40b2-8564-b53b7dd4c15b","timeout":10,"lfs":true,"build_args_sha":"__GH_SHA__","build_args_branch":"__GH_REF_NAME__","dockerfile":"showcase/shell-dashboard/Dockerfile","health_path":"/"}, + {"dispatch_name":"shell-docs","filter_key":"shell_docs","context":".","image":"showcase-shell-docs","railway_id":"7badfb8d-4228-414c-9145-b4026803714f","timeout":10,"lfs":true,"build_args_sha":"__GH_SHA__","build_args_branch":"__GH_REF_NAME__","dockerfile":"showcase/shell-docs/Dockerfile","health_path":"/"}, {"dispatch_name":"showcase-harness","filter_key":"showcase_harness","context":".","image":"showcase-harness","railway_id":"3a14bfed-0537-4d71-897b-7c593dca161d","timeout":20,"lfs":false,"build_args":"","dockerfile":"showcase/harness/Dockerfile","health_path":"/health"}, - {"dispatch_name":"showcase-aimock","filter_key":"showcase_aimock","context":"showcase/aimock","image":"showcase-aimock","railway_id":"0fa0435d-8a66-46f0-84fd-e4250b580013","timeout":5,"lfs":false,"build_args":"","dockerfile":"showcase/aimock/Dockerfile","health_path":"/health"} + {"dispatch_name":"showcase-aimock","filter_key":"showcase_aimock","context":"showcase/aimock","image":"showcase-aimock","railway_id":"0fa0435d-8a66-46f0-84fd-e4250b580013","timeout":5,"lfs":false,"build_args":"","dockerfile":"showcase/aimock/Dockerfile","health_path":"/health"}, + {"dispatch_name":"webhooks","filter_key":"webhooks","context":".","image":"showcase-eval-webhook","railway_id":"ba6acc13-7585-41fe-a5ee-585b34a58fcd","timeout":5,"lfs":false,"build_args":"","dockerfile":"","health_path":"/health","skip_build":true} ]' DISPATCH="$DISPATCH_SERVICE" @@ -319,47 +333,18 @@ jobs: env: BUILD_ARGS_SHA: ${{ matrix.service.build_args_sha }} BUILD_ARGS_BRANCH: ${{ matrix.service.build_args_branch }} - BUILD_ARGS_PB_URL: ${{ matrix.service.build_args_pb_url }} - BUILD_ARGS_BASE_URL: ${{ matrix.service.build_args_base_url }} - BUILD_ARGS_SHELL_URL: ${{ matrix.service.build_args_shell_url }} - BUILD_ARGS_OPS_URL: ${{ matrix.service.build_args_ops_url }} - BUILD_ARGS_ANALYTICS: ${{ matrix.service.build_args_analytics }} - NEXT_PUBLIC_POSTHOG_KEY: ${{ secrets.POSTHOG_PROJECT_KEY }} - NEXT_PUBLIC_REB2B_KEY: ${{ secrets.REB2B_KEY }} - NEXT_PUBLIC_SCARF_PIXEL_ID: ${{ secrets.SCARF_PIXEL_ID }} - NEXT_PUBLIC_REO_KEY: ${{ secrets.REO_PROJECT_KEY }} - NEXT_PUBLIC_GOOGLE_ANALYTICS_TRACKING_ID: ${{ secrets.GOOGLE_ANALYTICS_TRACKING_ID }} run: | + # set -euo pipefail: without `-e`, a transient `$GITHUB_OUTPUT` + # write failure (disk pressure, ENOSPC) could silently produce + # empty build-args and we'd ship an image without COMMIT_SHA / + # BRANCH baked in — invisible drift between build label and + # what's actually running. + set -euo pipefail ARGS="" if [ -n "$BUILD_ARGS_SHA" ]; then ARGS="COMMIT_SHA=${BUILD_ARGS_SHA}" ARGS="${ARGS}"$'\n'"BRANCH=${BUILD_ARGS_BRANCH}" fi - if [ -n "$BUILD_ARGS_PB_URL" ]; then - ARGS="${ARGS:+${ARGS}$'\n'}NEXT_PUBLIC_POCKETBASE_URL=${BUILD_ARGS_PB_URL}" - fi - if [ -n "$BUILD_ARGS_BASE_URL" ]; then - ARGS="${ARGS:+${ARGS}$'\n'}NEXT_PUBLIC_BASE_URL=${BUILD_ARGS_BASE_URL}" - fi - if [ -n "$BUILD_ARGS_SHELL_URL" ]; then - ARGS="${ARGS:+${ARGS}$'\n'}NEXT_PUBLIC_SHELL_URL=${BUILD_ARGS_SHELL_URL}" - fi - if [ -n "$BUILD_ARGS_OPS_URL" ]; then - # OPS_BASE_URL is required at build time by shell-dashboard's - # next.config.ts rewrites() — without it `next build` aborts. - ARGS="${ARGS:+${ARGS}$'\n'}OPS_BASE_URL=${BUILD_ARGS_OPS_URL}" - fi - if [ "$BUILD_ARGS_ANALYTICS" = "yes" ]; then - # Client-side analytics keys for the shell-docs bundle. - # NEXT_PUBLIC_* values must reach `next build` so they get - # inlined into client JS chunks. Sourced from repo secrets; - # Railway runtime env doesn't reach this build step. - ARGS="${ARGS:+${ARGS}$'\n'}NEXT_PUBLIC_POSTHOG_KEY=${NEXT_PUBLIC_POSTHOG_KEY}" - ARGS="${ARGS:+${ARGS}$'\n'}NEXT_PUBLIC_REB2B_KEY=${NEXT_PUBLIC_REB2B_KEY}" - ARGS="${ARGS:+${ARGS}$'\n'}NEXT_PUBLIC_SCARF_PIXEL_ID=${NEXT_PUBLIC_SCARF_PIXEL_ID}" - ARGS="${ARGS:+${ARGS}$'\n'}NEXT_PUBLIC_REO_KEY=${NEXT_PUBLIC_REO_KEY}" - ARGS="${ARGS:+${ARGS}$'\n'}NEXT_PUBLIC_GOOGLE_ANALYTICS_TRACKING_ID=${NEXT_PUBLIC_GOOGLE_ANALYTICS_TRACKING_ID}" - fi # Use delimiter to safely pass multiline value echo "args<> $GITHUB_OUTPUT echo "$ARGS" >> $GITHUB_OUTPUT @@ -401,6 +386,7 @@ jobs: done - name: Build and push + if: ${{ matrix.service.skip_build != true }} uses: depot/build-push-action@98e78adca7817480b8185f474a400b451d74e287 # v1.18.0 with: project: m2kw2wmmcp @@ -416,37 +402,278 @@ jobs: ghcr.io/copilotkit/${{ matrix.service.image }}:${{ github.sha }} build-args: ${{ steps.build-args.outputs.args }} - - name: Trigger Railway redeploy - if: matrix.service.railway_id != '' + - name: Write per-slot build result + if: always() env: - RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }} - SERVICE_ID: ${{ matrix.service.railway_id }} - ENV_ID: ${{ env.RAILWAY_ENV_ID }} + SERVICE: ${{ matrix.service.dispatch_name }} + BUILD_STATUS: ${{ job.status }} run: | - # Trigger Railway to pull the freshly-pushed :latest image. - # Not all services have environmentPatchCommit auto-update - # configured, so an explicit redeploy is the reliable path. - RESULT=$(curl -s --retry 2 --retry-all-errors --retry-delay 1 \ - --fail-with-body \ - -X POST "https://backboard.railway.com/graphql/v2" \ - -H "Authorization: Bearer $RAILWAY_TOKEN" \ - -H "Content-Type: application/json" \ - -d "{\"query\":\"mutation { serviceInstanceRedeploy(serviceId: \\\"$SERVICE_ID\\\", environmentId: \\\"$ENV_ID\\\") }\"}") - ERRORS=$(echo "$RESULT" | jq -r '.errors[]?.message // empty') - if [ -n "$ERRORS" ]; then - echo "::error::Railway redeploy failed for ${{ matrix.service.dispatch_name }}: $ERRORS" - exit 1 - fi - OK=$(echo "$RESULT" | jq -r '.data.serviceInstanceRedeploy // false') - if [ "$OK" != "true" ]; then - echo "::error::Railway redeploy did not confirm success for ${{ matrix.service.dispatch_name }}: $RESULT" - exit 1 + # job.status is one of: success, failure, cancelled. Normalize + # cancelled→skipped to match the BuildOutcome contract in + # showcase/scripts/lib/build-outputs.ts. We deliberately do NOT + # write to $GITHUB_OUTPUT — matrix-slot outputs are not + # aggregable across slots in GitHub Actions, so we publish the + # per-slot result as an artifact instead. The downstream + # aggregator job downloads every `build-result-*` artifact. + case "$BUILD_STATUS" in + success) STATUS=success ;; + failure) STATUS=failure ;; + *) STATUS=skipped ;; + esac + mkdir -p "$RUNNER_TEMP/build-result" + printf '{"service":"%s","status":"%s"}\n' "$SERVICE" "$STATUS" \ + > "$RUNNER_TEMP/build-result/result.json" + + - name: Upload per-slot build-result artifact + if: always() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + # Canonical per-slot name (see buildResultArtifactName in + # showcase/scripts/lib/build-outputs.ts). The aggregator + # downloads every artifact matching `build-result-*`. + name: build-result-${{ matrix.service.dispatch_name }} + path: ${{ runner.temp }}/build-result/result.json + if-no-files-found: error + retention-days: 7 + + aggregate-build-results: + name: Aggregate build results + needs: [detect-changes, build] + if: ${{ !cancelled() && needs.detect-changes.outputs.has_changes == 'true' }} + runs-on: ubuntu-latest + timeout-minutes: 3 + permissions: + contents: read + outputs: + results: ${{ steps.collect.outputs.results }} + any_success: ${{ steps.collect.outputs.any_success }} + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + persist-credentials: false + + - name: Setup pnpm + uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 + - name: Setup Node + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: 22.x + cache: pnpm + - name: Install + run: pnpm install --frozen-lockfile --ignore-scripts + + - name: Download all per-slot build-result artifacts + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + with: + # `pattern` matches every per-slot artifact emitted by the + # build matrix. `merge-multiple: false` keeps each artifact + # in its own subdirectory so we can iterate them deterministically. + pattern: build-result-* + path: ${{ runner.temp }}/build-results-in + merge-multiple: false + + - name: Collect per-service build outcomes + id: collect + env: + INPUT_DIR: ${{ runner.temp }}/build-results-in + OUTPUT_DIR: ${{ runner.temp }}/build-results-out + run: | + set -euo pipefail + mkdir -p "$OUTPUT_DIR" + # Each per-slot artifact extracts to + # $INPUT_DIR/build-result-/result.json + # The aggregator script (showcase/scripts/aggregate-build-results.ts) + # reads them, merges via the shared helper (mergeBuildResultFiles), + # writes $OUTPUT_DIR/results.json, and appends `results` + + # `any_success` to $GITHUB_OUTPUT. The contract (service + + # status enum) is enforced in one place (build-outputs.ts). + npx tsx showcase/scripts/aggregate-build-results.ts + + - name: Upload aggregated build-results artifact + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: build-results + path: ${{ runner.temp }}/build-results-out/results.json + if-no-files-found: error + retention-days: 7 + + redeploy-staging: + name: Trigger Railway staging redeploy + needs: [detect-changes, build, aggregate-build-results] + # Run if at least one service was in the matrix AND the build job + # was not outright cancelled or skipped AND at least one slot + # succeeded (per aggregate-build-results.outputs.any_success). The + # any_success guard is the explicit "do not redeploy when nothing + # was pushed" check — without it, an all-failed build run would + # still kick a redeploy that just re-pulls the stale :latest and + # silently looks healthy. The skipped/cancelled checks on the build + # job still cover the "verify-image-refs gate blocked the build job + # entirely" path. Partial build failures (fail-fast: false) still + # surface as needs.build.result == 'failure' with any_success == + # 'true', so redeploying what did get pushed is preserved. + if: >- + ${{ !cancelled() + && needs.detect-changes.outputs.has_changes == 'true' + && needs.build.result != 'skipped' + && needs.build.result != 'cancelled' + && needs.aggregate-build-results.outputs.any_success == 'true' }} + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + contents: read + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + persist-credentials: false + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: 22.x + - name: Compute changed-service list from build matrix + id: changed + env: + MATRIX_JSON: ${{ needs.detect-changes.outputs.matrix }} + BUILD_RESULTS_JSON: ${{ needs.aggregate-build-results.outputs.results }} + run: | + # We feed the redeploy script with the INTERSECTION of: + # (a) the scheduled build matrix (detect-changes.outputs.matrix — + # JSON array of objects with `dispatch_name`), and + # (b) the SUCCESS set from the aggregator + # (aggregate-build-results.outputs.results — JSON array of + # `{service: , status: success|failure|skipped}`; + # the `service` field is the dispatch_name; shape defined in + # showcase/scripts/lib/build-outputs.ts). + # Without this intersection a service whose Docker build FAILED + # would still be in the redeploy CSV, Railway would re-pull its + # stale `:latest`, and the verify workflow would report it as a + # fresh, healthy deploy — a false green. Skipped slots are also + # excluded (only `status == "success"` qualifies). + set -euo pipefail + matrix_names="$(echo "$MATRIX_JSON" | jq -r '[.[] | .dispatch_name]')" + success_names="$(echo "$BUILD_RESULTS_JSON" | jq -r '[.[] | select(.status == "success") | .service]')" + csv="$(jq -rn --argjson m "$matrix_names" --argjson s "$success_names" \ + '($m | map(select(. as $n | $s | index($n)))) | join(",")')" + if [ -z "$csv" ]; then + echo "No services in matrix ∩ success-set — skipping redeploy." + echo "services=" >> "$GITHUB_OUTPUT" + else + echo "services=$csv" >> "$GITHUB_OUTPUT" fi - echo "Railway redeploy triggered for ${{ matrix.service.dispatch_name }}" + echo "Computed services CSV (matrix ∩ build-success): $csv" + - name: Redeploy changed services in staging + if: steps.changed.outputs.services != '' + env: + RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }} + SERVICES_CSV: ${{ steps.changed.outputs.services }} + # Bridge the per-service redeploy summary to showcase_deploy.yml's + # `enforce-redeploy-gate` (consumed via the `redeploy-summary` + # artifact, extracted to `.redeploy/summary.json`). redeploy-env.ts + # writes this path atomically (.tmp → rename) but does NOT create + # parent dirs, so the step below mkdir's `.redeploy` first. + REDEPLOY_SUMMARY_JSON: .redeploy/summary.json + run: | + # Staging is non-blocking by design: the script always exits 0 + # and writes per-service failures into $GITHUB_STEP_SUMMARY. The + # verify-deploy workflow is the real release gate. + mkdir -p .redeploy + npx tsx showcase/scripts/redeploy-env.ts staging --services "$SERVICES_CSV" + + - name: Upload redeploy summary + # Upload is MANDATORY whenever a redeploy was attempted (services + # != ''). Both failure modes red the build — no false-green path: + # + # (A) HARD crash inside redeploy-env.ts BEFORE summary.json is + # written. redeploy-env.ts writes the summary atomically + # (.tmp → rename) AFTER the per-service loop completes, so + # a crash leaves no file. The redeploy step itself exits + # non-zero on that crash and fails the redeploy-staging + # job; this upload step is then skipped entirely by + # step-failure propagation. Build → red. + # + # (B) redeploy step exits 0 but summary.json is absent (e.g. a + # logic bug skipped the write). `if-no-files-found: error` + # reds this step → reds the redeploy-staging job → reds the + # build. The deploy workflow's resolve-matrix.if + # (workflow_run.conclusion == 'success') then blocks the + # deploy run from starting at all. + # + # Do NOT add hashFiles() guards here: that would silently skip + # the upload on (B), the deploy workflow would see "artifact + # absent" via check-redeploy-summary, treat it as "nothing + # redeployed", skip the gate, and ship a false-green. + # The legitimate "services == '' → nothing redeployed → no upload" + # path is preserved by the services != '' guard. + # + # If a future change ever switches this step to `if: always()`, + # `if-no-files-found: error` STILL reds path (A): the redeploy + # step's non-zero exit on a HARD crash is independent of upload + # gating, and `if-no-files-found: error` on `always()` then trips + # because summary.json was never written. So neither relaxation + # alone opens a false-green window. + # + # However, swapping the guard to `if: always()` would ALSO red the + # legitimate `services == ''` (nothing-to-redeploy) path — no + # summary.json is written there either, so `if-no-files-found: + # error` would trip on every push that didn't redeploy anything. + # Net effect: trades the (already-closed) false-green risk for a + # false-red on every non-buildable push. Don't do it. + if: steps.changed.outputs.services != '' + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + # Artifact name MUST stay `redeploy-summary`: showcase_deploy.yml's + # `resolve-matrix` job downloads it by this exact name and reads + # `.redeploy/summary.json` inside. + name: redeploy-summary + path: .redeploy/summary.json + if-no-files-found: error + retention-days: 7 + + notify-all-builds-failed: + name: Notify all builds failed (staging unchanged) + needs: [detect-changes, build, aggregate-build-results] + # Explicit "everything failed; nothing redeployed" signal — distinct + # from the `notify:` job below which fires on any build-job failure + # (some slots may still have succeeded in that case). Both jobs can + # fire; that's intentional and matches the Slack alert SOP. + if: >- + ${{ !cancelled() + && needs.detect-changes.outputs.has_changes == 'true' + && needs.build.result == 'failure' + && needs.aggregate-build-results.outputs.any_success == 'false' }} + # The `needs.build.result == 'failure'` clause guards against the case + # where the build job itself was SKIPPED (e.g. verify-image-refs failed + # upstream, so the matrix never executed). Without it, the aggregator + # would still report `any_success=false` and we'd Slack-spam "all + # builds failed" even though builds never ran — a misleading alert. + runs-on: ubuntu-latest + timeout-minutes: 3 + permissions: + contents: read + env: + SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_OSS_ALERTS }} + steps: + - name: Mark workflow red (no service succeeded) + run: | + echo "::error::All builds failed for this run; staging redeploy skipped; :latest unchanged." + exit 1 + - name: Slack #oss-alerts + if: always() && env.SLACK_WEBHOOK != '' + uses: slackapi/slack-github-action@b0fa283ad8fea605de13dc3f449259339835fc52 # v2.1.0 + with: + webhook: ${{ secrets.SLACK_WEBHOOK_OSS_ALERTS }} + webhook-type: incoming-webhook + payload: | + { "text": ${{ toJSON(format(':x: *Showcase: all builds failed*\nstaging unchanged (no redeploy)\nCommit: `{0}` by {1}\n', github.sha, github.actor, github.repository, github.run_id)) }} } notify: name: Notify on failure - needs: [build] + # Cover the whole build→aggregate→redeploy pipeline. `needs: [build]` + # alone meant a failure in aggregate-build-results or redeploy-staging + # produced ZERO Slack signal (verify just never ran). `if: failure()` + # already skips when none of the needs failed — so this still no-ops + # for the "no changes → build skipped" path, since skipped != failure. + # If `notify-all-builds-failed` also fires (genuine all-failed case), + # both alerts firing for the same event is acceptable per the alert SOP. + needs: [build, aggregate-build-results, redeploy-staging] if: failure() runs-on: ubuntu-latest permissions: diff --git a/.github/workflows/showcase_build_check.yml b/.github/workflows/showcase_build_check.yml index 8a58b62e53f..cb911bdeb1e 100644 --- a/.github/workflows/showcase_build_check.yml +++ b/.github/workflows/showcase_build_check.yml @@ -147,8 +147,8 @@ jobs: {"dispatch_name":"claude-sdk-python","filter_key":"claude_sdk_python","context":"showcase/integrations/claude-sdk-python","image":"showcase-claude-sdk-python","timeout":15,"lfs":false,"build_args":"","dockerfile":""}, {"dispatch_name":"built-in-agent","filter_key":"built_in_agent","context":"showcase/integrations/built-in-agent","image":"showcase-built-in-agent","timeout":15,"lfs":false,"build_args":"","dockerfile":""}, {"dispatch_name":"shell-dojo","filter_key":"shell_dojo","context":".","image":"showcase-shell-dojo","timeout":10,"lfs":false,"build_args":"","dockerfile":"showcase/shell-dojo/Dockerfile"}, - {"dispatch_name":"shell-dashboard","filter_key":"shell_dashboard","context":".","image":"showcase-shell-dashboard","timeout":10,"lfs":true,"build_args_sha":"__GH_SHA__","build_args_branch":"__GH_REF_NAME__","build_args_pb_url":"https://showcase-pocketbase-production.up.railway.app","build_args_shell_url":"https://showcase.copilotkit.ai","build_args_ops_url":"https://showcase-harness-production.up.railway.app","dockerfile":"showcase/shell-dashboard/Dockerfile"}, - {"dispatch_name":"shell-docs","filter_key":"shell_docs","context":".","image":"showcase-shell-docs","timeout":10,"lfs":true,"build_args_sha":"__GH_SHA__","build_args_branch":"__GH_REF_NAME__","build_args_base_url":"https://docs.copilotkit.ai","build_args_shell_url":"https://showcase.copilotkit.ai","build_args_analytics":"yes","dockerfile":"showcase/shell-docs/Dockerfile"}, + {"dispatch_name":"shell-dashboard","filter_key":"shell_dashboard","context":".","image":"showcase-shell-dashboard","timeout":10,"lfs":true,"build_args_sha":"__GH_SHA__","build_args_branch":"__GH_REF_NAME__","dockerfile":"showcase/shell-dashboard/Dockerfile"}, + {"dispatch_name":"shell-docs","filter_key":"shell_docs","context":".","image":"showcase-shell-docs","timeout":10,"lfs":true,"build_args_sha":"__GH_SHA__","build_args_branch":"__GH_REF_NAME__","dockerfile":"showcase/shell-docs/Dockerfile"}, {"dispatch_name":"showcase-harness","filter_key":"showcase_harness","context":".","image":"showcase-harness","timeout":20,"lfs":false,"build_args":"","dockerfile":"showcase/harness/Dockerfile"}, {"dispatch_name":"showcase-aimock","filter_key":"showcase_aimock","context":"showcase/aimock","image":"showcase-aimock","timeout":5,"lfs":false,"build_args":"","dockerfile":"showcase/aimock/Dockerfile"} ]' @@ -203,36 +203,12 @@ jobs: env: BUILD_ARGS_SHA: ${{ matrix.service.build_args_sha }} BUILD_ARGS_BRANCH: ${{ matrix.service.build_args_branch }} - BUILD_ARGS_PB_URL: ${{ matrix.service.build_args_pb_url }} - BUILD_ARGS_BASE_URL: ${{ matrix.service.build_args_base_url }} - BUILD_ARGS_SHELL_URL: ${{ matrix.service.build_args_shell_url }} - BUILD_ARGS_OPS_URL: ${{ matrix.service.build_args_ops_url }} - BUILD_ARGS_ANALYTICS: ${{ matrix.service.build_args_analytics }} run: | ARGS="" if [ -n "$BUILD_ARGS_SHA" ]; then ARGS="COMMIT_SHA=${BUILD_ARGS_SHA}" ARGS="${ARGS}"$'\n'"BRANCH=${BUILD_ARGS_BRANCH}" fi - if [ -n "$BUILD_ARGS_PB_URL" ]; then - ARGS="${ARGS:+${ARGS}$'\n'}NEXT_PUBLIC_POCKETBASE_URL=${BUILD_ARGS_PB_URL}" - fi - if [ -n "$BUILD_ARGS_BASE_URL" ]; then - ARGS="${ARGS:+${ARGS}$'\n'}NEXT_PUBLIC_BASE_URL=${BUILD_ARGS_BASE_URL}" - fi - if [ -n "$BUILD_ARGS_SHELL_URL" ]; then - ARGS="${ARGS:+${ARGS}$'\n'}NEXT_PUBLIC_SHELL_URL=${BUILD_ARGS_SHELL_URL}" - fi - if [ -n "$BUILD_ARGS_OPS_URL" ]; then - ARGS="${ARGS:+${ARGS}$'\n'}OPS_BASE_URL=${BUILD_ARGS_OPS_URL}" - fi - if [ "$BUILD_ARGS_ANALYTICS" = "yes" ]; then - ARGS="${ARGS:+${ARGS}$'\n'}NEXT_PUBLIC_POSTHOG_KEY=pr-build" - ARGS="${ARGS:+${ARGS}$'\n'}NEXT_PUBLIC_REB2B_KEY=pr-build" - ARGS="${ARGS:+${ARGS}$'\n'}NEXT_PUBLIC_SCARF_PIXEL_ID=pr-build" - ARGS="${ARGS:+${ARGS}$'\n'}NEXT_PUBLIC_REO_KEY=pr-build" - ARGS="${ARGS:+${ARGS}$'\n'}NEXT_PUBLIC_GOOGLE_ANALYTICS_TRACKING_ID=G-PRBUILD" - fi # Use delimiter to safely pass multiline value echo "args<> $GITHUB_OUTPUT echo "$ARGS" >> $GITHUB_OUTPUT diff --git a/.github/workflows/showcase_deploy.yml b/.github/workflows/showcase_deploy.yml index fae15686070..1a1fe083e03 100644 --- a/.github/workflows/showcase_deploy.yml +++ b/.github/workflows/showcase_deploy.yml @@ -1,14 +1,10 @@ name: "Showcase: Verify Deploy" -# Triggered after "Showcase: Build & Push" completes. This workflow verifies -# that Railway deployed the new images and that each service is healthy. -# The build workflow triggers Railway redeploys after pushing GHCR images; -# this workflow only verifies the result. -# -# This workflow CAN use cancel-in-progress: true because verification is -# idempotent. If a newer build completes while we're still verifying an -# older one, the newer verification supersedes — the older images are -# already stale. +# Triggered after "Showcase: Build & Push" completes. Verifies that the +# STAGING redeploy from the build workflow actually produced healthy +# services. Push-to-main redeploys staging only. This workflow is the +# staging gate; verify-deploy.ts is the parameterized probe driven off +# showcase/scripts/railway-envs.ts (the SSOT). on: workflow_run: @@ -18,260 +14,262 @@ on: workflow_dispatch: inputs: service: - description: "Service to verify" + description: "Service to verify (SSOT key or dispatch_name; 'all' = everything probe-eligible)" required: false default: "all" - type: choice - options: - - all - - shell - - langgraph-python - - mastra - - crewai-crews - - pydantic-ai - - google-adk - - ag2 - - agno - - llamaindex - - langgraph-fastapi - - langgraph-typescript - - langroid - - spring-ai - - strands - - ms-agent-python - - claude-sdk-typescript - - ms-agent-dotnet - - ms-agent-harness-dotnet - - claude-sdk-python - - built-in-agent - - shell-dojo - - shell-dashboard - - shell-docs - - showcase-harness - - showcase-aimock + type: string concurrency: group: showcase-verify-deploy cancel-in-progress: true -env: - RAILWAY_ENV_ID: "b14919f4-6417-429f-848d-c6ae2201e04f" - permissions: contents: read jobs: resolve-matrix: runs-on: ubuntu-latest - timeout-minutes: 3 + timeout-minutes: 5 permissions: contents: read actions: read - # Only run verification when the build workflow succeeded. - # On workflow_dispatch, always run (workflow_run context is absent). if: >- github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' outputs: - matrix: ${{ steps.build-matrix.outputs.matrix }} - has_services: ${{ steps.build-matrix.outputs.has_services }} - build_run_id: ${{ steps.build-matrix.outputs.build_run_id }} - build_run_url: ${{ steps.build-matrix.outputs.build_run_url }} + services_csv: ${{ steps.matrix.outputs.services_csv }} + has_services: ${{ steps.matrix.outputs.has_services }} + build_run_id: ${{ github.event.workflow_run.id }} + build_run_url: ${{ github.event.workflow_run.html_url }} + redeploy_red: ${{ steps.redeploy-gate.outputs.redeploy_red }} steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: persist-credentials: false + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: 22.x - - name: Build verification matrix - id: build-matrix - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - DISPATCH_SERVICE: ${{ github.event.inputs.service }} - WORKFLOW_RUN_ID: ${{ github.event.workflow_run.id }} - WORKFLOW_RUN_URL: ${{ github.event.workflow_run.html_url }} - REPO_FULL: ${{ github.repository }} - run: | - # Service registry: same as showcase_build.yml but only the fields - # needed for verification (dispatch_name, railway_id, health_path). - ALL_SERVICES='[ - {"dispatch_name":"shell","railway_id":"40eea0da-6071-4ea8-bdb9-39afb19225ec","health_path":"/"}, - {"dispatch_name":"langgraph-python","railway_id":"90d03214-4569-41b0-b4c1-6438a8a7b203","health_path":"/api/health"}, - {"dispatch_name":"mastra","railway_id":"d7979eb7-2405-4aab-ad21-438f4a1b08af","health_path":"/api/health"}, - {"dispatch_name":"crewai-crews","railway_id":"0e9c284d-8d87-4fcf-9f82-6b704d7e4bd4","health_path":"/api/health"}, - {"dispatch_name":"pydantic-ai","railway_id":"0a106173-2282-4887-a994-0ca276a99d69","health_path":"/api/health"}, - {"dispatch_name":"google-adk","railway_id":"87f60507-5a3d-4b8a-9e23-2b1de85d939c","health_path":"/api/health"}, - {"dispatch_name":"ag2","railway_id":"4a37481b-f264-4eb7-a9cd-0a9ebb9ac05c","health_path":"/api/health"}, - {"dispatch_name":"agno","railway_id":"32cab80b-e329-45bd-9c73-c4e1ddc94305","health_path":"/api/health"}, - {"dispatch_name":"llamaindex","railway_id":"285386e8-492d-4cb8-b632-0a7d4607378f","health_path":"/api/health"}, - {"dispatch_name":"langgraph-fastapi","railway_id":"06cccb5c-59f4-46b5-8adc-7113e77011a4","health_path":"/api/health"}, - {"dispatch_name":"langgraph-typescript","railway_id":"66246d3b-a18e-46f0-be51-5f3ff7a36e5a","health_path":"/api/health"}, - {"dispatch_name":"langroid","railway_id":"6dd9cb0a-66cc-46f1-972e-7cd74756157d","health_path":"/api/health"}, - {"dispatch_name":"spring-ai","railway_id":"eed5d041-91be-4282-b414-beea00843401","health_path":"/api/health"}, - {"dispatch_name":"strands","railway_id":"92e1cfad-ad53-403f-ab2b-5ab380832232","health_path":"/api/health"}, - {"dispatch_name":"ms-agent-python","railway_id":"655db75a-af8d-427d-a4f9-441570ae5003","health_path":"/api/health"}, - {"dispatch_name":"claude-sdk-typescript","railway_id":"18a98727-5700-44aa-b497-b60795dbbd6a","health_path":"/api/health"}, - {"dispatch_name":"ms-agent-dotnet","railway_id":"beeb2dd6-87a4-4599-aa07-0578f7bd6519","health_path":"/api/health"}, - {"dispatch_name":"ms-agent-harness-dotnet","railway_id":"6343d7f9-6c3f-4c8d-9a6e-79f03d2f1e37","health_path":"/api/health"}, - {"dispatch_name":"claude-sdk-python","railway_id":"b122ab65-9854-4cb2-a68e-b50ff13f7481","health_path":"/api/health"}, - {"dispatch_name":"built-in-agent","railway_id":"f4f8371a-bc46-45b2-b6d4-9c9af608bdbf","health_path":"/api/health"}, - {"dispatch_name":"shell-dojo","railway_id":"7ad1ece7-2228-49cd-8a78-bddf30322907","health_path":"/"}, - {"dispatch_name":"shell-dashboard","railway_id":"4d5dfd74-be61-40b2-8564-b53b7dd4c15b","health_path":"/"}, - {"dispatch_name":"shell-docs","railway_id":"7badfb8d-4228-414c-9145-b4026803714f","health_path":"/"}, - {"dispatch_name":"showcase-harness","railway_id":"3a14bfed-0537-4d71-897b-7c593dca161d","health_path":"/health"}, - {"dispatch_name":"showcase-aimock","railway_id":"0fa0435d-8a66-46f0-84fd-e4250b580013","health_path":"/health"} - ]' + - name: Check whether build uploaded a redeploy-summary artifact + id: check-redeploy-summary + # `actions/download-artifact@v4` with a `name:` HARD-FAILS when the + # named artifact does not exist. The artifact legitimately does + # NOT exist whenever the upstream build ran but redeployed nothing + # — e.g. a push touching `showcase/**` that fires the build's + # `paths:` filter, but `detect-changes` finds no buildable service + # changed, so `redeploy-staging` is skipped and never uploads. + # The build still concludes `success`, so this workflow fires on + # `workflow_run` and `resolve-matrix` runs. Without this pre-check + # the unguarded download would fail the job and (via + # `enforce-redeploy-gate` tripping on `result == 'failure'`) flip + # the whole deploy workflow RED — a false-red on a routine + # showcase-docs/script-only change. Listing artifacts via the API + # only requires `actions: read`, which `resolve-matrix` already has. + if: github.event_name == 'workflow_run' + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 + with: + script: | + // Fail loud on API error: github-script propagates unhandled + // rejections, which fails this step and (via the + // resolve-matrix.result == 'failure' clause on + // enforce-redeploy-gate) reds the workflow. Do NOT wrap this + // in try/catch — silently defaulting summary_present=false on + // a 5xx/403 would open the gate (skip verify) on what is + // actually a transient API failure, hiding a broken pipeline. + // + // Use the `name` query-param on listWorkflowRunArtifacts to + // ask the API to return only the redeploy-summary artifact. + // This makes the lookup robust to the build run uploading + // many artifacts (per-slot build-result-* + build-results + + // redeploy-summary — already ~28 today, well within per_page + // 100, but a future expansion past 100 would otherwise risk a + // false "absent" if redeploy-summary fell off the first page). + // The endpoint accepts `name` for an exact-match filter; we + // still paginate defensively in case the API returns multiple + // rows (e.g. an artifact with the same name re-uploaded). + const runId = context.payload.workflow_run.id; + const iterator = github.paginate.iterator( + github.rest.actions.listWorkflowRunArtifacts, + { + owner: context.repo.owner, + repo: context.repo.repo, + run_id: runId, + name: "redeploy-summary", + per_page: 100, + }, + ); + let present = false; + for await (const page of iterator) { + if ((page.data || []).some((a) => a.name === "redeploy-summary")) { + present = true; + break; + } + } + core.setOutput("summary_present", present ? "true" : "false"); + core.info(`redeploy-summary present for run ${runId}: ${present}`); - DISPATCH="$DISPATCH_SERVICE" - BUILD_RUN_ID="$WORKFLOW_RUN_ID" - BUILD_RUN_URL="$WORKFLOW_RUN_URL" + - name: Download redeploy summary from build workflow + # Three cases now handled distinctly: + # (a) workflow_dispatch — no `workflow_run` payload exists; the + # download is skipped and the bash `[ ! -f "$SUMMARY" ]` + # branch below treats it as "nothing to gate" (correct: a + # manual dispatch is not gated by a build's per-service set). + # (b) workflow_run + artifact absent — the upstream build + # redeployed nothing (e.g. no service had buildable + # changes); the precheck reports `summary_present=false`, + # the download is skipped, and the bash branch no-ops the + # gate. The deploy workflow should NOT red here — there is + # nothing to gate. + # (c) workflow_run + artifact PRESENT — we always attempt the + # download. We intentionally do NOT set + # `continue-on-error: true`: if the artifact exists but the + # download genuinely fails (network/permission), silently + # opening the gate would let verify probe the FULL service + # set against stale `:latest` and mask a broken redeploy as + # a green deploy. Fail loud instead. + if: >- + github.event_name == 'workflow_run' && + steps.check-redeploy-summary.outputs.summary_present == 'true' + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + with: + name: redeploy-summary + path: .redeploy + run-id: ${{ github.event.workflow_run.id }} + github-token: ${{ secrets.GITHUB_TOKEN }} - if [ "$DISPATCH" = "all" ] || [ -z "$DISPATCH" ]; then - if [ -n "$BUILD_RUN_ID" ]; then - # workflow_run trigger: discover which services the build - # workflow actually built by inspecting its matrix job names. - # Job names follow "build (, ...)" pattern. - JOBS_JSON=$(gh api "repos/${REPO_FULL}/actions/runs/${BUILD_RUN_ID}/jobs" --paginate 2>/dev/null \ - | jq -cs '[.[].jobs[]?]' 2>/dev/null || echo '[]') - BUILD_JOBS=$(echo "$JOBS_JSON" | jq -c '[.[] | select((.name // "") | startswith("build"))]') - # Extract dispatch_names from successful build job names - BUILT_SERVICES=$(echo "$BUILD_JOBS" | jq -c '[.[] | select(.conclusion == "success") | .name | capture("^build \\((?[^,)]+)") | .svc]') - # Filter ALL_SERVICES to only those that were actually built - MATRIX=$(echo "$ALL_SERVICES" | jq -c --argjson built "$BUILT_SERVICES" ' - [.[] | select(.dispatch_name as $dn | $built | index($dn) != null)] - ') - else - # workflow_dispatch with "all": verify every service - MATRIX=$(echo "$ALL_SERVICES" | jq -c '.') - fi - else - # Specific service dispatch - MATRIX=$(echo "$ALL_SERVICES" | jq -c --arg svc "$DISPATCH" '[.[] | select(.dispatch_name == $svc)]') + - name: Gate on staging redeploy errors + id: redeploy-gate + run: | + set -euo pipefail + SUMMARY=".redeploy/summary.json" + if [ ! -f "$SUMMARY" ]; then + echo "No redeploy summary found (workflow_dispatch path or build did not upload). Skipping gate." + echo "redeploy_red=false" >> "$GITHUB_OUTPUT" + echo "ok_services=" >> "$GITHUB_OUTPUT" + exit 0 fi - - echo "matrix=$MATRIX" >> "$GITHUB_OUTPUT" - if [ "$MATRIX" = "[]" ]; then - echo "has_services=false" >> "$GITHUB_OUTPUT" + # Shape guard: redeploy-env.ts writes per-entry `status` of + # exactly "ok" or "error". If the schema ever drifts (e.g. + # `status`→`state`, `ok`→`success`) every `select(.status==...)` + # silently yields empty → redeploy_red=false AND ok_services="" + # → resolve-verify-matrix skips verify on a real unverified + # redeploy = green CI on a broken release. Refuse the ambiguity: + # if the file has entries but ANY entry is missing a valid + # ok|error status (partial drift — some rows on the legacy schema, + # some on the new one), fail loud here so enforce-redeploy-gate + # reds the workflow (resolve-matrix.result == 'failure' fans into + # the gate). The previous TOTAL>0 && WITH_STATUS==0 check was + # all-or-nothing and silently dropped the drifted rows on a mixed + # summary. + TOTAL=$(jq 'length' "$SUMMARY") + WITH_STATUS=$(jq '[.[] | select(.status == "ok" or .status == "error")] | length' "$SUMMARY") + if [ "$TOTAL" -gt 0 ] && [ "$WITH_STATUS" -lt "$TOTAL" ]; then + echo "::error::summary.json shape drift: $WITH_STATUS of $TOTAL entries have status ok|error" + exit 1 + fi + # Per spec §3: the workflow MUST turn red on any staging + # status:"error", while verify still runs against the success-set. + ERRORS=$(jq -c '[.[] | select(.status == "error")]' "$SUMMARY") + ERROR_COUNT=$(echo "$ERRORS" | jq 'length') + OK=$(jq -r '[.[] | select(.status == "ok") | .service] | join(",")' "$SUMMARY") + echo "ok_services=$OK" >> "$GITHUB_OUTPUT" + if [ "$ERROR_COUNT" -gt 0 ]; then + echo "::error::Staging redeploy reported $ERROR_COUNT per-service error(s):" + echo "$ERRORS" | jq -r '.[] | " - \(.service): \(.error)"' + echo "redeploy_red=true" >> "$GITHUB_OUTPUT" else - echo "has_services=true" >> "$GITHUB_OUTPUT" + echo "redeploy_red=false" >> "$GITHUB_OUTPUT" fi - echo "build_run_id=${BUILD_RUN_ID}" >> "$GITHUB_OUTPUT" - echo "build_run_url=${BUILD_RUN_URL}" >> "$GITHUB_OUTPUT" + + - name: Build verify matrix from SSOT + id: matrix + env: + DISPATCH_SERVICE: ${{ github.event.inputs.service }} + OK_FROM_REDEPLOY: ${{ steps.redeploy-gate.outputs.ok_services }} + EVENT_NAME: ${{ github.event_name }} + SUMMARY_PRESENT: ${{ steps.check-redeploy-summary.outputs.summary_present }} + # The decision-table that picks the verify matrix lives in + # showcase/scripts/resolve-verify-matrix.ts (pure function + + # unit tests). Summary of cases: + # - workflow_dispatch + 'all'/empty → full probe-eligible set. + # - workflow_dispatch + specific svc → just that service + # (unknown name → error exit). + # - workflow_run + summary_present=false → has_services=false + # (build redeployed nothing). + # - workflow_run + summary_present=true + ok empty + # → has_services=false (success-set empty; verify is skipped). + # In practice redeploy-env.ts only emits status ok|error, + # so this branch implies redeploy_red=true and + # enforce-redeploy-gate reds the workflow independently — + # skipping verify here is correct (no ok services left to + # probe; the gate has already turned the workflow red). + # - workflow_run + summary_present=true + ok non-empty + # → intersect ok_services (SSOT key OR dispatchName aliases) + # with probe.staging-eligible SSOT services. has_services + # reflects CSV emptiness. When the intersection collapses + # to empty, verify is skipped; if there were per-service + # errors, enforce-redeploy-gate reds the workflow — otherwise + # the run is correctly green (every redeploy succeeded, just + # none probe-eligible). + run: npx tsx showcase/scripts/resolve-verify-matrix.ts verify: needs: [resolve-matrix] if: needs.resolve-matrix.outputs.has_services == 'true' runs-on: ubuntu-latest - timeout-minutes: 15 + timeout-minutes: 20 environment: railway permissions: contents: read actions: read - strategy: - fail-fast: false - matrix: - service: ${{ fromJSON(needs.resolve-matrix.outputs.matrix) }} - steps: - - name: Verify deploy health + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + persist-credentials: false + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: 22.x + + - name: Install + working-directory: showcase/scripts + run: npm ci + + - name: Run verify-deploy --env staging env: RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }} - SERVICE_ID: ${{ matrix.service.railway_id }} - ENV_ID: ${{ env.RAILWAY_ENV_ID }} + SERVICES_CSV: ${{ needs.resolve-matrix.outputs.services_csv }} run: | - # Fail fast if RAILWAY_TOKEN is not set if [ -z "$RAILWAY_TOKEN" ]; then echo "::error::RAILWAY_TOKEN is not set" exit 1 fi + npx tsx showcase/scripts/verify-deploy.ts --env staging --services "$SERVICES_CSV" - echo "Verifying Railway deploy for ${{ matrix.service.dispatch_name }}..." - - # The build workflow triggers Railway redeploys after pushing - # GHCR images. We poll until the service is healthy. 600s - # budget accommodates JVM/slow-boot services. - # Require 2 consecutive healthy polls before declaring success. - HEALTHY_STREAK=0 - REQUIRED_STREAK=2 - START=$(date +%s) - BUDGET=600 - i=0 - while [ $(($(date +%s) - START)) -lt "$BUDGET" ]; do - i=$((i + 1)) - RESULT=$(curl -s --retry 2 --retry-all-errors --retry-delay 1 \ - --fail-with-body \ - -H "Authorization: Bearer $RAILWAY_TOKEN" \ - -H "Content-Type: application/json" \ - -d "{\"query\":\"query { deployments(first: 1, input: { serviceId: \\\"$SERVICE_ID\\\", environmentId: \\\"$ENV_ID\\\" }) { edges { node { id status staticUrl } } } }\"}" \ - https://backboard.railway.com/graphql/v2 2>/dev/null) - - # Surface GraphQL errors and fail fast - ERRORS=$(echo "$RESULT" | jq -r '.errors[]?.message // empty') - if [ -n "$ERRORS" ]; then - echo "::error::Railway API error: $ERRORS" - exit 1 - fi - - DEPLOY_ID=$(echo "$RESULT" | jq -r '.data.deployments.edges[0].node.id // empty') - STATUS=$(echo "$RESULT" | jq -r '.data.deployments.edges[0].node.status // empty') - DOMAIN=$(echo "$RESULT" | jq -r '.data.deployments.edges[0].node.staticUrl // empty') - echo "Attempt $i: deploy=$DEPLOY_ID status=$STATUS domain=$DOMAIN streak=$HEALTHY_STREAK" - - # Fail on any terminal failure status - case "$STATUS" in - CRASHED|FAILED|REMOVED|SKIPPED) - echo "::error::Service ${{ matrix.service.dispatch_name }} deploy status: $STATUS" - exit 1 - ;; - esac - - if [ "$STATUS" = "SUCCESS" ] && [ -n "$DOMAIN" ]; then - HEALTH_PATH="${{ matrix.service.health_path }}" - if [ -z "$HEALTH_PATH" ]; then - echo "::error::health_path not configured for service ${{ matrix.service.dispatch_name }}" - exit 1 - fi - HEALTH_URL="https://${DOMAIN}${HEALTH_PATH}" - # Retry probe up to 3 times within this iteration before - # treating it as a genuine non-200 - HTTP_CODE="000" - for probe_try in 1 2 3; do - HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 15 "$HEALTH_URL" 2>/dev/null || echo "000") - if [ "$HTTP_CODE" = "200" ]; then - break - fi - if [ "$probe_try" -lt 3 ]; then - echo "HTTP check: $HEALTH_URL -> $HTTP_CODE (retry $probe_try/3)" - sleep 2 - fi - done - echo "HTTP check: $HEALTH_URL -> $HTTP_CODE" - # Fail fast on permanent client errors - case "$HTTP_CODE" in - 404|410|501) - echo "::error::Service ${{ matrix.service.dispatch_name }} health endpoint returned $HTTP_CODE at $HEALTH_URL" - exit 1 - ;; - esac - if [ "$HTTP_CODE" = "200" ]; then - HEALTHY_STREAK=$((HEALTHY_STREAK + 1)) - if [ "$HEALTHY_STREAK" -ge "$REQUIRED_STREAK" ]; then - echo "Service healthy ($HEALTHY_STREAK consecutive 200s)" - exit 0 - fi - else - HEALTHY_STREAK=0 - fi - else - HEALTHY_STREAK=0 - fi - - sleep 15 - done - echo "::error::Service ${{ matrix.service.dispatch_name }} did not become healthy within 600s" + enforce-redeploy-gate: + # Spec §3: workflow turns red on any per-service redeploy error, even + # if verify against the success-set passes. This job is independent of + # verify so the user sees both signals (what was redeployed badly, + # what was redeployed and is unhealthy) rather than one masking the other. + needs: [resolve-matrix] + # Trip the gate on EITHER a per-service redeploy error OR a complete + # resolve-matrix failure. A resolve-matrix failure leaves the + # `redeploy_red` output empty (jobs that fail mid-step don't publish + # outputs reliably), which would otherwise let an upstream crash slip + # past as "not red" — a silent bypass of the gate. + if: always() && (needs.resolve-matrix.outputs.redeploy_red == 'true' || needs.resolve-matrix.result == 'failure') + runs-on: ubuntu-latest + timeout-minutes: 2 + permissions: + contents: read + steps: + - name: Fail workflow on staging redeploy errors + run: | + echo "::error::One or more staging services reported status:error in the redeploy summary." + echo "See the resolve-matrix job's 'Gate on staging redeploy errors' step for details." exit 1 notify-harness: - needs: [resolve-matrix, verify] + needs: [resolve-matrix, verify, enforce-redeploy-gate] if: always() && needs.resolve-matrix.outputs.has_services == 'true' permissions: contents: read @@ -283,64 +281,28 @@ jobs: id: payload env: VERIFY_RESULT: ${{ needs.verify.result }} - MATRIX: ${{ needs.resolve-matrix.outputs.matrix }} + REDEPLOY_RESULT: ${{ needs.enforce-redeploy-gate.result }} + CSV: ${{ needs.resolve-matrix.outputs.services_csv }} BUILD_RUN_ID: ${{ needs.resolve-matrix.outputs.build_run_id }} BUILD_RUN_URL: ${{ needs.resolve-matrix.outputs.build_run_url }} RUN_ID: ${{ github.run_id }} RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - REPO_FULL: ${{ github.repository }} run: | set -euo pipefail - - SERVICES=$(echo "$MATRIX" | jq -c '[.[].dispatch_name]' 2>/dev/null || echo '[]') - SERVICES=${SERVICES:-'[]'} - - if [ "${VERIFY_RESULT}" = "cancelled" ]; then - FAILED='[]' - SUCCEEDED='[]' - CANCELLED=true - elif [ "${VERIFY_RESULT}" = "success" ]; then - FAILED='[]' - SUCCEEDED="$SERVICES" - CANCELLED=false + SERVICES=$(echo "$CSV" | jq -R 'split(",") | map(select(length>0))') + if [ "$VERIFY_RESULT" = "success" ] && [ "$REDEPLOY_RESULT" != "failure" ]; then + STATE="success" + elif [ "$VERIFY_RESULT" = "cancelled" ]; then + STATE="cancelled" else - # Partial failure: query per-job conclusions from the verify matrix - JOBS_JSON=$(gh api "repos/${REPO_FULL}/actions/runs/${RUN_ID}/jobs" --paginate 2>/dev/null \ - | jq -cs '[.[].jobs[]?]' 2>/dev/null || echo '[]') - VERIFY_JOBS=$(echo "$JOBS_JSON" | jq -c '[.[] | select((.name // "") | startswith("verify"))]' 2>/dev/null || echo '[]') - FAILED=$(echo "$SERVICES" | jq -c --argjson jobs "$VERIFY_JOBS" ' - [ - .[] as $svc - | $jobs[] - | select((.name // "") as $n | ($n | contains($svc))) - | select(.conclusion == "failure") - | $svc - ] | unique - ' 2>/dev/null || echo "$SERVICES") - SUCCEEDED=$(echo "$SERVICES" | jq -c --argjson jobs "$VERIFY_JOBS" ' - [ - .[] as $svc - | $jobs[] - | select((.name // "") as $n | ($n | contains($svc))) - | select(.conclusion == "success") - | $svc - ] | unique - ' 2>/dev/null || echo '[]') - CANCELLED=false + STATE="failure" fi PAYLOAD=$(jq -cn \ - --arg runId "$RUN_ID" \ - --arg runUrl "$RUN_URL" \ - --arg buildRunId "${BUILD_RUN_ID:-}" \ - --arg buildRunUrl "${BUILD_RUN_URL:-}" \ - --arg gateReason "" \ + --arg runId "$RUN_ID" --arg runUrl "$RUN_URL" \ + --arg buildRunId "${BUILD_RUN_ID:-}" --arg buildRunUrl "${BUILD_RUN_URL:-}" \ + --arg state "$STATE" \ --argjson services "$SERVICES" \ - --argjson failed "$FAILED" \ - --argjson succeeded "$SUCCEEDED" \ - --argjson cancelled "$CANCELLED" \ - --argjson gateSkipped false \ - '{runId:$runId,runUrl:$runUrl,buildRunId:$buildRunId,buildRunUrl:$buildRunUrl,services:$services,failed:$failed,succeeded:$succeeded,cancelled:$cancelled,gateSkipped:$gateSkipped,gateReason:$gateReason}') + '{runId:$runId,runUrl:$runUrl,buildRunId:$buildRunId,buildRunUrl:$buildRunUrl,services:$services,state:$state}') { echo "payload<; runs the +# spec §7 hardening (P1..P6). +# 3. verify-prod → verify-deploy.ts --env prod for the +# target service(s). Feature-level probes, +# not naked 200. +# 4. notify → Slack #oss-alerts on any red. Never #engr. + +on: + workflow_dispatch: + inputs: + service: + description: "SSOT key, dispatch_name, or 'all'" + required: true + default: "all" + type: string + digest: + description: "Optional digest override (default: snapshot from staging)" + required: false + type: string + +concurrency: + group: showcase-promote-${{ inputs.service }} + cancel-in-progress: false + +permissions: + contents: read + +jobs: + resolve-targets: + runs-on: ubuntu-latest + timeout-minutes: 3 + permissions: + contents: read + outputs: + services_csv: ${{ steps.resolve.outputs.services_csv }} + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + persist-credentials: false + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: 22.x + - name: Generate SSOT artifact + working-directory: showcase/scripts + run: | + npm ci + npx tsx emit-railway-envs-json.ts + - name: Resolve target service set + id: resolve + env: + INPUT: ${{ inputs.service }} + run: | + set -euo pipefail + GENERATED="showcase/scripts/railway-envs.generated.json" + if [ "$INPUT" = "all" ]; then + CSV=$(jq -r '.services[] | select(.probe.prod == true) | .name' "$GENERATED" | sort -u | tr '\n' ',' | sed 's/,$//') + else + RESOLVED=$(jq -r --arg s "$INPUT" ' + .services[] | select(.name == $s or .dispatchName == $s) | .name + ' "$GENERATED" | head -n1) + if [ -z "$RESOLVED" ]; then + echo "::error::Unknown service '$INPUT' (not an SSOT key or dispatch_name)" + exit 1 + fi + CSV="$RESOLVED" + fi + echo "services_csv=$CSV" >> "$GITHUB_OUTPUT" + + verify-staging-precondition: + needs: [resolve-targets] + runs-on: ubuntu-latest + timeout-minutes: 15 + environment: railway + permissions: + contents: read + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + persist-credentials: false + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: 22.x + - working-directory: showcase/scripts + run: npm ci + - name: Live-probe staging for promote precondition + env: + RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }} + SERVICES_CSV: ${{ needs.resolve-targets.outputs.services_csv }} + run: | + if [ -z "$RAILWAY_TOKEN" ]; then + echo "::error::RAILWAY_TOKEN is not set" + exit 1 + fi + # Spec §7.2 P3: the live staging probe at promote time is + # authoritative. CI verify history is a leading indicator only. + npx tsx showcase/scripts/verify-deploy.ts --env staging --services "$SERVICES_CSV" + + promote: + needs: [resolve-targets, verify-staging-precondition] + runs-on: ubuntu-latest + timeout-minutes: 20 + environment: railway + permissions: + contents: read + packages: read + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + persist-credentials: false + - uses: ruby/setup-ruby@afeafc3d1ab54a631816aba4c914a0081c12ff2f # v1.310.0 + with: + ruby-version: "3.3" + bundler-cache: false + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: 22.x + - working-directory: showcase/scripts + run: | + npm ci + npx tsx emit-railway-envs-json.ts + - name: bin/railway promote + env: + RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }} + GHCR_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SERVICES_CSV: ${{ needs.resolve-targets.outputs.services_csv }} + DIGEST: ${{ inputs.digest }} + run: | + set -euo pipefail + if [ -z "$RAILWAY_TOKEN" ]; then + echo "::error::RAILWAY_TOKEN is not set" + exit 1 + fi + # The local promote runs each service in turn; bin/railway + # handles spec §7 preconditions (P1..P6). --require-staging-green + # is default-on; the prior job already established staging is + # green but we keep the script-level guard as defense in depth. + IFS=',' read -ra SVCS <<< "$SERVICES_CSV" + for svc in "${SVCS[@]}"; do + args=(promote "$svc") + if [ -n "${DIGEST:-}" ]; then + args+=(--digest "$DIGEST") + fi + echo "==> showcase/bin/railway ${args[*]}" + showcase/bin/railway "${args[@]}" + done + + verify-prod: + needs: [resolve-targets, promote] + runs-on: ubuntu-latest + timeout-minutes: 20 + environment: railway + permissions: + contents: read + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + persist-credentials: false + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: 22.x + - working-directory: showcase/scripts + run: npm ci + - name: Run verify-deploy --env prod + env: + RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }} + SERVICES_CSV: ${{ needs.resolve-targets.outputs.services_csv }} + run: | + if [ -z "$RAILWAY_TOKEN" ]; then + echo "::error::RAILWAY_TOKEN is not set" + exit 1 + fi + npx tsx showcase/scripts/verify-deploy.ts --env prod --services "$SERVICES_CSV" + + notify: + # Slack #oss-alerts only. Never #engr (engr is sacred — release alerts only). + needs: [resolve-targets, verify-staging-precondition, promote, verify-prod] + if: always() + runs-on: ubuntu-latest + timeout-minutes: 3 + permissions: + contents: read + actions: read + steps: + - name: Compute state + id: state + env: + PRE: ${{ needs.verify-staging-precondition.result }} + PROMOTE: ${{ needs.promote.result }} + PROD: ${{ needs.verify-prod.result }} + CSV: ${{ needs.resolve-targets.outputs.services_csv }} + run: | + set -euo pipefail + if [ "$PRE" = "success" ] && [ "$PROMOTE" = "success" ] && [ "$PROD" = "success" ]; then + STATE="success"; ICON=":white_check_mark:" + else + STATE="failure"; ICON=":x:" + fi + { + echo "state=$STATE" + echo "icon=$ICON" + echo "csv=$CSV" + } >> "$GITHUB_OUTPUT" + - name: Post to #oss-alerts + if: steps.state.outputs.state == 'failure' + uses: slackapi/slack-github-action@b0fa283ad8fea605de13dc3f449259339835fc52 # v2.1.0 + with: + webhook: ${{ secrets.SLACK_WEBHOOK_OSS_ALERTS }} + webhook-type: incoming-webhook + payload: | + { + "text": ${{ toJSON(format( + '{0} *Showcase Promote Failed*\nServices: `{1}`\npre-staging={2} promote={3} verify-prod={4}\n<{5}/{6}/actions/runs/{7}|View run>', + steps.state.outputs.icon, + steps.state.outputs.csv, + needs.verify-staging-precondition.result, + needs.promote.result, + needs.verify-prod.result, + github.server_url, + github.repository, + github.run_id + )) }} + } diff --git a/.oxlintrc.json b/.oxlintrc.json index abf148fe9f5..e11a6ac5919 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -73,6 +73,28 @@ "typescript/no-import-type-side-effects": "off", "import/consistent-type-specifier-style": "off" } + }, + { + "files": [ + "showcase/shell-dashboard/src/**/*.{ts,tsx}", + "showcase/shell-docs/src/**/*.{ts,tsx}", + "showcase/shell/src/**/*.{ts,tsx}", + "showcase/shell-dojo/src/**/*.{ts,tsx}" + ], + "rules": { + "copilotkit/no-public-env-shell-read": "error" + } + }, + { + "files": [ + "showcase/shell-docs/src/content/**", + "showcase/**/lib/runtime-config*.{ts,tsx}", + "showcase/**/*.test.{ts,tsx}", + "showcase/**/*.spec.{ts,tsx}" + ], + "rules": { + "copilotkit/no-public-env-shell-read": "off" + } } ], "ignorePatterns": [ diff --git a/examples/integrations/langgraph-python-threads/apps/app/package.json b/examples/integrations/langgraph-python-threads/apps/app/package.json index e2f3f6f11a0..03614aee17f 100644 --- a/examples/integrations/langgraph-python-threads/apps/app/package.json +++ b/examples/integrations/langgraph-python-threads/apps/app/package.json @@ -8,9 +8,9 @@ "start": "node server.mjs" }, "dependencies": { - "@ag-ui/client": "0.0.52", - "@copilotkit/a2ui-renderer": "1.57.0", - "@copilotkit/react-core": "1.57.0", + "@ag-ui/client": "0.0.53", + "@copilotkit/a2ui-renderer": "1.59.1", + "@copilotkit/react-core": "1.59.1", "@hono/node-server": "^1.19.14", "@radix-ui/react-checkbox": "^1.3.3", "@radix-ui/react-label": "^2.1.8", diff --git a/examples/integrations/langgraph-python-threads/apps/bff/package.json b/examples/integrations/langgraph-python-threads/apps/bff/package.json index 52cc877e507..62a617a9793 100644 --- a/examples/integrations/langgraph-python-threads/apps/bff/package.json +++ b/examples/integrations/langgraph-python-threads/apps/bff/package.json @@ -9,7 +9,7 @@ "start": "node dist/server.js" }, "dependencies": { - "@copilotkit/runtime": "1.57.0", + "@copilotkit/runtime": "1.59.1", "@hono/node-server": "^1.13.6", "express": "5.2.1", "hono": "^4.7.11", diff --git a/examples/integrations/langgraph-python-threads/package-lock.json b/examples/integrations/langgraph-python-threads/package-lock.json index c4fa848a349..9847c7c0518 100644 --- a/examples/integrations/langgraph-python-threads/package-lock.json +++ b/examples/integrations/langgraph-python-threads/package-lock.json @@ -5,6 +5,7 @@ "packages": { "": { "name": "copilotkit-langgraph-template", + "hasInstallScript": true, "workspaces": [ "apps/*" ], @@ -21,451 +22,45 @@ } }, "apps/app": { - "name": "@repo/app", - "version": "0.1.0", - "dependencies": { - "@ag-ui/client": "0.0.52", - "@copilotkit/a2ui-renderer": "1.57.0", - "@copilotkit/react-core": "1.57.0", - "@copilotkit/react-ui": "1.57.0", - "@hono/node-server": "^1.19.14", - "@radix-ui/react-checkbox": "^1.3.3", - "@radix-ui/react-label": "^2.1.8", - "@radix-ui/react-separator": "^1.1.8", - "@tanstack/pacer": "0.21.0", - "class-variance-authority": "^0.7.1", - "clsx": "^2.1.1", - "hono": "^4.9.8", - "lucide-react": "^0.577.0", - "react": "^19.2.4", - "react-dom": "^19.2.4", - "react-rnd": "^10.5.2", - "react18-json-view": "^0.2.9", - "recharts": "^3.7.0", - "tailwind-merge": "^3.5.0", - "zod": "^3.23.8" - }, - "devDependencies": { - "@tailwindcss/postcss": "^4", - "@types/node": "^20", - "@types/react": "^19", - "@types/react-dom": "^19", - "@vitejs/plugin-react": "^5.1.0", - "tailwindcss": "^4", - "typescript": "^5", - "vite": "^7.1.12" - } - }, - "apps/app/node_modules/@ag-ui/core": { - "version": "0.0.53", - "resolved": "https://registry.npmjs.org/@ag-ui/core/-/core-0.0.53.tgz", - "integrity": "sha512-11UocR7fFdMWw503bWCX2IOK15vbWfxT11Mn9xOiPBVO/UVcn57ywGrlLL4UaBlPgmUTvuzr2yYR2ElSqiN2wQ==", - "dependencies": { - "zod": "^3.22.4" - } - }, - "apps/app/node_modules/@ag-ui/encoder": { - "version": "0.0.53", - "resolved": "https://registry.npmjs.org/@ag-ui/encoder/-/encoder-0.0.53.tgz", - "integrity": "sha512-bAOcfVdm6U4H6G6tW+DZfwPEQm1w/snVBTwaFn9nJcEMW69M7/HZuwvEc/7Zo0rK1jRL32N/j60PwTAeky19fw==", - "dependencies": { - "@ag-ui/core": "0.0.53", - "@ag-ui/proto": "0.0.53" - } - }, - "apps/app/node_modules/@ag-ui/proto": { - "version": "0.0.53", - "resolved": "https://registry.npmjs.org/@ag-ui/proto/-/proto-0.0.53.tgz", - "integrity": "sha512-swjz22xWT8YUZt5OhmUwkARDQdwt8XM1hmGZbQrhRnNPXKwrKJX9ELlbnQ4iFUQIKkMWpphzE3vA3yNKs2bbKw==", - "dependencies": { - "@ag-ui/core": "0.0.53", - "@bufbuild/protobuf": "^2.2.5", - "@protobuf-ts/protoc": "^2.11.1" - } - }, - "apps/app/node_modules/@copilotkit/a2ui-renderer": { - "version": "1.57.0", - "resolved": "https://registry.npmjs.org/@copilotkit/a2ui-renderer/-/a2ui-renderer-1.57.0.tgz", - "integrity": "sha512-5Fn48h02oj1e0gGHc+lSnKvh4yrP4MPS4lpnBMSnif/kUzuNcyxGblnBRdxfjZo8gWj9Un8Who5Wy513CdToMg==", - "license": "MIT", - "dependencies": { - "@a2ui/web_core": "0.9.0", - "clsx": "^2.1.1", - "zod": "^3.25.75", - "zod-to-json-schema": "^3.24.1" - }, - "peerDependencies": { - "react": "^18 || ^19 || ^19.0.0-rc", - "react-dom": "^18 || ^19 || ^19.0.0-rc" - } - }, - "apps/app/node_modules/@copilotkit/core": { - "version": "1.57.0", - "resolved": "https://registry.npmjs.org/@copilotkit/core/-/core-1.57.0.tgz", - "integrity": "sha512-fCh9/71uJO0yT2b/0o/Rf4moDEYP8nfpJZxy6/xfEJxUsz0NIb8vnQgGO1iE2l2BWTVP2Gg2iGn/Pe9K0YfF1g==", - "dependencies": { - "@ag-ui/client": "0.0.53", - "@copilotkit/shared": "1.57.0", - "@tanstack/pacer": "^0.20.1", - "phoenix": "^1.8.4", - "rxjs": "7.8.1", - "zod-to-json-schema": "^3.24.6" - }, - "engines": { - "node": ">=18" - } - }, - "apps/app/node_modules/@copilotkit/core/node_modules/@ag-ui/client": { - "version": "0.0.53", - "resolved": "https://registry.npmjs.org/@ag-ui/client/-/client-0.0.53.tgz", - "integrity": "sha512-Mkup36KUp0KXy9v89QtAOWDUoh8H1s1Vgl4zvQv9HqXuAK1TkbtpXJHpbgZJXIxTqd54KT6yCurmC2UkOP7FDQ==", - "dependencies": { - "@ag-ui/core": "0.0.53", - "@ag-ui/encoder": "0.0.53", - "@ag-ui/proto": "0.0.53", - "@types/uuid": "^10.0.0", - "compare-versions": "^6.1.1", - "fast-json-patch": "^3.1.1", - "rxjs": "7.8.1", - "untruncate-json": "^0.0.1", - "uuid": "^11.1.0", - "zod": "^3.22.4" - } - }, - "apps/app/node_modules/@copilotkit/core/node_modules/@tanstack/pacer": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@tanstack/pacer/-/pacer-0.20.1.tgz", - "integrity": "sha512-ZNQ1bIL6eUXVKdic0tiImvBVkWrg/IoSK6VIacTrO3d3HAGnd70qFJNJagR/YOJIOw4EKGWnodwpYZkN1pWuVQ==", - "license": "MIT", - "dependencies": { - "@tanstack/devtools-event-client": "^0.4.3", - "@tanstack/store": "^0.9.3" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - } - }, - "apps/app/node_modules/@copilotkit/react-core": { - "version": "1.57.0", - "resolved": "https://registry.npmjs.org/@copilotkit/react-core/-/react-core-1.57.0.tgz", - "integrity": "sha512-q+Vs+Dyu+ng1/JziWurTLMjev/f0FOz3NCGXT/aSQ2/Xm0ZLF6WzUbJnTRd2R7xdyY8y8UAez0cgnuxbFbXVDA==", - "license": "MIT", - "dependencies": { - "@ag-ui/client": "0.0.53", - "@ag-ui/core": "0.0.53", - "@copilotkit/a2ui-renderer": "1.57.0", - "@copilotkit/core": "1.57.0", - "@copilotkit/runtime-client-gql": "1.57.0", - "@copilotkit/shared": "1.57.0", - "@copilotkit/web-inspector": "1.57.0", - "@jetbrains/websandbox": "^1.1.3", - "@lit-labs/react": "^2.0.2", - "@radix-ui/react-dropdown-menu": "^2.1.15", - "@radix-ui/react-slot": "^1.2.3", - "@radix-ui/react-tooltip": "^1.2.7", - "@scarf/scarf": "^1.3.0", - "@tanstack/react-virtual": "^3.13.0", - "class-variance-authority": "^0.7.1", - "clsx": "^2.1.1", - "katex": "^0.16.22", - "lucide-react": "^0.525.0", - "react-markdown": "^8.0.7", - "rxjs": "7.8.1", - "streamdown": "^1.3.0", - "tailwind-merge": "^3.3.1", - "tw-animate-css": "^1.3.5", - "untruncate-json": "^0.0.1", - "use-stick-to-bottom": "^1.1.1", - "zod-to-json-schema": "^3.24.5" - }, - "peerDependencies": { - "react": "^18 || ^19 || ^19.0.0-rc", - "react-dom": "^18 || ^19 || ^19.0.0-rc", - "zod": ">=3.0.0" - } - }, - "apps/app/node_modules/@copilotkit/react-core/node_modules/@ag-ui/client": { - "version": "0.0.53", - "resolved": "https://registry.npmjs.org/@ag-ui/client/-/client-0.0.53.tgz", - "integrity": "sha512-Mkup36KUp0KXy9v89QtAOWDUoh8H1s1Vgl4zvQv9HqXuAK1TkbtpXJHpbgZJXIxTqd54KT6yCurmC2UkOP7FDQ==", - "dependencies": { - "@ag-ui/core": "0.0.53", - "@ag-ui/encoder": "0.0.53", - "@ag-ui/proto": "0.0.53", - "@types/uuid": "^10.0.0", - "compare-versions": "^6.1.1", - "fast-json-patch": "^3.1.1", - "rxjs": "7.8.1", - "untruncate-json": "^0.0.1", - "uuid": "^11.1.0", - "zod": "^3.22.4" - } - }, - "apps/app/node_modules/@copilotkit/react-core/node_modules/lucide-react": { - "version": "0.525.0", - "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.525.0.tgz", - "integrity": "sha512-Tm1txJ2OkymCGkvwoHt33Y2JpN5xucVq1slHcgE6Lk0WjDfjgKWor5CdVER8U6DvcfMwh4M8XxmpTiyzfmfDYQ==", - "license": "ISC", - "peerDependencies": { - "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "apps/app/node_modules/@copilotkit/react-ui": { - "version": "1.57.0", - "resolved": "https://registry.npmjs.org/@copilotkit/react-ui/-/react-ui-1.57.0.tgz", - "integrity": "sha512-a7YvBi3d7LuHownG0xMRIgVfgGA62hqiQQ3pPvFwTEamCmmc9qDZIzo589KTRDBYLsWFnvb25v3jEQz1KWWUXQ==", - "license": "MIT", - "dependencies": { - "@copilotkit/react-core": "1.57.0", - "@copilotkit/runtime-client-gql": "1.57.0", - "@copilotkit/shared": "1.57.0", - "@headlessui/react": "^2.2.9", - "react-markdown": "^10.1.0", - "react-syntax-highlighter": "^15.6.1", - "rehype-raw": "^7.0.0", - "remark-gfm": "^4.0.1", - "remark-math": "^6.0.0" - }, - "peerDependencies": { - "react": "^18 || ^19 || ^19.0.0-rc" - } - }, - "apps/app/node_modules/@copilotkit/react-ui/node_modules/react-markdown": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz", - "integrity": "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "hast-util-to-jsx-runtime": "^2.0.0", - "html-url-attributes": "^3.0.0", - "mdast-util-to-hast": "^13.0.0", - "remark-parse": "^11.0.0", - "remark-rehype": "^11.0.0", - "unified": "^11.0.0", - "unist-util-visit": "^5.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - }, - "peerDependencies": { - "@types/react": ">=18", - "react": ">=18" - } - }, - "apps/app/node_modules/@copilotkit/runtime-client-gql": { - "version": "1.57.0", - "resolved": "https://registry.npmjs.org/@copilotkit/runtime-client-gql/-/runtime-client-gql-1.57.0.tgz", - "integrity": "sha512-gQebShiQ2ywjKstC+AwK8W91n49YtqxbhNIfpUFCg6uzlK++CQ2ma9u6tsvJphitsaLfBrhJDJto4BzXzg1wPQ==", - "license": "MIT", - "dependencies": { - "@copilotkit/shared": "1.57.0", - "@urql/core": "^5.0.3", - "untruncate-json": "^0.0.1", - "urql": "^4.1.0" - }, - "peerDependencies": { - "react": "^18 || ^19 || ^19.0.0-rc" - } - }, - "apps/app/node_modules/@copilotkit/shared": { - "version": "1.57.0", - "resolved": "https://registry.npmjs.org/@copilotkit/shared/-/shared-1.57.0.tgz", - "integrity": "sha512-X6uqeAWLDh08LUj4a0gpC9mrlmlyaviuybsnGJQPzAJonYIehYH63bEksj6xFgIA1FfkmEzK2XTxUnbrtpMd3g==", - "license": "MIT", - "dependencies": { - "@ag-ui/client": "0.0.53", - "@copilotkit/license-verifier": "0.2.0", - "@segment/analytics-node": "^2.1.2", - "@standard-schema/spec": "^1.0.0", - "chalk": "4.1.2", - "graphql": "^16.8.1", - "partial-json": "^0.1.7", - "uuid": "^11.1.0", - "zod": "^3.23.3", - "zod-to-json-schema": "^3.23.5" - }, - "peerDependencies": { - "@ag-ui/core": ">=0.0.48" - } - }, - "apps/app/node_modules/@copilotkit/shared/node_modules/@ag-ui/client": { - "version": "0.0.53", - "resolved": "https://registry.npmjs.org/@ag-ui/client/-/client-0.0.53.tgz", - "integrity": "sha512-Mkup36KUp0KXy9v89QtAOWDUoh8H1s1Vgl4zvQv9HqXuAK1TkbtpXJHpbgZJXIxTqd54KT6yCurmC2UkOP7FDQ==", - "dependencies": { - "@ag-ui/core": "0.0.53", - "@ag-ui/encoder": "0.0.53", - "@ag-ui/proto": "0.0.53", - "@types/uuid": "^10.0.0", - "compare-versions": "^6.1.1", - "fast-json-patch": "^3.1.1", - "rxjs": "7.8.1", - "untruncate-json": "^0.0.1", - "uuid": "^11.1.0", - "zod": "^3.22.4" - } - }, - "apps/app/node_modules/@copilotkit/web-inspector": { - "version": "1.57.0", - "resolved": "https://registry.npmjs.org/@copilotkit/web-inspector/-/web-inspector-1.57.0.tgz", - "integrity": "sha512-V05eKxqWNFnyhoNct7qc21AjFipxzK8RQNA72tRgnug38hodSNr6haqQGOcacjLD+tpe17KDEMO45QYm9YuExQ==", - "dependencies": { - "@ag-ui/client": "0.0.53", - "@copilotkit/core": "1.57.0", - "lit": "^3.2.0", - "lucide": "^0.525.0", - "marked": "^12.0.2" - }, - "engines": { - "node": ">=18" - } - }, - "apps/app/node_modules/@copilotkit/web-inspector/node_modules/@ag-ui/client": { - "version": "0.0.53", - "resolved": "https://registry.npmjs.org/@ag-ui/client/-/client-0.0.53.tgz", - "integrity": "sha512-Mkup36KUp0KXy9v89QtAOWDUoh8H1s1Vgl4zvQv9HqXuAK1TkbtpXJHpbgZJXIxTqd54KT6yCurmC2UkOP7FDQ==", - "dependencies": { - "@ag-ui/core": "0.0.53", - "@ag-ui/encoder": "0.0.53", - "@ag-ui/proto": "0.0.53", - "@types/uuid": "^10.0.0", - "compare-versions": "^6.1.1", - "fast-json-patch": "^3.1.1", - "rxjs": "7.8.1", - "untruncate-json": "^0.0.1", - "uuid": "^11.1.0", - "zod": "^3.22.4" - } - }, - "apps/app/node_modules/@tanstack/store": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/@tanstack/store/-/store-0.9.3.tgz", - "integrity": "sha512-8reSzl/qGWGGVKhBoxXPMWzATSbZLZFWhwBAFO9NAyp0TxzfBP0mIrGb8CP8KrQTmvzXlR/vFPPUrHTLBGyFyw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - } - }, - "apps/app/node_modules/@types/hast": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", - "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", - "license": "MIT", - "dependencies": { - "@types/unist": "*" - } - }, - "apps/app/node_modules/@types/unist": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", - "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", - "license": "MIT" - }, - "apps/app/node_modules/remark-parse": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", - "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-from-markdown": "^2.0.0", - "micromark-util-types": "^2.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "apps/app/node_modules/remark-rehype": { - "version": "11.1.2", - "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", - "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "mdast-util-to-hast": "^13.0.0", - "unified": "^11.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "apps/app/node_modules/rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.1.0" - } - }, - "apps/app/node_modules/unified": { - "version": "11.0.5", - "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", - "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "bail": "^2.0.0", - "devlop": "^1.0.0", - "extend": "^3.0.0", - "is-plain-obj": "^4.0.0", - "trough": "^2.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "apps/app/node_modules/unist-util-visit": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", - "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0", - "unist-util-visit-parents": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "apps/app/node_modules/vfile": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", - "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", - "license": "MIT", + "name": "@repo/app", + "version": "0.1.0", "dependencies": { - "@types/unist": "^3.0.0", - "vfile-message": "^4.0.0" + "@ag-ui/client": "0.0.53", + "@copilotkit/a2ui-renderer": "1.59.1", + "@copilotkit/react-core": "1.59.1", + "@hono/node-server": "^1.19.14", + "@radix-ui/react-checkbox": "^1.3.3", + "@radix-ui/react-label": "^2.1.8", + "@radix-ui/react-separator": "^1.1.8", + "@tanstack/pacer": "0.21.0", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "hono": "^4.9.8", + "lucide-react": "^0.577.0", + "react": "^19.2.4", + "react-dom": "^19.2.4", + "react-rnd": "^10.5.2", + "react18-json-view": "^0.2.9", + "recharts": "^3.7.0", + "tailwind-merge": "^3.5.0", + "zod": "^3.23.8" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "devDependencies": { + "@tailwindcss/postcss": "^4", + "@types/node": "^20", + "@types/react": "^19", + "@types/react-dom": "^19", + "@vitejs/plugin-react": "^5.1.0", + "tailwindcss": "^4", + "typescript": "^5", + "vite": "^7.1.12" } }, "apps/bff": { "name": "@repo/bff", "version": "0.1.0", "dependencies": { - "@copilotkit/runtime": "1.57.0", + "@copilotkit/runtime": "1.59.1", "@hono/node-server": "^1.13.6", "express": "5.2.1", "hono": "^4.7.11", @@ -488,61 +83,31 @@ "rxjs": "7.8.1" } }, - "apps/bff/node_modules/@ag-ui/client": { - "version": "0.0.53", - "dependencies": { - "@ag-ui/core": "0.0.53", - "@ag-ui/encoder": "0.0.53", - "@ag-ui/proto": "0.0.53", - "@types/uuid": "^10.0.0", - "compare-versions": "^6.1.1", - "fast-json-patch": "^3.1.1", - "rxjs": "7.8.1", - "untruncate-json": "^0.0.1", - "uuid": "^11.1.0", - "zod": "^3.22.4" - } - }, - "apps/bff/node_modules/@ag-ui/core": { - "version": "0.0.53", - "dependencies": { - "zod": "^3.22.4" - } - }, - "apps/bff/node_modules/@ag-ui/encoder": { - "version": "0.0.53", - "dependencies": { - "@ag-ui/core": "0.0.53", - "@ag-ui/proto": "0.0.53" - } - }, - "apps/bff/node_modules/@ag-ui/proto": { - "version": "0.0.53", - "dependencies": { - "@ag-ui/core": "0.0.53", - "@bufbuild/protobuf": "^2.2.5", - "@protobuf-ts/protoc": "^2.11.1" - } + "apps/bff/node_modules/@copilotkit/license-verifier": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@copilotkit/license-verifier/-/license-verifier-0.4.2.tgz", + "integrity": "sha512-0+Rdtg4gOwOBFBpZFxYsjgwBcCLja5z03YC6WA3KEntHYhsnoJ2aqNG6c0we8ZExCNYlEO4M7kHIfG5LXzqMYQ==", + "license": "MIT" }, "apps/bff/node_modules/@copilotkit/runtime": { - "version": "1.57.0", - "resolved": "https://registry.npmjs.org/@copilotkit/runtime/-/runtime-1.57.0.tgz", - "integrity": "sha512-rvFXgQZkFcojdGubmS51OUYnrXQ/MLFj8TrhEgEKRwHT2wqB7OuA1DAPn1d2bip6LqUOPYsFOFGt9Qhy19narw==", + "version": "1.59.1", + "resolved": "https://registry.npmjs.org/@copilotkit/runtime/-/runtime-1.59.1.tgz", + "integrity": "sha512-0Rl+8i2xj3dGiy+NG6tnItg0wGgXxOnB8BVkMsHC6PxRQp7am3PqUNrOQTFI67i7bvXfEPNdZINm97djzOvVSQ==", "license": "MIT", "dependencies": { "@ag-ui/a2ui-middleware": "0.0.5", "@ag-ui/client": "0.0.53", "@ag-ui/core": "0.0.53", "@ag-ui/encoder": "0.0.53", - "@ag-ui/langgraph": "0.0.31", + "@ag-ui/langgraph": "0.0.34", "@ag-ui/mcp-apps-middleware": "0.0.3", "@ai-sdk/anthropic": "^3.0.49", "@ai-sdk/google": "^3.0.33", "@ai-sdk/google-vertex": "^3.0.97", "@ai-sdk/mcp": "^1.0.21", "@ai-sdk/openai": "^3.0.36", - "@copilotkit/license-verifier": "0.2.0", - "@copilotkit/shared": "1.57.0", + "@copilotkit/license-verifier": "~0.4.2", + "@copilotkit/shared": "1.59.1", "@graphql-yoga/plugin-defer-stream": "^3.3.1", "@hono/node-server": "^1.13.5", "@modelcontextprotocol/sdk": "^1.18.2", @@ -681,27 +246,6 @@ "uuid": "dist/bin/uuid" } }, - "apps/bff/node_modules/@copilotkit/shared": { - "version": "1.57.0", - "resolved": "https://registry.npmjs.org/@copilotkit/shared/-/shared-1.57.0.tgz", - "integrity": "sha512-X6uqeAWLDh08LUj4a0gpC9mrlmlyaviuybsnGJQPzAJonYIehYH63bEksj6xFgIA1FfkmEzK2XTxUnbrtpMd3g==", - "license": "MIT", - "dependencies": { - "@ag-ui/client": "0.0.53", - "@copilotkit/license-verifier": "0.2.0", - "@segment/analytics-node": "^2.1.2", - "@standard-schema/spec": "^1.0.0", - "chalk": "4.1.2", - "graphql": "^16.8.1", - "partial-json": "^0.1.7", - "uuid": "^11.1.0", - "zod": "^3.23.3", - "zod-to-json-schema": "^3.23.5" - }, - "peerDependencies": { - "@ag-ui/core": ">=0.0.48" - } - }, "apps/bff/node_modules/@types/node": { "version": "22.19.17", "dev": true, @@ -911,6 +455,8 @@ }, "node_modules/@0no-co/graphql.web": { "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@0no-co/graphql.web/-/graphql.web-1.2.0.tgz", + "integrity": "sha512-/1iHy9TTr63gE1YcR5idjx8UREz1s0kFhydf3bBLCXyqjhkIc6igAzTOx3zPifCwFR87tsh/4Pa9cNts6d2otw==", "license": "MIT", "peerDependencies": { "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" @@ -932,11 +478,13 @@ } }, "node_modules/@ag-ui/client": { - "version": "0.0.52", + "version": "0.0.53", + "resolved": "https://registry.npmjs.org/@ag-ui/client/-/client-0.0.53.tgz", + "integrity": "sha512-Mkup36KUp0KXy9v89QtAOWDUoh8H1s1Vgl4zvQv9HqXuAK1TkbtpXJHpbgZJXIxTqd54KT6yCurmC2UkOP7FDQ==", "dependencies": { - "@ag-ui/core": "0.0.52", - "@ag-ui/encoder": "0.0.52", - "@ag-ui/proto": "0.0.52", + "@ag-ui/core": "0.0.53", + "@ag-ui/encoder": "0.0.53", + "@ag-ui/proto": "0.0.53", "@types/uuid": "^10.0.0", "compare-versions": "^6.1.1", "fast-json-patch": "^3.1.1", @@ -954,20 +502,26 @@ } }, "node_modules/@ag-ui/core": { - "version": "0.0.52", + "version": "0.0.53", + "resolved": "https://registry.npmjs.org/@ag-ui/core/-/core-0.0.53.tgz", + "integrity": "sha512-11UocR7fFdMWw503bWCX2IOK15vbWfxT11Mn9xOiPBVO/UVcn57ywGrlLL4UaBlPgmUTvuzr2yYR2ElSqiN2wQ==", "dependencies": { "zod": "^3.22.4" } }, "node_modules/@ag-ui/encoder": { - "version": "0.0.52", + "version": "0.0.53", + "resolved": "https://registry.npmjs.org/@ag-ui/encoder/-/encoder-0.0.53.tgz", + "integrity": "sha512-bAOcfVdm6U4H6G6tW+DZfwPEQm1w/snVBTwaFn9nJcEMW69M7/HZuwvEc/7Zo0rK1jRL32N/j60PwTAeky19fw==", "dependencies": { - "@ag-ui/core": "0.0.52", - "@ag-ui/proto": "0.0.52" + "@ag-ui/core": "0.0.53", + "@ag-ui/proto": "0.0.53" } }, "node_modules/@ag-ui/langgraph": { - "version": "0.0.31", + "version": "0.0.34", + "resolved": "https://registry.npmjs.org/@ag-ui/langgraph/-/langgraph-0.0.34.tgz", + "integrity": "sha512-3BV3/X9iRXL3EOFq1DxAZffbh2V6bJ2G8aoNJP6wHx40jbPdJT7aDvZb4DmzlFmXVWJ2kzfD4AnvxLAx9ooPLQ==", "dependencies": { "@langchain/core": "^1.1.40", "@langchain/langgraph-sdk": "^1.8.8", @@ -982,6 +536,8 @@ }, "node_modules/@ag-ui/langgraph/node_modules/rxjs": { "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.1.0" @@ -1005,9 +561,11 @@ } }, "node_modules/@ag-ui/proto": { - "version": "0.0.52", + "version": "0.0.53", + "resolved": "https://registry.npmjs.org/@ag-ui/proto/-/proto-0.0.53.tgz", + "integrity": "sha512-swjz22xWT8YUZt5OhmUwkARDQdwt8XM1hmGZbQrhRnNPXKwrKJX9ELlbnQ4iFUQIKkMWpphzE3vA3yNKs2bbKw==", "dependencies": { - "@ag-ui/core": "0.0.52", + "@ag-ui/core": "0.0.53", "@bufbuild/protobuf": "^2.2.5", "@protobuf-ts/protoc": "^2.11.1" } @@ -1459,13 +1017,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/runtime": { - "version": "7.29.2", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/template": { "version": "7.28.6", "dev": true, @@ -1514,6 +1065,8 @@ }, "node_modules/@bufbuild/protobuf": { "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.12.0.tgz", + "integrity": "sha512-B/XlCaFIP8LOwzo+bz5uFzATYokcwCKQcghqnlfwSmM5eX/qTkvDBnDPs+gXtX/RyjxJ4DRikECcPJbyALA8FA==", "license": "(Apache-2.0 AND BSD-3-Clause)" }, "node_modules/@cfworker/json-schema": { @@ -1582,8 +1135,187 @@ "commander": "~13.1.0" } }, - "node_modules/@copilotkit/license-verifier": { - "version": "0.2.0" + "node_modules/@copilotkit/a2ui-renderer": { + "version": "1.59.1", + "resolved": "https://registry.npmjs.org/@copilotkit/a2ui-renderer/-/a2ui-renderer-1.59.1.tgz", + "integrity": "sha512-jRltYlLKqwWREiS430pJ0ukpA1L7hralerVFYlsgtkMhRMf9DzSMl+Gbt48FDGt/QteS7YwsT+nZrUWanOXJXg==", + "license": "MIT", + "dependencies": { + "@a2ui/web_core": "0.9.0", + "clsx": "^2.1.1", + "zod": "^3.25.75", + "zod-to-json-schema": "^3.24.1" + }, + "peerDependencies": { + "react": "^18 || ^19 || ^19.0.0-rc", + "react-dom": "^18 || ^19 || ^19.0.0-rc" + } + }, + "node_modules/@copilotkit/core": { + "version": "1.59.1", + "resolved": "https://registry.npmjs.org/@copilotkit/core/-/core-1.59.1.tgz", + "integrity": "sha512-i1cdGADVNR/TkkZcMWGyrInBMutnHlCVxvLoujet9x8OkgMVOF/T6w9Ep7PNNY5Wwlu+i2XwGQPz5bb1/NyUdw==", + "dependencies": { + "@ag-ui/client": "0.0.53", + "@copilotkit/shared": "1.59.1", + "@tanstack/pacer": "^0.20.1", + "phoenix": "^1.8.4", + "rxjs": "7.8.1", + "zod-to-json-schema": "^3.24.6" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@copilotkit/core/node_modules/@tanstack/pacer": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@tanstack/pacer/-/pacer-0.20.1.tgz", + "integrity": "sha512-ZNQ1bIL6eUXVKdic0tiImvBVkWrg/IoSK6VIacTrO3d3HAGnd70qFJNJagR/YOJIOw4EKGWnodwpYZkN1pWuVQ==", + "license": "MIT", + "dependencies": { + "@tanstack/devtools-event-client": "^0.4.3", + "@tanstack/store": "^0.9.3" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@copilotkit/core/node_modules/@tanstack/store": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/@tanstack/store/-/store-0.9.3.tgz", + "integrity": "sha512-8reSzl/qGWGGVKhBoxXPMWzATSbZLZFWhwBAFO9NAyp0TxzfBP0mIrGb8CP8KrQTmvzXlR/vFPPUrHTLBGyFyw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@copilotkit/core/node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@copilotkit/react-core": { + "version": "1.59.1", + "resolved": "https://registry.npmjs.org/@copilotkit/react-core/-/react-core-1.59.1.tgz", + "integrity": "sha512-vteM29uKKhN1IfS9iKIMDw28IUGMSuUJd3eB6gAJhE2lI4GUr0K02vytUclqHKxR1/vUZpsvqvPJuDLCFO4ujw==", + "license": "MIT", + "dependencies": { + "@ag-ui/client": "0.0.53", + "@ag-ui/core": "0.0.53", + "@copilotkit/a2ui-renderer": "1.59.1", + "@copilotkit/core": "1.59.1", + "@copilotkit/runtime-client-gql": "1.59.1", + "@copilotkit/shared": "1.59.1", + "@copilotkit/web-inspector": "1.59.1", + "@jetbrains/websandbox": "^1.1.3", + "@lit-labs/react": "^2.0.2", + "@radix-ui/react-dropdown-menu": "^2.1.15", + "@radix-ui/react-slot": "^1.2.3", + "@radix-ui/react-tooltip": "^1.2.7", + "@scarf/scarf": "^1.3.0", + "@tanstack/react-virtual": "^3.13.0", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "katex": "^0.16.22", + "lucide-react": "^0.525.0", + "react-markdown": "^8.0.7", + "rxjs": "7.8.1", + "streamdown": "^1.3.0", + "tailwind-merge": "^3.3.1", + "tw-animate-css": "^1.3.5", + "untruncate-json": "^0.0.1", + "use-stick-to-bottom": "^1.1.1", + "zod-to-json-schema": "^3.24.5" + }, + "peerDependencies": { + "react": "^18 || ^19 || ^19.0.0-rc", + "react-dom": "^18 || ^19 || ^19.0.0-rc", + "zod": ">=3.0.0" + } + }, + "node_modules/@copilotkit/react-core/node_modules/lucide-react": { + "version": "0.525.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.525.0.tgz", + "integrity": "sha512-Tm1txJ2OkymCGkvwoHt33Y2JpN5xucVq1slHcgE6Lk0WjDfjgKWor5CdVER8U6DvcfMwh4M8XxmpTiyzfmfDYQ==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@copilotkit/react-core/node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@copilotkit/runtime-client-gql": { + "version": "1.59.1", + "resolved": "https://registry.npmjs.org/@copilotkit/runtime-client-gql/-/runtime-client-gql-1.59.1.tgz", + "integrity": "sha512-JLBDy5Y37LiZhKWIwSm4ETQBOzy3vHDUvbvjtxDzF6kxsddswyXEMtjQUEc93RDwDCh1XuH2FLnd1ha48kmOLQ==", + "license": "MIT", + "dependencies": { + "@copilotkit/shared": "1.59.1", + "@urql/core": "^5.0.3", + "untruncate-json": "^0.0.1", + "urql": "^4.1.0" + }, + "peerDependencies": { + "react": "^18 || ^19 || ^19.0.0-rc" + } + }, + "node_modules/@copilotkit/shared": { + "version": "1.59.1", + "resolved": "https://registry.npmjs.org/@copilotkit/shared/-/shared-1.59.1.tgz", + "integrity": "sha512-vCJB4bao5Tu54mc7D/h/vLfUZsDECl/kgW/GVli2soMzIJW6UAHFhtt2D9MN206xDtE6gXqBnBIMKNG+JO61SQ==", + "license": "MIT", + "dependencies": { + "@ag-ui/client": "0.0.53", + "@copilotkit/license-verifier": "~0.4.2", + "@segment/analytics-node": "^2.1.2", + "@standard-schema/spec": "^1.0.0", + "chalk": "4.1.2", + "graphql": "^16.8.1", + "partial-json": "^0.1.7", + "uuid": "^11.1.0", + "zod": "^3.23.3", + "zod-to-json-schema": "^3.23.5" + }, + "peerDependencies": { + "@ag-ui/core": ">=0.0.48" + } + }, + "node_modules/@copilotkit/shared/node_modules/@copilotkit/license-verifier": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@copilotkit/license-verifier/-/license-verifier-0.4.2.tgz", + "integrity": "sha512-0+Rdtg4gOwOBFBpZFxYsjgwBcCLja5z03YC6WA3KEntHYhsnoJ2aqNG6c0we8ZExCNYlEO4M7kHIfG5LXzqMYQ==", + "license": "MIT" + }, + "node_modules/@copilotkit/web-inspector": { + "version": "1.59.1", + "resolved": "https://registry.npmjs.org/@copilotkit/web-inspector/-/web-inspector-1.59.1.tgz", + "integrity": "sha512-vA+xhTiXLh7x0IKP84O7neAAZfx6FYzzwGAr1lVPBLDABGb8Ax2ZuSGywMEEtAiu9r5ravPTN+oT71yRNjD4yA==", + "dependencies": { + "@ag-ui/client": "0.0.53", + "@copilotkit/core": "1.59.1", + "lit": "^3.2.0", + "lucide": "^0.525.0", + "marked": "^12.0.2" + }, + "engines": { + "node": ">=18" + } }, "node_modules/@dabh/diagnostics": { "version": "2.0.8", @@ -1660,21 +1392,8 @@ "version": "1.7.6", "license": "MIT", "dependencies": { - "@floating-ui/core": "^1.7.5", - "@floating-ui/utils": "^0.2.11" - } - }, - "node_modules/@floating-ui/react": { - "version": "0.26.28", - "license": "MIT", - "dependencies": { - "@floating-ui/react-dom": "^2.1.2", - "@floating-ui/utils": "^0.2.8", - "tabbable": "^6.0.0" - }, - "peerDependencies": { - "react": ">=16.8.0", - "react-dom": ">=16.8.0" + "@floating-ui/core": "^1.7.5", + "@floating-ui/utils": "^0.2.11" } }, "node_modules/@floating-ui/react-dom": { @@ -1858,24 +1577,6 @@ "node": ">=18.0.0" } }, - "node_modules/@headlessui/react": { - "version": "2.2.10", - "license": "MIT", - "dependencies": { - "@floating-ui/react": "^0.26.16", - "@react-aria/focus": "^3.20.2", - "@react-aria/interactions": "^3.25.0", - "@tanstack/react-virtual": "^3.13.9", - "use-sync-external-store": "^1.5.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "react": "^18 || ^19 || ^19.0.0-rc", - "react-dom": "^18 || ^19 || ^19.0.0-rc" - } - }, "node_modules/@hono/node-server": { "version": "1.19.14", "license": "MIT", @@ -1908,27 +1609,6 @@ "mlly": "^1.8.2" } }, - "node_modules/@internationalized/date": { - "version": "3.12.1", - "license": "Apache-2.0", - "dependencies": { - "@swc/helpers": "^0.5.0" - } - }, - "node_modules/@internationalized/number": { - "version": "3.6.6", - "license": "Apache-2.0", - "dependencies": { - "@swc/helpers": "^0.5.0" - } - }, - "node_modules/@internationalized/string": { - "version": "3.2.8", - "license": "Apache-2.0", - "dependencies": { - "@swc/helpers": "^0.5.0" - } - }, "node_modules/@isaacs/fs-minipass": { "version": "4.0.1", "dev": true, @@ -1985,14 +1665,13 @@ } }, "node_modules/@langchain/core": { - "version": "1.1.42", + "version": "1.1.48", + "resolved": "https://registry.npmjs.org/@langchain/core/-/core-1.1.48.tgz", + "integrity": "sha512-fQU6Guyb1pwc2fEplmA8FPbKfOMAofjnyJzExevro0FxEiuGHE18Ov/ZHmT9trWCDTZRI9eW1VIc6aChxV8pAQ==", "license": "MIT", "dependencies": { "@cfworker/json-schema": "^4.0.2", "@standard-schema/spec": "^1.1.0", - "ansi-styles": "^5.0.0", - "camelcase": "6", - "decamelize": "1.2.0", "js-tiktoken": "^1.0.12", "langsmith": ">=0.5.0 <1.0.0", "mustache": "^4.2.0", @@ -2003,16 +1682,6 @@ "node": ">=20" } }, - "node_modules/@langchain/core/node_modules/ansi-styles": { - "version": "5.2.0", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/@langchain/core/node_modules/langsmith": { "version": "0.5.26", "license": "MIT", @@ -2045,11 +1714,14 @@ } }, "node_modules/@langchain/langgraph": { - "version": "1.2.9", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@langchain/langgraph/-/langgraph-1.3.2.tgz", + "integrity": "sha512-SL7Ktsr681R7da+1b2MVOWEbaCoFJOXEJPTGOjg4JIG4C7quWbTYC8DzxhcCxte6D/8cGp0rYDBnbKLXEpNqlA==", "license": "MIT", "dependencies": { - "@langchain/langgraph-checkpoint": "^1.0.1", - "@langchain/langgraph-sdk": "~1.8.9", + "@langchain/langgraph-checkpoint": "^1.0.2", + "@langchain/langgraph-sdk": "~1.9.4", + "@langchain/protocol": "^0.0.15", "@standard-schema/spec": "1.1.0", "uuid": "^10.0.0" }, @@ -2057,7 +1729,7 @@ "node": ">=18" }, "peerDependencies": { - "@langchain/core": "^1.1.40", + "@langchain/core": "^1.1.44", "zod": "^3.25.32 || ^4.2.0", "zod-to-json-schema": "^3.x" }, @@ -2122,7 +1794,9 @@ } }, "node_modules/@langchain/langgraph-checkpoint": { - "version": "1.0.1", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@langchain/langgraph-checkpoint/-/langgraph-checkpoint-1.0.3.tgz", + "integrity": "sha512-lzamDsWxD/25zIVeEO7Xv38Rq1BCZUQYWx0V6zewHf4LpjzzyxB5N9aZcwdsl+SvHRJASGCnvLleqD9dp3afxg==", "license": "MIT", "dependencies": { "uuid": "^10.0.0" @@ -2131,7 +1805,7 @@ "node": ">=18" }, "peerDependencies": { - "@langchain/core": "^1.0.1" + "@langchain/core": "^1.1.44" } }, "node_modules/@langchain/langgraph-checkpoint/node_modules/uuid": { @@ -2179,25 +1853,25 @@ } }, "node_modules/@langchain/langgraph-sdk": { - "version": "1.8.10", + "version": "1.9.10", + "resolved": "https://registry.npmjs.org/@langchain/langgraph-sdk/-/langgraph-sdk-1.9.10.tgz", + "integrity": "sha512-hsvRVAUR2miafkLpl6HjwYkciKxMk4IZdCJz7fO7wzL344RvIBfbNLeXfcPCKF4VP/pR90723M8Am1olW5xvHQ==", "license": "MIT", "dependencies": { + "@langchain/protocol": "^0.0.16", "@types/json-schema": "^7.0.15", "p-queue": "^9.0.1", "p-retry": "^7.1.1", "uuid": "^13.0.0" }, "peerDependencies": { - "@langchain/core": "^1.1.16", + "@langchain/core": "^1.1.44", "react": "^18 || ^19", "react-dom": "^18 || ^19", "svelte": "^4.0.0 || ^5.0.0", "vue": "^3.0.0" }, "peerDependenciesMeta": { - "@langchain/core": { - "optional": true - }, "react": { "optional": true }, @@ -2212,6 +1886,12 @@ } } }, + "node_modules/@langchain/langgraph-sdk/node_modules/@langchain/protocol": { + "version": "0.0.16", + "resolved": "https://registry.npmjs.org/@langchain/protocol/-/protocol-0.0.16.tgz", + "integrity": "sha512-ws+J7MaHyhO5dG7f0vdyHQiUn9hoCnki0f3crJPa4MCTGzcRC39jYSCghyrGtBPYQnZbUQiGyRVpW3z3M8IpJg==", + "license": "MIT" + }, "node_modules/@langchain/langgraph-sdk/node_modules/eventemitter3": { "version": "5.0.4", "license": "MIT" @@ -2280,6 +1960,12 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/@langchain/protocol": { + "version": "0.0.15", + "resolved": "https://registry.npmjs.org/@langchain/protocol/-/protocol-0.0.15.tgz", + "integrity": "sha512-MllvbpMjqHevUm+v94M422mH7XKN+wGCvJRBVROTWBotEDOATYB4Ktk2UheYP859y9o2LlhtPek5t1T9eyfAbQ==", + "license": "MIT" + }, "node_modules/@lit-labs/react": { "version": "2.1.3", "license": "BSD-3-Clause", @@ -2288,7 +1974,9 @@ } }, "node_modules/@lit-labs/ssr-dom-shim": { - "version": "1.5.1", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.6.0.tgz", + "integrity": "sha512-VHb0ALPMTlgKjM6yIxxoQNnpKyUKLD04VzeQdsiXkMqkvYlAHxq9glGLmgbb889/1GsohSOAjvQYoiBppXFqrQ==", "license": "BSD-3-Clause" }, "node_modules/@lit/react": { @@ -2300,6 +1988,8 @@ }, "node_modules/@lit/reactive-element": { "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.1.2.tgz", + "integrity": "sha512-pbCDiVMnne1lYUIaYNN5wrwQXDtHaYtg7YEFPeW+hws6U47WeFvISGUWekPGKWOP1ygrs0ef0o1VJMk1exos5A==", "license": "BSD-3-Clause", "dependencies": { "@lit-labs/ssr-dom-shim": "^1.5.0" @@ -2395,6 +2085,8 @@ }, "node_modules/@protobuf-ts/protoc": { "version": "2.11.1", + "resolved": "https://registry.npmjs.org/@protobuf-ts/protoc/-/protoc-2.11.1.tgz", + "integrity": "sha512-mUZJaV0daGO6HUX90o/atzQ6A7bbN2RSuHtdwo8SSF2Qoe3zHwa4IHyCN1evftTeHfLmdz+45qo47sL+5P8nyg==", "license": "Apache-2.0", "bin": { "protoc": "protoc.js" @@ -3123,38 +2815,6 @@ "version": "1.1.1", "license": "MIT" }, - "node_modules/@react-aria/focus": { - "version": "3.22.0", - "license": "Apache-2.0", - "dependencies": { - "@swc/helpers": "^0.5.0", - "react-aria": "3.48.0" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", - "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" - } - }, - "node_modules/@react-aria/interactions": { - "version": "3.28.0", - "license": "Apache-2.0", - "dependencies": { - "@react-types/shared": "^3.34.0", - "@swc/helpers": "^0.5.0", - "react-aria": "3.48.0" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", - "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" - } - }, - "node_modules/@react-types/shared": { - "version": "3.34.0", - "license": "Apache-2.0", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" - } - }, "node_modules/@reduxjs/toolkit": { "version": "2.11.2", "license": "MIT", @@ -3362,13 +3022,6 @@ "version": "0.3.0", "license": "MIT" }, - "node_modules/@swc/helpers": { - "version": "0.5.21", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.8.0" - } - }, "node_modules/@tailwindcss/node": { "version": "4.2.4", "dev": true, @@ -3857,6 +3510,8 @@ }, "node_modules/@urql/core": { "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@urql/core/-/core-5.2.0.tgz", + "integrity": "sha512-/n0ieD0mvvDnVAXEQgX/7qJiVcvYvNkOHeBvkwtylfjydar123caCXcl58PXFY11oU1oquJocVXHxLAbtv4x1A==", "license": "MIT", "dependencies": { "@0no-co/graphql.web": "^1.0.13", @@ -4315,16 +3970,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/camelcase": { - "version": "6.3.0", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/caniuse-lite": { "version": "1.0.30001791", "dev": true, @@ -4376,14 +4021,6 @@ "node": ">=8" } }, - "node_modules/character-entities": { - "version": "1.2.4", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/character-entities-html4": { "version": "2.1.0", "license": "MIT", @@ -4392,22 +4029,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/character-entities-legacy": { - "version": "1.1.4", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-reference-invalid": { - "version": "1.1.4", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/chevrotain": { "version": "12.0.0", "license": "Apache-2.0", @@ -5233,13 +4854,6 @@ } } }, - "node_modules/decamelize": { - "version": "1.2.0", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/decimal.js-light": { "version": "2.5.1", "license": "MIT" @@ -5786,17 +5400,6 @@ ], "license": "BSD-3-Clause" }, - "node_modules/fault": { - "version": "1.0.4", - "license": "MIT", - "dependencies": { - "format": "^0.2.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/fd-slicer": { "version": "1.1.0", "dev": true, @@ -5885,12 +5488,6 @@ "dev": true, "license": "MIT" }, - "node_modules/format": { - "version": "0.2.2", - "engines": { - "node": ">=0.4.x" - } - }, "node_modules/formdata-polyfill": { "version": "4.0.10", "license": "MIT", @@ -6423,14 +6020,6 @@ "@types/unist": "*" } }, - "node_modules/hast-util-parse-selector": { - "version": "2.2.5", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/hast-util-raw": { "version": "9.1.0", "license": "MIT", @@ -6681,63 +6270,10 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/hastscript": { - "version": "6.0.0", - "license": "MIT", - "dependencies": { - "@types/hast": "^2.0.0", - "comma-separated-tokens": "^1.0.0", - "hast-util-parse-selector": "^2.0.0", - "property-information": "^5.0.0", - "space-separated-tokens": "^1.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hastscript/node_modules/comma-separated-tokens": { - "version": "1.0.8", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/hastscript/node_modules/property-information": { - "version": "5.6.0", - "license": "MIT", - "dependencies": { - "xtend": "^4.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/hastscript/node_modules/space-separated-tokens": { - "version": "1.1.5", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/help-me": { "version": "5.0.0", "license": "MIT" }, - "node_modules/highlight.js": { - "version": "10.7.3", - "license": "BSD-3-Clause", - "engines": { - "node": "*" - } - }, - "node_modules/highlightjs-vue": { - "version": "1.0.0", - "license": "CC0-1.0" - }, "node_modules/hono": { "version": "4.12.15", "license": "MIT", @@ -6874,26 +6410,6 @@ "node": ">= 0.10" } }, - "node_modules/is-alphabetical": { - "version": "1.0.4", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-alphanumerical": { - "version": "1.0.4", - "license": "MIT", - "dependencies": { - "is-alphabetical": "^1.0.0", - "is-decimal": "^1.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/is-buffer": { "version": "2.0.5", "funding": [ @@ -6915,14 +6431,6 @@ "node": ">=4" } }, - "node_modules/is-decimal": { - "version": "1.0.4", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/is-docker": { "version": "3.0.0", "dev": true, @@ -6945,14 +6453,6 @@ "node": ">=8" } }, - "node_modules/is-hexadecimal": { - "version": "1.0.4", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/is-inside-container": { "version": "1.0.0", "dev": true, @@ -7173,10 +6673,12 @@ "license": "MIT" }, "node_modules/langchain": { - "version": "1.3.5", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/langchain/-/langchain-1.4.2.tgz", + "integrity": "sha512-SLGipy0r4nqQD0aiUOBYLMeGFfB/QiYnMndfZ8sGN89vXDCIXbYqcE7G/4QDDX3nZsM7/emQpoScmlxEX6sDnQ==", "license": "MIT", "dependencies": { - "@langchain/langgraph": "^1.2.9", + "@langchain/langgraph": "^1.3.2", "@langchain/langgraph-checkpoint": "^1.0.1", "langsmith": ">=0.5.0 <1.0.0", "zod": "^3.25.76 || ^4" @@ -7185,11 +6687,13 @@ "node": ">=20" }, "peerDependencies": { - "@langchain/core": "^1.1.42" + "@langchain/core": "^1.1.48" } }, "node_modules/langchain/node_modules/langsmith": { - "version": "0.6.0", + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/langsmith/-/langsmith-0.7.3.tgz", + "integrity": "sha512-Gg+IeRGoF1/aStu80aEnIXJCu+N6+4NoV4tAVFS51ZPRBsRa2KG0LkS7K7/ryZ/yES7O9xdqah5QuuWMIeMjQw==", "license": "MIT", "dependencies": { "p-queue": "6.6.2" @@ -7336,7 +6840,9 @@ } }, "node_modules/lit": { - "version": "3.3.2", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/lit/-/lit-3.3.3.tgz", + "integrity": "sha512-fycuvZg/hkpozL00lm1pEJH5nN/lr9ZXd6mJI2HSN4+Bzc+LDNdEApJ6HFbPkdFNHLvOplIIuJvxkS4XUxqirw==", "license": "BSD-3-Clause", "dependencies": { "@lit/reactive-element": "^2.1.0", @@ -7346,6 +6852,8 @@ }, "node_modules/lit-element": { "version": "4.2.2", + "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.2.2.tgz", + "integrity": "sha512-aFKhNToWxoyhkNDmWZwEva2SlQia+jfG0fjIWV//YeTaWrVnOxD89dPKfigCUspXFmjzOEUQpOkejH5Ly6sG0w==", "license": "BSD-3-Clause", "dependencies": { "@lit-labs/ssr-dom-shim": "^1.5.0", @@ -7354,7 +6862,9 @@ } }, "node_modules/lit-html": { - "version": "3.3.2", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.3.3.tgz", + "integrity": "sha512-el8M6jK2o3RXBnrSHX3ZKrsN8zEV63pSExTO1wYJz7QndGYZ8353e2a5PPX+qHe2aGayfnchQmkAojaWAREOIA==", "license": "BSD-3-Clause", "dependencies": { "@types/trusted-types": "^2.0.2" @@ -7415,18 +6925,6 @@ "loose-envify": "cli.js" } }, - "node_modules/lowlight": { - "version": "1.20.0", - "license": "MIT", - "dependencies": { - "fault": "^1.0.0", - "highlight.js": "~10.7.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/lru-cache": { "version": "5.1.1", "dev": true, @@ -7437,6 +6935,8 @@ }, "node_modules/lucide": { "version": "0.525.0", + "resolved": "https://registry.npmjs.org/lucide/-/lucide-0.525.0.tgz", + "integrity": "sha512-sfehWlaE/7NVkcEQ4T9JD3eID8RNMIGJBBUq9wF3UFiJIrcMKRbU3g1KGfDk4svcW7yw8BtDLXaXo02scDtUYQ==", "license": "ISC" }, "node_modules/lucide-react": { @@ -7464,6 +6964,8 @@ }, "node_modules/marked": { "version": "12.0.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-12.0.2.tgz", + "integrity": "sha512-qXUm7e/YKFoqFPYPa3Ukg9xlI5cyAtGmyEIzMfW//m6kXwCy2Ps9DYf5ioijFKQ8qyuscrHoY04iJGctu2Kg0Q==", "license": "MIT", "bin": { "marked": "bin/marked.js" @@ -8889,22 +8391,6 @@ "version": "1.6.0", "license": "MIT" }, - "node_modules/parse-entities": { - "version": "2.0.0", - "license": "MIT", - "dependencies": { - "character-entities": "^1.0.0", - "character-entities-legacy": "^1.0.0", - "character-reference-invalid": "^1.0.0", - "is-alphanumerical": "^1.0.0", - "is-decimal": "^1.0.0", - "is-hexadecimal": "^1.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/parse-ms": { "version": "4.0.0", "dev": true, @@ -9212,13 +8698,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/prismjs": { - "version": "1.30.0", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/process": { "version": "0.11.10", "license": "MIT", @@ -9346,25 +8825,6 @@ "node": ">=0.10.0" } }, - "node_modules/react-aria": { - "version": "3.48.0", - "license": "Apache-2.0", - "dependencies": { - "@internationalized/date": "^3.12.1", - "@internationalized/number": "^3.6.6", - "@internationalized/string": "^3.2.8", - "@react-types/shared": "^3.34.0", - "@swc/helpers": "^0.5.0", - "aria-hidden": "^1.2.3", - "clsx": "^2.0.0", - "react-stately": "3.46.0", - "use-sync-external-store": "^1.6.0" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", - "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" - } - }, "node_modules/react-dom": { "version": "19.2.5", "license": "MIT", @@ -9514,21 +8974,6 @@ "version": "2.6.2", "license": "0BSD" }, - "node_modules/react-stately": { - "version": "3.46.0", - "license": "Apache-2.0", - "dependencies": { - "@internationalized/date": "^3.12.1", - "@internationalized/number": "^3.6.6", - "@internationalized/string": "^3.2.8", - "@react-types/shared": "^3.34.0", - "@swc/helpers": "^0.5.0", - "use-sync-external-store": "^1.6.0" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" - } - }, "node_modules/react-style-singleton": { "version": "2.2.3", "license": "MIT", @@ -9549,21 +8994,6 @@ } } }, - "node_modules/react-syntax-highlighter": { - "version": "15.6.6", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.3.1", - "highlight.js": "^10.4.1", - "highlightjs-vue": "^1.0.0", - "lowlight": "^1.17.0", - "prismjs": "^1.30.0", - "refractor": "^3.6.0" - }, - "peerDependencies": { - "react": ">= 0.14.0" - } - }, "node_modules/react18-json-view": { "version": "0.2.10", "license": "MIT", @@ -9653,26 +9083,6 @@ "version": "0.2.2", "license": "Apache-2.0" }, - "node_modules/refractor": { - "version": "3.6.0", - "license": "MIT", - "dependencies": { - "hastscript": "^6.0.0", - "parse-entities": "^2.0.0", - "prismjs": "~1.27.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/refractor/node_modules/prismjs": { - "version": "1.27.0", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/regex": { "version": "6.1.0", "license": "MIT", @@ -11276,10 +10686,6 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/tabbable": { - "version": "6.4.0", - "license": "MIT" - }, "node_modules/tailwind-merge": { "version": "3.5.0", "license": "MIT", @@ -11748,6 +11154,8 @@ }, "node_modules/urql": { "version": "4.2.2", + "resolved": "https://registry.npmjs.org/urql/-/urql-4.2.2.tgz", + "integrity": "sha512-3GgqNa6iF7bC4hY/ImJKN4REQILcSU9VKcKL8gfELZM8mM5BnLH1BsCc8kBdnVGD1LIFOs4W3O2idNHhON1r0w==", "license": "MIT", "dependencies": { "@urql/core": "^5.1.1", @@ -12172,6 +11580,8 @@ }, "node_modules/wonka": { "version": "6.3.6", + "resolved": "https://registry.npmjs.org/wonka/-/wonka-6.3.6.tgz", + "integrity": "sha512-MXH+6mDHAZ2GuMpgKS055FR6v0xVP3XwquxIMYXgiW+FejHQlMGlvVRZT4qMCxR+bEo/FCtIdKxwej9WV3YQag==", "license": "MIT" }, "node_modules/wrap-ansi": { @@ -12227,13 +11637,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/xtend": { - "version": "4.0.2", - "license": "MIT", - "engines": { - "node": ">=0.4" - } - }, "node_modules/y18n": { "version": "5.0.8", "dev": true, diff --git a/examples/showcases/daytona-runcode/.gitignore b/examples/showcases/daytona-runcode/.gitignore new file mode 100644 index 00000000000..2ceb2187f04 --- /dev/null +++ b/examples/showcases/daytona-runcode/.gitignore @@ -0,0 +1,4 @@ +node_modules +.next +.env +.env.local diff --git a/examples/showcases/daytona-runcode/README.md b/examples/showcases/daytona-runcode/README.md new file mode 100644 index 00000000000..50da1288c87 --- /dev/null +++ b/examples/showcases/daytona-runcode/README.md @@ -0,0 +1,51 @@ +# CopilotKit × Daytona — `runCode` showcase + +The deployable demo behind the [Daytona cookbook recipe](../../../showcase/shell-docs/src/content/docs/cookbook/daytona.mdx). +A minimal CopilotKit **Built-in Agent** app whose only added capability is a `runCode` server tool that executes +Python / TypeScript / JavaScript inside an isolated [Daytona](https://www.daytona.io) sandbox and streams the +result back to the chat — with a custom `useRenderTool` card (fixed-height streaming code pane with +syntax highlighting and a fixed-height result pane). + +## What's inside + +- `app/api/copilotkit-single/route.ts` — the recipe's `runCode` tool wired into a `BuiltInAgent` on + `createCopilotEndpointSingleRoute`. Pure recipe code (Python/TS/JS via `daytona.create({ language })`). +- `app/page.tsx` — `CopilotKitProvider` + `CopilotSidebar` + a `useRenderTool({ name: "runCode" })` + renderer using `react-syntax-highlighter` (Prism + `vscDarkPlus`, mirroring + `@copilotkit/react-ui`'s own `CodeBlock`). +- A system prompt that tells the agent the tool result is rendered directly to the user, so it + doesn't restate stdout in chat text. + +## Prerequisites + +- Node 18+ +- An OpenAI API key (`OPENAI_API_KEY`) — the recipe defaults to `openai:gpt-5.4-mini`, overridable via + the `MODEL` env var. +- A Daytona API key (`DAYTONA_API_KEY`) — create one in the + [Daytona dashboard](https://app.daytona.io/dashboard/keys). + +## Run locally + +```bash +npm install +echo "OPENAI_API_KEY=sk-…" > .env.local +echo "DAYTONA_API_KEY=…" >> .env.local +npm run dev # http://localhost:3000 +``` + +Open the page, click into the sidebar chat, and ask something like: + +> Run a Python snippet that prints the first 10 Fibonacci numbers. + +> Run JavaScript that logs Date.now(). + +## Notes + +- This is a **standalone npm project** — intentionally not in the monorepo's `pnpm-workspace.yaml`. + `npm install` here installs against published `@copilotkit/*@1.58.0`, which is the same surface the + recipe targets. +- `new Daytona()` throws at module load if `DAYTONA_API_KEY` is missing, so the app won't boot without + it. That's by design — the key is required, not optional. +- For arbitrary languages beyond Python/TS/JS, swap `sandbox.process.codeRun` for + `sandbox.process.executeCommand(...)` and optionally use a custom Daytona `Image` with the toolchain + preinstalled. See the recipe's _Going further_ section. diff --git a/examples/showcases/daytona-runcode/app/api/copilotkit-single/route.ts b/examples/showcases/daytona-runcode/app/api/copilotkit-single/route.ts new file mode 100644 index 00000000000..1c9cd251772 --- /dev/null +++ b/examples/showcases/daytona-runcode/app/api/copilotkit-single/route.ts @@ -0,0 +1,62 @@ +import { + CopilotRuntime, + InMemoryAgentRunner, + createCopilotEndpointSingleRoute, + BuiltInAgent, + defineTool, +} from "@copilotkit/runtime/v2"; +import { Daytona } from "@daytonaio/sdk"; +import { handle } from "hono/vercel"; +import { z } from "zod"; + +// --- The cookbook recipe: a server tool that runs code in a Daytona sandbox --- +const daytona = new Daytona(); // reads DAYTONA_API_KEY + +const runCode = defineTool({ + name: "runCode", + description: + "Execute code in an isolated Daytona sandbox and return its output.", + parameters: z.object({ + code: z.string().describe("The code to run in the sandbox"), + language: z + .enum(["python", "typescript", "javascript"]) + .default("python") + .describe("Language runtime for the code"), + }), + execute: async ({ code, language }) => { + const sandbox = await daytona.create({ language }); + try { + const res = await sandbox.process.codeRun(code); + return { stdout: res.result, exitCode: res.exitCode }; + } finally { + await sandbox.delete(); + } + }, +}); + +const agent = new BuiltInAgent({ + model: process.env.MODEL ?? "openai:gpt-5.4-mini", + prompt: + "You are a coding assistant. When the user asks you to run, execute, or compute " + + "something with code, use the runCode tool to run it in a Daytona sandbox. " + + "Default to Python unless told otherwise. " + + "IMPORTANT: the tool's output (stdout + exit code) is rendered directly to the user " + + "in a separate UI panel; the user can already see it. Do NOT repeat, restate, or " + + "include the stdout or the generated code in your reply. Acknowledge briefly (a single " + + "short sentence) or stay silent. Never reproduce content that already appears in the " + + "tool card.", + tools: [runCode], + maxSteps: 2, +}); + +const runtime = new CopilotRuntime({ + agents: { default: agent }, + runner: new InMemoryAgentRunner(), +}); + +const app = createCopilotEndpointSingleRoute({ + runtime, + basePath: "/api/copilotkit-single", +}); + +export const POST = handle(app); diff --git a/examples/showcases/daytona-runcode/app/layout.tsx b/examples/showcases/daytona-runcode/app/layout.tsx new file mode 100644 index 00000000000..4ff3d1b7a8c --- /dev/null +++ b/examples/showcases/daytona-runcode/app/layout.tsx @@ -0,0 +1,15 @@ +import type { ReactNode } from "react"; +import "@copilotkit/react-core/v2/styles.css"; + +export const metadata = { + title: "CopilotKit × Daytona", + description: "A built-in agent that runs code in Daytona sandboxes.", +}; + +export default function RootLayout({ children }: { children: ReactNode }) { + return ( + + {children} + + ); +} diff --git a/examples/showcases/daytona-runcode/app/page.tsx b/examples/showcases/daytona-runcode/app/page.tsx new file mode 100644 index 00000000000..a575f80fc87 --- /dev/null +++ b/examples/showcases/daytona-runcode/app/page.tsx @@ -0,0 +1,297 @@ +"use client"; + +import { + CopilotChat, + CopilotChatConfigurationProvider, + CopilotKitProvider, + useConfigureSuggestions, + useRenderTool, +} from "@copilotkit/react-core/v2"; +import { useEffect, useRef } from "react"; +import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; +import { vscDarkPlus } from "react-syntax-highlighter/dist/esm/styles/prism"; +import { z } from "zod"; + +export const dynamic = "force-dynamic"; + +const runCodeSchema = z.object({ + code: z.string(), + language: z.enum(["python", "typescript", "javascript"]).default("python"), +}); + +const card: React.CSSProperties = { + border: "1px solid #e2e8f0", + borderRadius: 10, + background: "#ffffff", + padding: "10px 12px", + margin: "8px 0", + fontFamily: "ui-sans-serif, system-ui, sans-serif", + fontSize: 13, + color: "#0f172a", +}; +const header: React.CSSProperties = { + display: "flex", + alignItems: "center", + gap: 8, + marginBottom: 8, +}; +const langPill: React.CSSProperties = { + fontSize: 11, + padding: "1px 6px", + borderRadius: 999, + background: "#eef2ff", + color: "#4338ca", + border: "1px solid #c7d2fe", +}; +const statusText: React.CSSProperties = { + fontSize: 12, + color: "#475569", + marginLeft: "auto", +}; +const sandboxTag: React.CSSProperties = { + fontWeight: 600, + letterSpacing: 0.2, +}; + +// Tall fixed-height (~12 lines) scrollable code pane — the visual emphasis of the demo. +const codeScroller: React.CSSProperties = { + height: "18em", + overflow: "auto", + borderRadius: 6, + background: "#1e1e1e", // matches vscDarkPlus + border: "1px solid #1e1e1e", +}; +const codeHighlighterStyle: React.CSSProperties = { + margin: 0, + padding: "8px 10px", + background: "transparent", + fontSize: 12, + lineHeight: 1.5, +}; +const labelStyle: React.CSSProperties = { + fontSize: 11, + color: "#94a3b8", + fontWeight: 400, + marginBottom: 4, + letterSpacing: 0.3, +}; +const codeLabel: React.CSSProperties = { ...labelStyle, marginTop: 0 }; +const resultLabel: React.CSSProperties = { ...labelStyle, marginTop: 10 }; +const resultPane: React.CSSProperties = { + height: "4.6em", + overflow: "auto", + margin: 0, + padding: "6px 10px", + background: "#0f172a", + color: "#e2e8f0", + borderRadius: 6, + fontFamily: + "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace", + fontSize: 12, + lineHeight: 1.5, + whiteSpace: "pre-wrap", +}; + +function Spinner() { + return ( + + ); +} + +function RunCodeCard({ + status, + parameters, + result, +}: { + status: "inProgress" | "executing" | "complete"; + parameters: Partial<{ + code: string; + language: "python" | "typescript" | "javascript"; + }>; + result: string | undefined; +}) { + const language = parameters?.language ?? "python"; + const code = parameters?.code ?? ""; + + const codeRef = useRef(null); + useEffect(() => { + const el = codeRef.current; + if (!el) return; + const distance = el.scrollHeight - el.scrollTop - el.clientHeight; + if (distance < 24) el.scrollTop = el.scrollHeight; + }, [code]); + + const head = (label: React.ReactNode) => ( +
+ Daytona sandbox + {language} + {label} +
+ ); + + const codeBlock = code ? ( + <> +
Generated code:
+
+ + {code} + +
+ + ) : null; + + if (status === "inProgress") { + return ( +
+ {head( + <> +  preparing… + , + )} + {codeBlock} +
+ ); + } + + if (status === "executing") { + return ( +
+ {head( + <> +  running… + , + )} + {codeBlock} +
+ ); + } + + let stdout = ""; + let exitCode = 0; + try { + const parsed = JSON.parse(result ?? "{}"); + stdout = typeof parsed.stdout === "string" ? parsed.stdout : ""; + exitCode = typeof parsed.exitCode === "number" ? parsed.exitCode : 0; + } catch { + stdout = String(result ?? ""); + } + const ok = exitCode === 0; + + return ( +
+ {head( + + {ok ? "✓ done" : `✗ exit ${exitCode}`} + , + )} + {codeBlock} +
Result:
+
{stdout || "(no output)"}
+
+ ); +} + +function RegisterRenderers() { + useRenderTool( + { + name: "runCode", + parameters: runCodeSchema, + render: (props) => ( + + } + result={(props as { result?: string }).result} + /> + ), + }, + [], + ); + + // Starter pills under the input — descriptive titles, kept available across + // the conversation so users can try each language without scrolling back. + useConfigureSuggestions( + { + available: "always", + suggestions: [ + { + title: "Python — Zoo animals", + message: "Run a Python snippet listing 20 popular zoo animals", + }, + { + title: "TypeScript — Fibonacci numbers", + message: + "Run TypeScript that builds an array of the first 10 Fibonacci numbers and logs the JSON", + }, + { + title: "JavaScript — Current timestamp", + message: "Run JavaScript that logs the current timestamp", + }, + ], + }, + [], + ); + + return null; +} + +export default function Page() { + return ( + + + +
+ +
+ +
+
+
+
+ ); +} diff --git a/examples/showcases/daytona-runcode/next.config.ts b/examples/showcases/daytona-runcode/next.config.ts new file mode 100644 index 00000000000..cb651cdc007 --- /dev/null +++ b/examples/showcases/daytona-runcode/next.config.ts @@ -0,0 +1,5 @@ +import type { NextConfig } from "next"; + +const nextConfig: NextConfig = {}; + +export default nextConfig; diff --git a/examples/showcases/daytona-runcode/package-lock.json b/examples/showcases/daytona-runcode/package-lock.json new file mode 100644 index 00000000000..7698187ed47 --- /dev/null +++ b/examples/showcases/daytona-runcode/package-lock.json @@ -0,0 +1,20054 @@ +{ + "name": "daytona-recipe-validation", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "daytona-recipe-validation", + "dependencies": { + "@copilotkit/react-core": "1.58.0", + "@copilotkit/runtime": "1.58.0", + "@daytonaio/sdk": "latest", + "@types/node": "^22.10.5", + "@types/react": "^18.3.18", + "@types/react-dom": "^18.3.5", + "@types/react-syntax-highlighter": "^15.5.13", + "hono": "^4.6.14", + "next": "^15.5.18", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-syntax-highlighter": "^16.1.1", + "typescript": "^5.7.3", + "zod": "^3.23.8" + } + }, + "node_modules/@0no-co/graphql.web": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@0no-co/graphql.web/-/graphql.web-1.2.0.tgz", + "integrity": "sha512-/1iHy9TTr63gE1YcR5idjx8UREz1s0kFhydf3bBLCXyqjhkIc6igAzTOx3zPifCwFR87tsh/4Pa9cNts6d2otw==", + "license": "MIT", + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" + }, + "peerDependenciesMeta": { + "graphql": { + "optional": true + } + } + }, + "node_modules/@a2ui/web_core": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@a2ui/web_core/-/web_core-0.9.0.tgz", + "integrity": "sha512-TsMWuEeuVDsScGIGPy/fWIZu+EOBRfhx6KwjKh3VwY1AwysRenQM8zDr8VrSk14Wck/aBgVxk2zWVrMCK2/s6A==", + "license": "Apache-2.0", + "dependencies": { + "@preact/signals-core": "^1.13.0", + "date-fns": "^4.1.0", + "zod": "^3.25.76", + "zod-to-json-schema": "^3.25.1" + } + }, + "node_modules/@ag-ui/a2ui-middleware": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/@ag-ui/a2ui-middleware/-/a2ui-middleware-0.0.5.tgz", + "integrity": "sha512-nhF8LBzq36BmLR1O3nStxqSpIu1+4//ttqOyQgiXUsKZK1znCyN4q4MBTARrkFa7CuFr3fcNTiZuS4V1HRUHTw==", + "dependencies": { + "clarinet": "^0.12.6" + }, + "peerDependencies": { + "@ag-ui/client": ">=0.0.40", + "rxjs": "7.8.1" + } + }, + "node_modules/@ag-ui/client": { + "version": "0.0.53", + "resolved": "https://registry.npmjs.org/@ag-ui/client/-/client-0.0.53.tgz", + "integrity": "sha512-Mkup36KUp0KXy9v89QtAOWDUoh8H1s1Vgl4zvQv9HqXuAK1TkbtpXJHpbgZJXIxTqd54KT6yCurmC2UkOP7FDQ==", + "dependencies": { + "@ag-ui/core": "0.0.53", + "@ag-ui/encoder": "0.0.53", + "@ag-ui/proto": "0.0.53", + "@types/uuid": "^10.0.0", + "compare-versions": "^6.1.1", + "fast-json-patch": "^3.1.1", + "rxjs": "7.8.1", + "untruncate-json": "^0.0.1", + "uuid": "^11.1.0", + "zod": "^3.22.4" + } + }, + "node_modules/@ag-ui/core": { + "version": "0.0.53", + "resolved": "https://registry.npmjs.org/@ag-ui/core/-/core-0.0.53.tgz", + "integrity": "sha512-11UocR7fFdMWw503bWCX2IOK15vbWfxT11Mn9xOiPBVO/UVcn57ywGrlLL4UaBlPgmUTvuzr2yYR2ElSqiN2wQ==", + "dependencies": { + "zod": "^3.22.4" + } + }, + "node_modules/@ag-ui/encoder": { + "version": "0.0.53", + "resolved": "https://registry.npmjs.org/@ag-ui/encoder/-/encoder-0.0.53.tgz", + "integrity": "sha512-bAOcfVdm6U4H6G6tW+DZfwPEQm1w/snVBTwaFn9nJcEMW69M7/HZuwvEc/7Zo0rK1jRL32N/j60PwTAeky19fw==", + "dependencies": { + "@ag-ui/core": "0.0.53", + "@ag-ui/proto": "0.0.53" + } + }, + "node_modules/@ag-ui/langgraph": { + "version": "0.0.31", + "resolved": "https://registry.npmjs.org/@ag-ui/langgraph/-/langgraph-0.0.31.tgz", + "integrity": "sha512-mK24pfQZiV5SlnDLhTka+873gw7QQOAWXqqDSnwkuyoQQQFX7KC8xZR+4Da2dWqyVhbhNPx+amE16X7twS1wcg==", + "dependencies": { + "@langchain/core": "^1.1.40", + "@langchain/langgraph-sdk": "^1.8.8", + "langchain": ">=1.2.0", + "partial-json": "^0.1.7", + "rxjs": "7.8.1" + }, + "peerDependencies": { + "@ag-ui/client": ">=0.0.42", + "@ag-ui/core": ">=0.0.42" + } + }, + "node_modules/@ag-ui/mcp-apps-middleware": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@ag-ui/mcp-apps-middleware/-/mcp-apps-middleware-0.0.3.tgz", + "integrity": "sha512-Z+NZQXj4J+Y/2PLNsiyhNzRWFtTT2sAoC5dztoAlIsdfzvLvYEa52YGdHesNTN85JJqaIrYxQ8e31IBpKjrnoA==", + "dependencies": { + "@modelcontextprotocol/sdk": "^1.0.0", + "rxjs": "7.8.1" + }, + "peerDependencies": { + "@ag-ui/client": ">=0.0.40" + } + }, + "node_modules/@ag-ui/proto": { + "version": "0.0.53", + "resolved": "https://registry.npmjs.org/@ag-ui/proto/-/proto-0.0.53.tgz", + "integrity": "sha512-swjz22xWT8YUZt5OhmUwkARDQdwt8XM1hmGZbQrhRnNPXKwrKJX9ELlbnQ4iFUQIKkMWpphzE3vA3yNKs2bbKw==", + "dependencies": { + "@ag-ui/core": "0.0.53", + "@bufbuild/protobuf": "^2.2.5", + "@protobuf-ts/protoc": "^2.11.1" + } + }, + "node_modules/@ai-sdk/anthropic": { + "version": "3.0.80", + "resolved": "https://registry.npmjs.org/@ai-sdk/anthropic/-/anthropic-3.0.80.tgz", + "integrity": "sha512-lT8flzmQe7brMXcj+HxIRqC5/P8N2spHj88n7fdY84K8Ay5TI5hbeic3P2T668d9UmKZtIUcefLwgGW4xzfVkA==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "3.0.10", + "@ai-sdk/provider-utils": "4.0.27" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, + "node_modules/@ai-sdk/gateway": { + "version": "3.0.120", + "resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-3.0.120.tgz", + "integrity": "sha512-MYKAeD2q7/sa1ZdqtL2tw0Me0B8Tok6Q/fhkJDhJl39dG8u+VBlWO9yk9lcdm784bM418o1EKObo4aOxs6+18Q==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "3.0.10", + "@ai-sdk/provider-utils": "4.0.27", + "@vercel/oidc": "3.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, + "node_modules/@ai-sdk/google": { + "version": "3.0.80", + "resolved": "https://registry.npmjs.org/@ai-sdk/google/-/google-3.0.80.tgz", + "integrity": "sha512-5ORbm/yFUPO0MEvZsxBMN0cdKw2+lwU/wVn5KN3KF8Dmk1LughuDuUohMh/7iU/XFTiyB0OvmTW/tdV/J7O9zg==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "3.0.10", + "@ai-sdk/provider-utils": "4.0.27" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, + "node_modules/@ai-sdk/google-vertex": { + "version": "3.0.139", + "resolved": "https://registry.npmjs.org/@ai-sdk/google-vertex/-/google-vertex-3.0.139.tgz", + "integrity": "sha512-kWlq+YFGsZEqMWrZKRJdluOzqSQVE5QZ39EcOueNsBH301Tn2HoOVTcjcWpkpbIGmJgDB5lH6DgofD4i2kukdQ==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/anthropic": "2.0.79", + "@ai-sdk/google": "2.0.74", + "@ai-sdk/openai-compatible": "1.0.39", + "@ai-sdk/provider": "2.0.3", + "@ai-sdk/provider-utils": "3.0.25", + "google-auth-library": "^10.5.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, + "node_modules/@ai-sdk/google-vertex/node_modules/@ai-sdk/anthropic": { + "version": "2.0.79", + "resolved": "https://registry.npmjs.org/@ai-sdk/anthropic/-/anthropic-2.0.79.tgz", + "integrity": "sha512-K0U09FPDO1kmLPjRLXFcNSvmnKHJBMARCb8r3Ulw7wU6/+Zh9djWcFDiPPNsklg6yAezcdLTcYPszgWJJ6iOTA==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "2.0.3", + "@ai-sdk/provider-utils": "3.0.25" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, + "node_modules/@ai-sdk/google-vertex/node_modules/@ai-sdk/google": { + "version": "2.0.74", + "resolved": "https://registry.npmjs.org/@ai-sdk/google/-/google-2.0.74.tgz", + "integrity": "sha512-Lhw1742RXc+4pRIvqVXa0jdl5+qdpmw8lj0lm6OchUg9rVGHzymlaxe7CDiYX5U2af4jbjKeTY22LDi3bIycgQ==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "2.0.3", + "@ai-sdk/provider-utils": "3.0.25" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, + "node_modules/@ai-sdk/google-vertex/node_modules/@ai-sdk/provider": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-2.0.3.tgz", + "integrity": "sha512-h88OPkavHTiN9tMn2l5awAznGB0lXzjcLhgR1/rvjB2zlLprsNxbM2tt6OJsHUxduLC3klq0/eqaSf6fX5XVww==", + "license": "Apache-2.0", + "dependencies": { + "json-schema": "^0.4.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@ai-sdk/google-vertex/node_modules/@ai-sdk/provider-utils": { + "version": "3.0.25", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-3.0.25.tgz", + "integrity": "sha512-CvsRu+32Y8a167s+lrIBtsybvgTHp8j9y+6BeTvLeoW3Q+okw/b4CnNUFOLIXsRaKHQKAH+IHNJPYWywfpw0LA==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "2.0.3", + "@standard-schema/spec": "^1.0.0", + "eventsource-parser": "^3.0.6" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, + "node_modules/@ai-sdk/mcp": { + "version": "1.0.44", + "resolved": "https://registry.npmjs.org/@ai-sdk/mcp/-/mcp-1.0.44.tgz", + "integrity": "sha512-ThWJDR5n2ZyO+7QqH3+1I31lb6hO+eRFvHY9QgXLCczpbrK0LCyGtI/i0V+eRpHBiDWnocPotTDCRc2T5xzLGg==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "3.0.10", + "@ai-sdk/provider-utils": "4.0.27", + "pkce-challenge": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, + "node_modules/@ai-sdk/openai": { + "version": "3.0.65", + "resolved": "https://registry.npmjs.org/@ai-sdk/openai/-/openai-3.0.65.tgz", + "integrity": "sha512-ZlVoWH+zrdiYDiUt6n/xvfCsk33mzsB81TUQkBRVx79rxU1FKZqVH9J/QCtEpSLqx0cUzjvtIw9l9p7EbUv+dw==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "3.0.10", + "@ai-sdk/provider-utils": "4.0.27" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, + "node_modules/@ai-sdk/openai-compatible": { + "version": "1.0.39", + "resolved": "https://registry.npmjs.org/@ai-sdk/openai-compatible/-/openai-compatible-1.0.39.tgz", + "integrity": "sha512-001hdQPPXxYBWrz5d+eAmBVYmwzsB+guIey1DFXi1ZEE5H3j7fRrhPpX55MdM9Fle2DS7WZ8b3qkumCIWE92YQ==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "2.0.3", + "@ai-sdk/provider-utils": "3.0.25" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, + "node_modules/@ai-sdk/openai-compatible/node_modules/@ai-sdk/provider": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-2.0.3.tgz", + "integrity": "sha512-h88OPkavHTiN9tMn2l5awAznGB0lXzjcLhgR1/rvjB2zlLprsNxbM2tt6OJsHUxduLC3klq0/eqaSf6fX5XVww==", + "license": "Apache-2.0", + "dependencies": { + "json-schema": "^0.4.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@ai-sdk/openai-compatible/node_modules/@ai-sdk/provider-utils": { + "version": "3.0.25", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-3.0.25.tgz", + "integrity": "sha512-CvsRu+32Y8a167s+lrIBtsybvgTHp8j9y+6BeTvLeoW3Q+okw/b4CnNUFOLIXsRaKHQKAH+IHNJPYWywfpw0LA==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "2.0.3", + "@standard-schema/spec": "^1.0.0", + "eventsource-parser": "^3.0.6" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, + "node_modules/@ai-sdk/provider": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-3.0.10.tgz", + "integrity": "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw==", + "license": "Apache-2.0", + "dependencies": { + "json-schema": "^0.4.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@ai-sdk/provider-utils": { + "version": "4.0.27", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-4.0.27.tgz", + "integrity": "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "3.0.10", + "@standard-schema/spec": "^1.1.0", + "eventsource-parser": "^3.0.8" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, + "node_modules/@antfu/install-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.1.0.tgz", + "integrity": "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==", + "license": "MIT", + "dependencies": { + "package-manager-detector": "^1.3.0", + "tinyexec": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@aws-crypto/crc32": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", + "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/crc32c": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32c/-/crc32c-5.2.0.tgz", + "integrity": "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha1-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha1-browser/-/sha1-browser-5.2.0.tgz", + "integrity": "sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "license": "Apache-2.0", + "dependencies": { + "@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.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/client-s3": { + "version": "3.1055.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.1055.0.tgz", + "integrity": "sha512-FxVwuw86c2Mw4p+0tOtoE+1sDTk+eOBZD/NwwK+wwx1gHkdO/EYSv231O9A1YM8HPjUrI0vZ/hP/szckBxHW0A==", + "license": "Apache-2.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.14", + "@aws-sdk/credential-provider-node": "^3.972.45", + "@aws-sdk/middleware-bucket-endpoint": "^3.972.16", + "@aws-sdk/middleware-expect-continue": "^3.972.13", + "@aws-sdk/middleware-flexible-checksums": "^3.974.22", + "@aws-sdk/middleware-location-constraint": "^3.972.11", + "@aws-sdk/middleware-sdk-s3": "^3.972.43", + "@aws-sdk/middleware-ssec": "^3.972.11", + "@aws-sdk/signature-v4-multi-region": "^3.996.29", + "@aws-sdk/types": "^3.973.9", + "@smithy/core": "^3.24.3", + "@smithy/fetch-http-handler": "^5.4.3", + "@smithy/node-http-handler": "^4.7.3", + "@smithy/types": "^4.14.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.974.14", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.974.14.tgz", + "integrity": "sha512-ppamm04uoj3hhNO5IlQSs5D6rWX1fWkzcn6a4pZrojk8Y6ObY9wzLDdT/Eq3gv6O9hOebi9tYTNB8b8fQj9XJw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.9", + "@aws-sdk/xml-builder": "^3.972.26", + "@aws/lambda-invoke-store": "^0.2.2", + "@smithy/core": "^3.24.3", + "@smithy/signature-v4": "^5.4.2", + "@smithy/types": "^4.14.2", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/crc64-nvme": { + "version": "3.972.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/crc64-nvme/-/crc64-nvme-3.972.9.tgz", + "integrity": "sha512-P+QGozmXn2mZZI7sDgk+aUm+RTI61MPSFB+Ir2vjEjEbEsE4e7hYtzrDvAUxZy9ko81h53e11+F/GYlvwDkaOQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.972.40", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.40.tgz", + "integrity": "sha512-jjT0p0Y7KZtcvExYiPCLJnqM9lkXDV1KBEg/13OE2DXv/9batzlyJHVKUEnRNJccY0O2Sul17E1su38CgdBhGQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.14", + "@aws-sdk/types": "^3.973.9", + "@smithy/core": "^3.24.3", + "@smithy/types": "^4.14.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.972.42", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.42.tgz", + "integrity": "sha512-+3fsKtWybe5BjKEUA3/07oh7Ayfd82IED2+gyyaVfS/4PU78E3TaOQxSGOJ1t7Imefoidw/ne9QA7apX8wEnJg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.14", + "@aws-sdk/types": "^3.973.9", + "@smithy/core": "^3.24.3", + "@smithy/fetch-http-handler": "^5.4.3", + "@smithy/node-http-handler": "^4.7.3", + "@smithy/types": "^4.14.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.972.44", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.44.tgz", + "integrity": "sha512-gZFw5wBefCIPg9vpT+gV5FdhfNKhYTVDZa1IsZCcn3SRoYUOJ/E05vwIogkJoonqBL0ttBGi5vhthX7xceekRg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.14", + "@aws-sdk/credential-provider-env": "^3.972.40", + "@aws-sdk/credential-provider-http": "^3.972.42", + "@aws-sdk/credential-provider-login": "^3.972.44", + "@aws-sdk/credential-provider-process": "^3.972.40", + "@aws-sdk/credential-provider-sso": "^3.972.44", + "@aws-sdk/credential-provider-web-identity": "^3.972.44", + "@aws-sdk/nested-clients": "^3.997.12", + "@aws-sdk/types": "^3.973.9", + "@smithy/core": "^3.24.3", + "@smithy/credential-provider-imds": "^4.3.2", + "@smithy/types": "^4.14.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login": { + "version": "3.972.44", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.44.tgz", + "integrity": "sha512-QqEGHfQeZgUDqh7zpqHufrZ8T644ELEWvB+4gUdewLyRw4IRF+6CJqeQuRWqucZdQzoQeMh7fNAD9BWxFAdNig==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.14", + "@aws-sdk/nested-clients": "^3.997.12", + "@aws-sdk/types": "^3.973.9", + "@smithy/core": "^3.24.3", + "@smithy/types": "^4.14.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.972.45", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.45.tgz", + "integrity": "sha512-3YCv52ExXIRz3LAVNysevd+s7akSpg9dl39v9LJ7dOQH+s5rHi3jMZYQyxwMmglxQGMuzYRfQ0o1VSP2UOlIRw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "^3.972.40", + "@aws-sdk/credential-provider-http": "^3.972.42", + "@aws-sdk/credential-provider-ini": "^3.972.44", + "@aws-sdk/credential-provider-process": "^3.972.40", + "@aws-sdk/credential-provider-sso": "^3.972.44", + "@aws-sdk/credential-provider-web-identity": "^3.972.44", + "@aws-sdk/types": "^3.973.9", + "@smithy/core": "^3.24.3", + "@smithy/credential-provider-imds": "^4.3.2", + "@smithy/types": "^4.14.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.972.40", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.40.tgz", + "integrity": "sha512-cXaozlgJCOwmE6D7x4npcPdyk7kiFZdrGjN3D6tXXtItJJMNGPafDfAJn4YQmciMooG/X+b0Y6RTqdVVMx26jg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.14", + "@aws-sdk/types": "^3.973.9", + "@smithy/core": "^3.24.3", + "@smithy/types": "^4.14.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.972.44", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.44.tgz", + "integrity": "sha512-YePoj5kQuPmE0MHnyftXCfsO8ZSBd2kDr50XEIUrdejSbGFlayYvUuCohdb8drhGhPm6b65o7H1eC26EZhwUvA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.14", + "@aws-sdk/nested-clients": "^3.997.12", + "@aws-sdk/token-providers": "3.1054.0", + "@aws-sdk/types": "^3.973.9", + "@smithy/core": "^3.24.3", + "@smithy/types": "^4.14.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.972.44", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.44.tgz", + "integrity": "sha512-Ys/JJe++8Z2Y5meR1taMBaVcrGBA0/XsVTQR+qOKZbdNyg+8Jlv5rYZSwh8SqEHY00goSOZy7PHzZ2rLNQxDLg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.14", + "@aws-sdk/nested-clients": "^3.997.12", + "@aws-sdk/types": "^3.973.9", + "@smithy/core": "^3.24.3", + "@smithy/types": "^4.14.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/lib-storage": { + "version": "3.1055.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/lib-storage/-/lib-storage-3.1055.0.tgz", + "integrity": "sha512-wlTeeGJYkEMmTk1wiYkhMz7mvjEoeJlWMQ7kMNJ+GOAbvHYd+u8qQ/PCt7XNPVqnYej1WZs0fskOBMTYzsqIJw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.3", + "@smithy/types": "^4.14.2", + "buffer": "5.6.0", + "events": "3.3.0", + "stream-browserify": "3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-s3": "^3.1055.0" + } + }, + "node_modules/@aws-sdk/middleware-bucket-endpoint": { + "version": "3.972.16", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.972.16.tgz", + "integrity": "sha512-FhasMTBDBmMN7EEa1hUeHwo5p5Mv3Dm8w0VEbdXX/6ola/uyhRuJt8zGkH09mLTmab20USTzEpPqyqEoe1MqNg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.14", + "@aws-sdk/types": "^3.973.9", + "@smithy/core": "^3.24.3", + "@smithy/types": "^4.14.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-expect-continue": { + "version": "3.972.13", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.972.13.tgz", + "integrity": "sha512-sHiqIFg8o2ipT7t40B89Vj0ubSUtY6OSt/+Ee/OXhHch5K4+81zP2+QX8Lkc/nJ2QSmCySxOke7TEbmX69fe2g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.9", + "@smithy/core": "^3.24.3", + "@smithy/types": "^4.14.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-flexible-checksums": { + "version": "3.974.22", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.974.22.tgz", + "integrity": "sha512-ot1kZ1JGHUxcXPOARhej/n/+Odfx9VPt60pNrUq8Lf/U2blIF3+uj5v56gw76VD70dZvrfeLNo9jKz6pQJfOlA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@aws-crypto/crc32c": "5.2.0", + "@aws-crypto/util": "5.2.0", + "@aws-sdk/core": "^3.974.14", + "@aws-sdk/crc64-nvme": "^3.972.9", + "@aws-sdk/types": "^3.973.9", + "@smithy/core": "^3.24.3", + "@smithy/types": "^4.14.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-location-constraint": { + "version": "3.972.11", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.972.11.tgz", + "integrity": "sha512-hkfspNUP4criAH6ton6BGKgnm5dZx+7bUOy1YqlTfejDeUPAM23D81q/IX+hdlS3KUsfwGz5ADTqZWKBEUpf4A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.9", + "@smithy/types": "^4.14.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3": { + "version": "3.972.43", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.43.tgz", + "integrity": "sha512-CBmixMY36JdAdt9ALgm7yVlvOXGUCHt9Z2kn5p9XVO5StO6HCH+cayV7YYV1CDLsXvVyebaXgBmif9wHoxCeNA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.14", + "@aws-sdk/signature-v4-multi-region": "^3.996.29", + "@aws-sdk/types": "^3.973.9", + "@smithy/core": "^3.24.3", + "@smithy/types": "^4.14.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-ssec": { + "version": "3.972.11", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.972.11.tgz", + "integrity": "sha512-7PQvGNhtveKlvVqNahqWx5yrwxP7ecwAoB1dYBf8eKwfo2tzzCbNnW+q2nO3N066ktQaB4iBQbDRWtizm+amoQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.9", + "@smithy/types": "^4.14.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.997.12", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.12.tgz", + "integrity": "sha512-Js2VYaCM269feB0cs0cGmlIhdOgT9aMqzdBx68lCy6kVCYfzr0T36ovUFDvfUmatkuBeyBJhCwaLBh7P8meH5Q==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.974.14", + "@aws-sdk/signature-v4-multi-region": "^3.996.29", + "@aws-sdk/types": "^3.973.9", + "@smithy/core": "^3.24.3", + "@smithy/fetch-http-handler": "^5.4.3", + "@smithy/node-http-handler": "^4.7.3", + "@smithy/types": "^4.14.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.996.29", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.29.tgz", + "integrity": "sha512-Few9FoQqOt/0KSvZYP+qdW0dfOhfQ9N+gl2UUDvCPW6mkPKHli9LMbKxWj+wZ5zKPaOoqxuR3Hhy3OTpndkfSw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.9", + "@smithy/signature-v4": "^5.4.2", + "@smithy/types": "^4.14.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.1054.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1054.0.tgz", + "integrity": "sha512-hG9YKApmZOw+drJ9Nuoaf/OvC8e5W1+3eoLeN5p2uVCZRWsv27teIS0b4kiH6Sfv3WMmamqYJxmE2WMwyp/L/A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.14", + "@aws-sdk/nested-clients": "^3.997.12", + "@aws-sdk/types": "^3.973.9", + "@smithy/core": "^3.24.3", + "@smithy/types": "^4.14.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/types": { + "version": "3.973.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.9.tgz", + "integrity": "sha512-kuBfgQVdcz5Bmapc4A13YbpVw/pXkesfhetcFYwbntqas8sF41OHyd4o28+/TG2ZQdHBsv90Lsu5y6oitvYCdg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.965.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.5.tgz", + "integrity": "sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/xml-builder": { + "version": "3.972.26", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.26.tgz", + "integrity": "sha512-cDbrqvDS73whl6YAPSPq0U6whzG6UWI9PuWh0wrUuGoZexhWEqhdunbukV7iBoaWnFV1AODutM5hOD6rtn439g==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.2", + "fast-xml-parser": "5.7.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws/lambda-invoke-store": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.4.tgz", + "integrity": "sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@braintree/sanitize-url": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.1.2.tgz", + "integrity": "sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==", + "license": "MIT" + }, + "node_modules/@bufbuild/protobuf": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.12.0.tgz", + "integrity": "sha512-B/XlCaFIP8LOwzo+bz5uFzATYokcwCKQcghqnlfwSmM5eX/qTkvDBnDPs+gXtX/RyjxJ4DRikECcPJbyALA8FA==", + "license": "(Apache-2.0 AND BSD-3-Clause)" + }, + "node_modules/@cfworker/json-schema": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@cfworker/json-schema/-/json-schema-4.1.1.tgz", + "integrity": "sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==", + "license": "MIT" + }, + "node_modules/@chevrotain/types": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.1.2.tgz", + "integrity": "sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw==", + "license": "Apache-2.0" + }, + "node_modules/@copilotkit/a2ui-renderer": { + "version": "1.58.0", + "resolved": "https://registry.npmjs.org/@copilotkit/a2ui-renderer/-/a2ui-renderer-1.58.0.tgz", + "integrity": "sha512-Pf3H4xMMFsgZ70mddddx68Msl/XPsws+7+KeTIiD0nhSc/Bnu5XigrM+5f0ocUQCOPJY+hOxNSm3t0Le1jCPfA==", + "license": "MIT", + "dependencies": { + "@a2ui/web_core": "0.9.0", + "clsx": "^2.1.1", + "zod": "^3.25.75", + "zod-to-json-schema": "^3.24.1" + }, + "peerDependencies": { + "react": "^18 || ^19 || ^19.0.0-rc", + "react-dom": "^18 || ^19 || ^19.0.0-rc" + } + }, + "node_modules/@copilotkit/core": { + "version": "1.58.0", + "resolved": "https://registry.npmjs.org/@copilotkit/core/-/core-1.58.0.tgz", + "integrity": "sha512-n0VBPFrRT4hSZJyG7XGP+Zc3BiK+QxXRkq5V9IyEY06e2NQ1N7V697wIh0gB1AuYonJL6qUg79oG3rOumqhnlA==", + "dependencies": { + "@ag-ui/client": "0.0.53", + "@copilotkit/shared": "1.58.0", + "@tanstack/pacer": "^0.20.1", + "phoenix": "^1.8.4", + "rxjs": "7.8.1", + "zod-to-json-schema": "^3.24.6" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@copilotkit/license-verifier": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@copilotkit/license-verifier/-/license-verifier-0.4.0.tgz", + "integrity": "sha512-axD7B767YVHGfz/nekw3wUk9GFZGIcIq2X9AZfNA0qb6GnHcB3yJ636T21LHhon5sHerxe1oOOuNYmiAUMNonQ==" + }, + "node_modules/@copilotkit/react-core": { + "version": "1.58.0", + "resolved": "https://registry.npmjs.org/@copilotkit/react-core/-/react-core-1.58.0.tgz", + "integrity": "sha512-/f79r/YL/Yujr2x89aoQ0GoB51655aCa0j3vaAGxgrDL52qf9tE1pqvQrkp3THLly3PCU/vC4+C1UdN+L+EADw==", + "license": "MIT", + "dependencies": { + "@ag-ui/client": "0.0.53", + "@ag-ui/core": "0.0.53", + "@copilotkit/a2ui-renderer": "1.58.0", + "@copilotkit/core": "1.58.0", + "@copilotkit/runtime-client-gql": "1.58.0", + "@copilotkit/shared": "1.58.0", + "@copilotkit/web-inspector": "1.58.0", + "@jetbrains/websandbox": "^1.1.3", + "@lit-labs/react": "^2.0.2", + "@radix-ui/react-dropdown-menu": "^2.1.15", + "@radix-ui/react-slot": "^1.2.3", + "@radix-ui/react-tooltip": "^1.2.7", + "@scarf/scarf": "^1.3.0", + "@tanstack/react-virtual": "^3.13.0", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "katex": "^0.16.22", + "lucide-react": "^0.525.0", + "react-markdown": "^8.0.7", + "rxjs": "7.8.1", + "streamdown": "^1.3.0", + "tailwind-merge": "^3.3.1", + "tw-animate-css": "^1.3.5", + "untruncate-json": "^0.0.1", + "use-stick-to-bottom": "^1.1.1", + "zod-to-json-schema": "^3.24.5" + }, + "peerDependencies": { + "react": "^18 || ^19 || ^19.0.0-rc", + "react-dom": "^18 || ^19 || ^19.0.0-rc", + "zod": ">=3.0.0" + } + }, + "node_modules/@copilotkit/runtime": { + "version": "1.58.0", + "resolved": "https://registry.npmjs.org/@copilotkit/runtime/-/runtime-1.58.0.tgz", + "integrity": "sha512-Ic08Pqn1pySsLJUhFMkbSqo6FJiFNRmwOmoFaiEUt+NUfCe4ItD8jJZ1ViwxNBQ/Y40lx5iM1HweWT0GOqHKJA==", + "license": "MIT", + "dependencies": { + "@ag-ui/a2ui-middleware": "0.0.5", + "@ag-ui/client": "0.0.53", + "@ag-ui/core": "0.0.53", + "@ag-ui/encoder": "0.0.53", + "@ag-ui/langgraph": "0.0.31", + "@ag-ui/mcp-apps-middleware": "0.0.3", + "@ai-sdk/anthropic": "^3.0.49", + "@ai-sdk/google": "^3.0.33", + "@ai-sdk/google-vertex": "^3.0.97", + "@ai-sdk/mcp": "^1.0.21", + "@ai-sdk/openai": "^3.0.36", + "@copilotkit/license-verifier": "0.4.0", + "@copilotkit/shared": "1.58.0", + "@graphql-yoga/plugin-defer-stream": "^3.3.1", + "@hono/node-server": "^1.13.5", + "@modelcontextprotocol/sdk": "^1.18.2", + "@remix-run/node-fetch-server": "^0.13.0", + "@scarf/scarf": "^1.3.0", + "@segment/analytics-node": "^2.1.2", + "ai": "^6.0.104", + "clarinet": "^0.12.4", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.1", + "cors": "^2.8.5", + "express": "^4.21.2", + "graphql": "^16.8.1", + "graphql-scalars": "^1.23.0", + "graphql-yoga": "^5.3.1", + "hono": "^4.11.4", + "openai": "^4.85.1 || >=5.0.0", + "partial-json": "^0.1.7", + "phoenix": "^1.8.4", + "pino": "^9.2.0", + "pino-pretty": "^11.2.1", + "reflect-metadata": "^0.2.2", + "rxjs": "7.8.1", + "type-graphql": "2.0.0-rc.1", + "uuid": "^10.0.0", + "ws": "^8.18.0", + "zod": "^3.23.3" + }, + "peerDependencies": { + "@anthropic-ai/sdk": "^0.57.0", + "@langchain/aws": ">=0.1.9", + "@langchain/community": ">=0.3.58", + "@langchain/core": ">=0.3.66", + "@langchain/google-gauth": ">=0.1.0", + "@langchain/langgraph-sdk": ">=0.1.2", + "@langchain/openai": ">=0.4.2", + "groq-sdk": ">=0.3.0 <1.0.0", + "langchain": ">=0.3.3", + "openai": "^4.85.1 || >=5.0.0" + }, + "peerDependenciesMeta": { + "@anthropic-ai/sdk": { + "optional": true + }, + "@langchain/aws": { + "optional": true + }, + "@langchain/community": { + "optional": true + }, + "@langchain/google-gauth": { + "optional": true + }, + "@langchain/langgraph-sdk": { + "optional": true + }, + "@langchain/openai": { + "optional": true + }, + "groq-sdk": { + "optional": true + }, + "langchain": { + "optional": true + }, + "openai": { + "optional": true + } + } + }, + "node_modules/@copilotkit/runtime-client-gql": { + "version": "1.58.0", + "resolved": "https://registry.npmjs.org/@copilotkit/runtime-client-gql/-/runtime-client-gql-1.58.0.tgz", + "integrity": "sha512-88NDCB+XDY9DzUbwXU+1Ax7tvaPt6A1O/dPkWFxMj/pBziM7cE3temfgiXD9n31d1D+FJvzYIayiTkJJZUbHXA==", + "license": "MIT", + "dependencies": { + "@copilotkit/shared": "1.58.0", + "@urql/core": "^5.0.3", + "untruncate-json": "^0.0.1", + "urql": "^4.1.0" + }, + "peerDependencies": { + "react": "^18 || ^19 || ^19.0.0-rc" + } + }, + "node_modules/@copilotkit/runtime/node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "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).", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@copilotkit/shared": { + "version": "1.58.0", + "resolved": "https://registry.npmjs.org/@copilotkit/shared/-/shared-1.58.0.tgz", + "integrity": "sha512-RTy8duBBeX4eJOq17O789ks4q0GDoerXecm6QGca3sv8lbYFaTeNku3BB7N8h+RiXm5jjclbaCp+dRlUCprAQQ==", + "license": "MIT", + "dependencies": { + "@ag-ui/client": "0.0.53", + "@copilotkit/license-verifier": "0.4.0", + "@segment/analytics-node": "^2.1.2", + "@standard-schema/spec": "^1.0.0", + "chalk": "4.1.2", + "graphql": "^16.8.1", + "partial-json": "^0.1.7", + "uuid": "^11.1.0", + "zod": "^3.23.3", + "zod-to-json-schema": "^3.23.5" + }, + "peerDependencies": { + "@ag-ui/core": ">=0.0.48" + } + }, + "node_modules/@copilotkit/web-inspector": { + "version": "1.58.0", + "resolved": "https://registry.npmjs.org/@copilotkit/web-inspector/-/web-inspector-1.58.0.tgz", + "integrity": "sha512-gWRzAZ8tBuAAV1PTdPaQTeKhLzSF9XjF3x/S4jPvdtEZwEmvlQvkKuDoIC3M22lVQLk/61Y1a0gXff3KMiwygw==", + "dependencies": { + "@ag-ui/client": "0.0.53", + "@copilotkit/core": "1.58.0", + "lit": "^3.2.0", + "lucide": "^0.525.0", + "marked": "^12.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@daytona/api-client": { + "version": "0.182.0", + "resolved": "https://registry.npmjs.org/@daytona/api-client/-/api-client-0.182.0.tgz", + "integrity": "sha512-xvfb7UsXM+pdONuHPHBp2q6v07/IyQa6KSGLOEcDXNQt8oEWFBDVM//gQI98X0oadhdoe25FqWTHOjphrgpjzA==", + "license": "Apache-2.0", + "dependencies": { + "axios": "^1.6.1" + } + }, + "node_modules/@daytona/toolbox-api-client": { + "version": "0.182.0", + "resolved": "https://registry.npmjs.org/@daytona/toolbox-api-client/-/toolbox-api-client-0.182.0.tgz", + "integrity": "sha512-xWOlbBmVE59QNCUkNI/APXDpVf66xB09EdqlylpguwijTIRCycR+Y01bO0733WovHTMV6e3TVqomEbvpjCA3xg==", + "license": "Apache-2.0", + "dependencies": { + "axios": "^1.6.1" + } + }, + "node_modules/@daytonaio/sdk": { + "version": "0.182.0", + "resolved": "https://registry.npmjs.org/@daytonaio/sdk/-/sdk-0.182.0.tgz", + "integrity": "sha512-+NRIcuWB8XUYXOFD4Ay7AlFPNlXPCcc4OLx/zXX2BQjNv2WO3VQDLDUydjA8ERlQfs277wpqFX0AGRHoOwQbuw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/client-s3": "^3.787.0", + "@aws-sdk/lib-storage": "^3.798.0", + "@daytona/api-client": "0.182.0", + "@daytona/toolbox-api-client": "0.182.0", + "@iarna/toml": "^2.2.5", + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/exporter-trace-otlp-http": "^0.217.0", + "@opentelemetry/instrumentation-http": "^0.217.0", + "@opentelemetry/otlp-exporter-base": "0.217.0", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/sdk-node": "^0.217.0", + "@opentelemetry/sdk-trace-base": "^2.7.1", + "@opentelemetry/semantic-conventions": "^1.40.0", + "axios": "^1.15.2", + "busboy": "^1.0.0", + "dotenv": "^17.0.1", + "expand-tilde": "^2.0.2", + "fast-glob": "^3.3.0", + "form-data": "^4.0.4", + "isomorphic-ws": "^5.0.0", + "pathe": "^2.0.3", + "shell-quote": "^1.8.2", + "tar": "^7.5.11" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@envelop/core": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/@envelop/core/-/core-5.5.1.tgz", + "integrity": "sha512-3DQg8sFskDo386TkL5j12jyRAdip/8yzK3x7YGbZBgobZ4aKXrvDU0GppU0SnmrpQnNaiTUsxBs9LKkwQ/eyvw==", + "license": "MIT", + "dependencies": { + "@envelop/instrumentation": "^1.0.0", + "@envelop/types": "^5.2.1", + "@whatwg-node/promise-helpers": "^1.2.4", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@envelop/instrumentation": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@envelop/instrumentation/-/instrumentation-1.0.0.tgz", + "integrity": "sha512-cxgkB66RQB95H3X27jlnxCRNTmPuSTgmBAq6/4n2Dtv4hsk4yz8FadA1ggmd0uZzvKqWD6CR+WFgTjhDqg7eyw==", + "license": "MIT", + "dependencies": { + "@whatwg-node/promise-helpers": "^1.2.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@envelop/types": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@envelop/types/-/types-5.2.1.tgz", + "integrity": "sha512-CsFmA3u3c2QoLDTfEpGr4t25fjMU31nyvse7IzWTvb0ZycuPjMjb0fjlheh+PbhBYb9YLugnT2uY6Mwcg1o+Zg==", + "license": "MIT", + "dependencies": { + "@whatwg-node/promise-helpers": "^1.0.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@fastify/busboy": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-3.2.0.tgz", + "integrity": "sha512-m9FVDXU3GT2ITSe0UaMA5rU3QkfC/UXtCU8y0gSN/GugTqtVldOBWIB5V6V3sbmenVZUIpU6f+mPEO2+m5iTaA==", + "license": "MIT" + }, + "node_modules/@floating-ui/core": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz", + "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.11" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz", + "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.5", + "@floating-ui/utils": "^0.2.11" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.8.tgz", + "integrity": "sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.7.6" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz", + "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", + "license": "MIT" + }, + "node_modules/@graphql-tools/executor": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@graphql-tools/executor/-/executor-1.5.3.tgz", + "integrity": "sha512-mgBFC0bsrZPZLu9EnydpMnAuQ8Iiq0CEbUcsmvXsm2/iYektGHDN/+bmb7hicA6dWZtdPfklYJmr21WD0GnOfA==", + "license": "MIT", + "dependencies": { + "@graphql-tools/utils": "^11.1.0", + "@graphql-typed-document-node/core": "^3.2.0", + "@repeaterjs/repeater": "^3.0.4", + "@whatwg-node/disposablestack": "^0.0.6", + "@whatwg-node/promise-helpers": "^1.0.0", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/executor/node_modules/@graphql-tools/utils": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-11.1.0.tgz", + "integrity": "sha512-PtFVG4r8Z2LEBSaPYQMusBiB3o6kjLVJyjCLbnWem/SpSuM21v6LTmgpkXfYU1qpBV2UGsFyuEnSJInl8fR1Ag==", + "license": "MIT", + "dependencies": { + "@graphql-typed-document-node/core": "^3.1.1", + "@whatwg-node/promise-helpers": "^1.0.0", + "cross-inspect": "1.0.1", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/merge": { + "version": "9.1.9", + "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-9.1.9.tgz", + "integrity": "sha512-iHUWNjRHeQRYdgIMIuChThOwoKzA9vrzYeslgfBo5eUYEyHGZCoDPjAavssoYXLwstYt1dZj2J22jSzc2DrN0Q==", + "license": "MIT", + "dependencies": { + "@graphql-tools/utils": "^11.1.0", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/merge/node_modules/@graphql-tools/utils": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-11.1.0.tgz", + "integrity": "sha512-PtFVG4r8Z2LEBSaPYQMusBiB3o6kjLVJyjCLbnWem/SpSuM21v6LTmgpkXfYU1qpBV2UGsFyuEnSJInl8fR1Ag==", + "license": "MIT", + "dependencies": { + "@graphql-typed-document-node/core": "^3.1.1", + "@whatwg-node/promise-helpers": "^1.0.0", + "cross-inspect": "1.0.1", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/schema": { + "version": "10.0.33", + "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-10.0.33.tgz", + "integrity": "sha512-O6P3RIftO0jafnSsFAqpjurUuUxJ43s/AdPVLQsBkI6y4Ic/tKm4C1Qm1KKQsCDTOxXPJClh/v3g7k7yLKCFBQ==", + "license": "MIT", + "dependencies": { + "@graphql-tools/merge": "^9.1.9", + "@graphql-tools/utils": "^11.1.0", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/schema/node_modules/@graphql-tools/utils": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-11.1.0.tgz", + "integrity": "sha512-PtFVG4r8Z2LEBSaPYQMusBiB3o6kjLVJyjCLbnWem/SpSuM21v6LTmgpkXfYU1qpBV2UGsFyuEnSJInl8fR1Ag==", + "license": "MIT", + "dependencies": { + "@graphql-typed-document-node/core": "^3.1.1", + "@whatwg-node/promise-helpers": "^1.0.0", + "cross-inspect": "1.0.1", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/utils": { + "version": "10.11.0", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-10.11.0.tgz", + "integrity": "sha512-iBFR9GXIs0gCD+yc3hoNswViL1O5josI33dUqiNStFI/MHLCEPduasceAcazRH77YONKNiviHBV8f7OgcT4o2Q==", + "license": "MIT", + "dependencies": { + "@graphql-typed-document-node/core": "^3.1.1", + "@whatwg-node/promise-helpers": "^1.0.0", + "cross-inspect": "1.0.1", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-typed-document-node/core": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.2.0.tgz", + "integrity": "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==", + "license": "MIT", + "peerDependencies": { + "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-yoga/logger": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@graphql-yoga/logger/-/logger-2.0.1.tgz", + "integrity": "sha512-Nv0BoDGLMg9QBKy9cIswQ3/6aKaKjlTh87x3GiBg2Z4RrjyrM48DvOOK0pJh1C1At+b0mUIM67cwZcFTDLN4sA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@graphql-yoga/plugin-defer-stream": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/@graphql-yoga/plugin-defer-stream/-/plugin-defer-stream-3.21.0.tgz", + "integrity": "sha512-zWhbsl9ceyR8PwRnJCwKpE7KtakaaIi0Fsit4K+LkQu7XIGuVmLlAB9Cr6Esi0AjDmjAnviLYotknythNcEyGA==", + "license": "MIT", + "dependencies": { + "@graphql-tools/utils": "^10.11.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "graphql": "^15.2.0 || ^16.0.0", + "graphql-yoga": "^5.21.0" + } + }, + "node_modules/@graphql-yoga/subscription": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@graphql-yoga/subscription/-/subscription-5.0.5.tgz", + "integrity": "sha512-oCMWOqFs6QV96/NZRt/ZhTQvzjkGB4YohBOpKM4jH/lDT4qb7Lex/aGCxpi/JD9njw3zBBtMqxbaC22+tFHVvw==", + "license": "MIT", + "dependencies": { + "@graphql-yoga/typed-event-target": "^3.0.2", + "@repeaterjs/repeater": "^3.0.4", + "@whatwg-node/events": "^0.1.0", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@graphql-yoga/typed-event-target": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@graphql-yoga/typed-event-target/-/typed-event-target-3.0.2.tgz", + "integrity": "sha512-ZpJxMqB+Qfe3rp6uszCQoag4nSw42icURnBRfFYSOmTgEeOe4rD0vYlbA8spvCu2TlCesNTlEN9BLWtQqLxabA==", + "license": "MIT", + "dependencies": { + "@repeaterjs/repeater": "^3.0.4", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@grpc/grpc-js": { + "version": "1.14.4", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.4.tgz", + "integrity": "sha512-k9Dj3DV/itK9D06Y8f190Qgop7/Ui+D0njFV3LHMPwPT75DpXLQohE9Wmz0QElrJnzsjB7KPWiKJbOl7IPDArQ==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/proto-loader": "^0.8.0", + "@js-sdsl/ordered-map": "^4.4.2" + }, + "engines": { + "node": ">=12.10.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.1.tgz", + "integrity": "sha512-wtF6h+DY6M3YaDBPAmvuuA6jV8Sif9MjtOI5euKFWRgCDl5PeDpPsHR9u2l6St5ceY8AZgoNDww5+HvEsXFsGg==", + "license": "Apache-2.0", + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.5.5", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@grpc/proto-loader/node_modules/protobufjs": { + "version": "7.6.1", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.1.tgz", + "integrity": "sha512-4K0myLaWL5EteuSAro91EGFgcfVgxb64Jx+7oDAY6GOkXD4M69yuSEljNcInGVCA5sOPxmZ/EqDLj2x0Q0+Ygg==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.5", + "@protobufjs/eventemitter": "^1.1.1", + "@protobufjs/fetch": "^1.1.1", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.2", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.1", + "@types/node": ">=13.7.0", + "long": "^5.3.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@hono/node-server": { + "version": "1.19.14", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", + "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==", + "license": "MIT", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, + "node_modules/@iarna/toml": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz", + "integrity": "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==", + "license": "ISC" + }, + "node_modules/@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", + "license": "MIT" + }, + "node_modules/@iconify/utils": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-3.1.3.tgz", + "integrity": "sha512-LPKOXPn/zV+zis1oOfGWogaXVpqUybF3ZS6SCZIsz8vg0ivVp9+fVqyYB7xq0aiST/VhUQYGO1qo6uoYSiEJqw==", + "license": "MIT", + "dependencies": { + "@antfu/install-pkg": "^1.1.0", + "@iconify/types": "^2.0.0", + "import-meta-resolve": "^4.2.0" + } + }, + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@jetbrains/websandbox": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jetbrains/websandbox/-/websandbox-1.2.1.tgz", + "integrity": "sha512-FXg2JZ4O4DPf3Tj3MGwnQuJ8jEzddqEZszBEVD4SFPkq44FrbLomS7BLY3Q3eKV312oQRJU9api1YvX3uhNelw==", + "license": "Apache-2.0" + }, + "node_modules/@js-sdsl/ordered-map": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", + "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "node_modules/@langchain/core": { + "version": "1.1.48", + "resolved": "https://registry.npmjs.org/@langchain/core/-/core-1.1.48.tgz", + "integrity": "sha512-fQU6Guyb1pwc2fEplmA8FPbKfOMAofjnyJzExevro0FxEiuGHE18Ov/ZHmT9trWCDTZRI9eW1VIc6aChxV8pAQ==", + "license": "MIT", + "dependencies": { + "@cfworker/json-schema": "^4.0.2", + "@standard-schema/spec": "^1.1.0", + "js-tiktoken": "^1.0.12", + "langsmith": ">=0.5.0 <1.0.0", + "mustache": "^4.2.0", + "p-queue": "^6.6.2", + "zod": "^3.25.76 || ^4" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@langchain/langgraph": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@langchain/langgraph/-/langgraph-1.3.2.tgz", + "integrity": "sha512-SL7Ktsr681R7da+1b2MVOWEbaCoFJOXEJPTGOjg4JIG4C7quWbTYC8DzxhcCxte6D/8cGp0rYDBnbKLXEpNqlA==", + "license": "MIT", + "dependencies": { + "@langchain/langgraph-checkpoint": "^1.0.2", + "@langchain/langgraph-sdk": "~1.9.4", + "@langchain/protocol": "^0.0.15", + "@standard-schema/spec": "1.1.0", + "uuid": "^10.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@langchain/core": "^1.1.44", + "zod": "^3.25.32 || ^4.2.0", + "zod-to-json-schema": "^3.x" + }, + "peerDependenciesMeta": { + "zod-to-json-schema": { + "optional": true + } + } + }, + "node_modules/@langchain/langgraph-checkpoint": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@langchain/langgraph-checkpoint/-/langgraph-checkpoint-1.0.2.tgz", + "integrity": "sha512-F4E5Tr0nt8FGghgdscJtHw+ABzChOHeI80R7Y1pjIHdiJom6c2ieo76vL+FWiny80JmoGqhrVAEIWrw0cXKPxg==", + "license": "MIT", + "dependencies": { + "uuid": "^10.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@langchain/core": "^1.1.44" + } + }, + "node_modules/@langchain/langgraph-checkpoint/node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "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).", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@langchain/langgraph-sdk": { + "version": "1.9.9", + "resolved": "https://registry.npmjs.org/@langchain/langgraph-sdk/-/langgraph-sdk-1.9.9.tgz", + "integrity": "sha512-aiWHbmqxWj5sAMwFsaB3eSGQvKpMbUKTlt9zbAC0T7IiFqDYUWi9gJUGsTdvJutAfB3P/NzC4s8ETUtUQEUlYg==", + "license": "MIT", + "dependencies": { + "@langchain/protocol": "^0.0.15", + "@types/json-schema": "^7.0.15", + "p-queue": "^9.0.1", + "p-retry": "^7.1.1", + "uuid": "^13.0.0" + }, + "peerDependencies": { + "@langchain/core": "^1.1.44", + "react": "^18 || ^19", + "react-dom": "^18 || ^19", + "svelte": "^4.0.0 || ^5.0.0", + "vue": "^3.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "svelte": { + "optional": true + }, + "vue": { + "optional": true + } + } + }, + "node_modules/@langchain/langgraph-sdk/node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "license": "MIT" + }, + "node_modules/@langchain/langgraph-sdk/node_modules/p-queue": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-9.3.0.tgz", + "integrity": "sha512-7NED7xhQ74Ngp4JP/2e0VZHp7vSWfJfqeiR92jPgxsz6m0Se4P03YoTKa9dDXyZ3r6P616gUXttrB6nnHYKang==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^5.0.4", + "p-timeout": "^7.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@langchain/langgraph-sdk/node_modules/p-timeout": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-7.0.1.tgz", + "integrity": "sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@langchain/langgraph-sdk/node_modules/uuid": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.2.tgz", + "integrity": "sha512-vzi9uRZ926x4XV73S/4qQaTwPXM2JBj6/6lI/byHH1jOpCzb0zDbfytgA9LcN/hzb2l7WQSQnxITOVx5un/wGw==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist-node/bin/uuid" + } + }, + "node_modules/@langchain/langgraph/node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "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).", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@langchain/protocol": { + "version": "0.0.15", + "resolved": "https://registry.npmjs.org/@langchain/protocol/-/protocol-0.0.15.tgz", + "integrity": "sha512-MllvbpMjqHevUm+v94M422mH7XKN+wGCvJRBVROTWBotEDOATYB4Ktk2UheYP859y9o2LlhtPek5t1T9eyfAbQ==", + "license": "MIT" + }, + "node_modules/@lit-labs/react": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@lit-labs/react/-/react-2.1.3.tgz", + "integrity": "sha512-OD9h2JynerBQUMNzb563jiVpxfvPF0HjQkKY2mx0lpVYvD7F+rtJpOGz6ek+6ufMidV3i+MPT9SX62OKWHFrQg==", + "license": "BSD-3-Clause", + "dependencies": { + "@lit/react": "^1.0.3" + } + }, + "node_modules/@lit-labs/ssr-dom-shim": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.6.0.tgz", + "integrity": "sha512-VHb0ALPMTlgKjM6yIxxoQNnpKyUKLD04VzeQdsiXkMqkvYlAHxq9glGLmgbb889/1GsohSOAjvQYoiBppXFqrQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@lit/react": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@lit/react/-/react-1.0.8.tgz", + "integrity": "sha512-p2+YcF+JE67SRX3mMlJ1TKCSTsgyOVdAwd/nxp3NuV1+Cb6MWALbN6nT7Ld4tpmYofcE5kcaSY1YBB9erY+6fw==", + "license": "BSD-3-Clause", + "peerDependencies": { + "@types/react": "17 || 18 || 19" + } + }, + "node_modules/@lit/reactive-element": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.1.2.tgz", + "integrity": "sha512-pbCDiVMnne1lYUIaYNN5wrwQXDtHaYtg7YEFPeW+hws6U47WeFvISGUWekPGKWOP1ygrs0ef0o1VJMk1exos5A==", + "license": "BSD-3-Clause", + "dependencies": { + "@lit-labs/ssr-dom-shim": "^1.5.0" + } + }, + "node_modules/@lukeed/csprng": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@lukeed/csprng/-/csprng-1.1.0.tgz", + "integrity": "sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@lukeed/uuid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@lukeed/uuid/-/uuid-2.0.1.tgz", + "integrity": "sha512-qC72D4+CDdjGqJvkFMMEAtancHUQ7/d/tAiHf64z8MopFDmcrtbcJuerDtFceuAfQJ2pDSfCKCtbqoGBNnwg0w==", + "license": "MIT", + "dependencies": { + "@lukeed/csprng": "^1.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@mermaid-js/parser": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-1.1.1.tgz", + "integrity": "sha512-VuHdsYMK1bT6X2JbcAaWAhugTRvRBRyuZgd+c22swUeI9g/ntaxF7CY7dYarhZovofCbUNO0G7JesfmNtjYOCw==", + "license": "MIT", + "dependencies": { + "@chevrotain/types": "~11.1.1" + } + }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", + "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", + "license": "MIT", + "dependencies": { + "@hono/node-server": "^1.19.9", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", + "json-schema-typed": "^8.0.2", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/body-parser": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/content-disposition": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/type-is": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", + "license": "MIT", + "dependencies": { + "content-type": "^2.0.0", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/type-is/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@next/env": { + "version": "15.5.18", + "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.18.tgz", + "integrity": "sha512-hAV85Ckd9QR6RvH04MEKwsfLTksvFpO47j9xwtoIuvuPnlwecpSi+uZTtm8HirVbtlI2Fnz//xpcSTjFdyJk+g==", + "license": "MIT" + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "15.5.18", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.18.tgz", + "integrity": "sha512-w0WvQf1n+txiwns/9pwIQteCJpZTbxzO2SE0FLcwuD4v0WEh1JPOjdyxWL21XwJsdpx8cFRjyzxzCS/siP7HcQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "15.5.18", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.18.tgz", + "integrity": "sha512-znn71QmDuxm+BOaglihMZfvyySMnNljkVIY5Z2TCssBmm+WqL6c19VhtH5ktFkHa8EZ2bnTUpcNcmNSQsg67og==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "15.5.18", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.18.tgz", + "integrity": "sha512-yPPe5MNL+igZUa+OsqQJisqSfh6oarIuA1Q0BDxljGJhRQyZeP+WRHh7rs/jZUGMh5aY0YdIjXZG0VohkKkUdw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "15.5.18", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.18.tgz", + "integrity": "sha512-glaCczEWIrHsokFZ3pP08U4BpKxwIdnT+txdOM32OBgpL9Yw4aqx8NejmgtZQZOdstQ5f0L3CasIZudzCuD+nw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "15.5.18", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.18.tgz", + "integrity": "sha512-oUfg2EgJmU3R0OCOWiokGFUTvZiPfXtriXiuF3YNxRoROCdgvTedHIzYoeKH34gsZxS/V7mHbfq2hpAHwhH1/A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "15.5.18", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.18.tgz", + "integrity": "sha512-JLxSP3KTd9iu/bvUMQxH7RJo9xKSHf55/6RPE4a6FTSZygGn7uvZbCej0AHXydwkggQGSD9UddSjwv6Xz5ESfA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "15.5.18", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.18.tgz", + "integrity": "sha512-ir1v7enP52K2HNz3tQQvwF+x7VNxBk1ciiZ18WBPvxf4C59IqdfmHPJYK3vH7rSxpuCVw/8C712wTXNAtEp+NA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "15.5.18", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.18.tgz", + "integrity": "sha512-LIu5me6QTANCd25E7I5uIEfvgQ06RK7tvHAbYo3zCb3VpxQEPvMcSpd87NwUABDT6MbGPdEGR5VRiK4PPTJhQg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nodable/entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-nyT7T3nbMyBI/lvr6L5TyWbFJAI9FTgVRakNoBqCD+PmID8DzFrrNdLLtHMwMszOtqZa8PAOV24ZqDnQrhQINA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/nodable" + } + ], + "license": "MIT" + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@opentelemetry/api": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.1.tgz", + "integrity": "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==", + "license": "Apache-2.0", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/api-logs": { + "version": "0.217.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.217.0.tgz", + "integrity": "sha512-Cdq0jW2lknrNfrAm92MyEAvpe2cRsKjdnQLHUL6xRA4IVUnsWx6P65E7NcUO0Y+L4w1Aee5iV8FvjSwd+lrs9A==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.3.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/configuration": { + "version": "0.217.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/configuration/-/configuration-0.217.0.tgz", + "integrity": "sha512-xCtrYOhBqdy6ZOMfe0Oa73ZKF+2LMhoOv4L5vmwAHVvOXUg+V3fvKuEIr9ZyD0Ow+vxllEjWO6PV1wd0DOtyvw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "yaml": "^2.0.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.9.0" + } + }, + "node_modules/@opentelemetry/context-async-hooks": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.7.1.tgz", + "integrity": "sha512-OPFBYuXEn1E4ja3Y6eeA7O+ZnLBNcXTV5Cgsn1VaqBZ6hC5FnpZPLBNme1LJY8ZtF4aOujPKFoeWN4ik487KuQ==", + "license": "Apache-2.0", + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/core": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.7.1.tgz", + "integrity": "sha512-QAqIj32AtK6+pEVNG7EOVxHdE06RP+FM5qpiEJ4RtDcFIqKUZHYhl7/7UY5efhwmwNAg7j8QbJVBLxMerc0+gw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-grpc": { + "version": "0.217.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-grpc/-/exporter-logs-otlp-grpc-0.217.0.tgz", + "integrity": "sha512-vC5S0Dc+noxD86CVtNu1+awCHPA5Kewi1Sg23ps+9lh4YifwsKXh3pe4XTNEKtUJiAcjpJ5dqStGakLbrSE+YQ==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/grpc-js": "^1.14.3", + "@opentelemetry/core": "2.7.1", + "@opentelemetry/otlp-exporter-base": "0.217.0", + "@opentelemetry/otlp-grpc-exporter-base": "0.217.0", + "@opentelemetry/otlp-transformer": "0.217.0", + "@opentelemetry/sdk-logs": "0.217.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-http": { + "version": "0.217.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-http/-/exporter-logs-otlp-http-0.217.0.tgz", + "integrity": "sha512-KfLAdt1uilVE+3FxbgVnp2ZrzqbIawzcesnRoi+Kh9ckB5Ld5D8btUgoBvwTbdmuNx1j6b132Wsh72azq+pPNQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.217.0", + "@opentelemetry/core": "2.7.1", + "@opentelemetry/otlp-exporter-base": "0.217.0", + "@opentelemetry/otlp-transformer": "0.217.0", + "@opentelemetry/sdk-logs": "0.217.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-proto": { + "version": "0.217.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-proto/-/exporter-logs-otlp-proto-0.217.0.tgz", + "integrity": "sha512-Se0GG/ZO24mQTlQj7zprR4pNI0nKe4lPDPBsuJmi6508b9TlZEuUd3EfyuHk6oJxzL7fGyDFYAbxNigQvRP2ZQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.217.0", + "@opentelemetry/core": "2.7.1", + "@opentelemetry/otlp-exporter-base": "0.217.0", + "@opentelemetry/otlp-transformer": "0.217.0", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/sdk-logs": "0.217.0", + "@opentelemetry/sdk-trace-base": "2.7.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-grpc": { + "version": "0.217.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-grpc/-/exporter-metrics-otlp-grpc-0.217.0.tgz", + "integrity": "sha512-0GpJKnCoVaVA1rKBMVPHziznfOQlXgH72S9ktjBAF1AnAVPzX7vVEBGrhwiSxxHDAiefXk+J8znApsMb/K6Z3w==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/grpc-js": "^1.14.3", + "@opentelemetry/core": "2.7.1", + "@opentelemetry/exporter-metrics-otlp-http": "0.217.0", + "@opentelemetry/otlp-exporter-base": "0.217.0", + "@opentelemetry/otlp-grpc-exporter-base": "0.217.0", + "@opentelemetry/otlp-transformer": "0.217.0", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/sdk-metrics": "2.7.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-http": { + "version": "0.217.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-http/-/exporter-metrics-otlp-http-0.217.0.tgz", + "integrity": "sha512-1zkMzzhiNJdVmLxuwkltqWGw4fOOam47bqRxmuQNjyKJe/9NmY5cIrZ4kiQV7sVGxoOgT0ZvGUfLcjvtpC/b9Q==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/otlp-exporter-base": "0.217.0", + "@opentelemetry/otlp-transformer": "0.217.0", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/sdk-metrics": "2.7.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-proto": { + "version": "0.217.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-proto/-/exporter-metrics-otlp-proto-0.217.0.tgz", + "integrity": "sha512-nfxt/KxVGFkjkO/M+58y1ugHu/dwPtxG4eYq0KApcQ7xk5CHzhdn+IuLZfDSvNDrJ3Uy5q++Fj/wbK7i8yryfQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/exporter-metrics-otlp-http": "0.217.0", + "@opentelemetry/otlp-exporter-base": "0.217.0", + "@opentelemetry/otlp-transformer": "0.217.0", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/sdk-metrics": "2.7.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-prometheus": { + "version": "0.217.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-prometheus/-/exporter-prometheus-0.217.0.tgz", + "integrity": "sha512-U9MCXxJu0sBCh5aEkylYRR4xVIL8D1CW6dGwvYXbfFr0qveSorfD0XJchCAWoW6QfAAIcY/yxjf4Dj8OgkHBPw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/sdk-metrics": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-grpc": { + "version": "0.217.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-grpc/-/exporter-trace-otlp-grpc-0.217.0.tgz", + "integrity": "sha512-fPZs2fw7veLH3pEKu8vSepUa2fQpAE2P7al6qU10aH9GrEJJ8YaPgsd5xON7by5rbcEVS71FOU2aWyK6nzB7VQ==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/grpc-js": "^1.14.3", + "@opentelemetry/core": "2.7.1", + "@opentelemetry/otlp-exporter-base": "0.217.0", + "@opentelemetry/otlp-grpc-exporter-base": "0.217.0", + "@opentelemetry/otlp-transformer": "0.217.0", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/sdk-trace-base": "2.7.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-http": { + "version": "0.217.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.217.0.tgz", + "integrity": "sha512-38YQoqtYjglz2GV94LGUN/djLvxtvGIQO68o6qAFPVshjmwSdX1F2i0c7vn3lEl1L5B/YqjB/bgKXaVx7KO+RQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/otlp-exporter-base": "0.217.0", + "@opentelemetry/otlp-transformer": "0.217.0", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/sdk-trace-base": "2.7.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-proto": { + "version": "0.217.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-proto/-/exporter-trace-otlp-proto-0.217.0.tgz", + "integrity": "sha512-nPV8gKHUiSuTZpQcnZU3/pBlK7crSyEGpZuh5MtWySB0vv6NNG0QvvfKitQt+Fc2Mc6qfyU54KlZcurwoTbrVg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/otlp-exporter-base": "0.217.0", + "@opentelemetry/otlp-transformer": "0.217.0", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/sdk-trace-base": "2.7.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-zipkin": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-zipkin/-/exporter-zipkin-2.7.1.tgz", + "integrity": "sha512-mfsD9bKAxcKrh5+y08TPodvClBO0CznBE3p79YAGnO81WI4LrdsGA65T53e4iTSbCalW4WaUpkbeJcbpyIUHfg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/sdk-trace-base": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.0" + } + }, + "node_modules/@opentelemetry/instrumentation": { + "version": "0.217.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.217.0.tgz", + "integrity": "sha512-24ucQMjz7Y34Kw3trbxL2ZrssbtgWnR+Clpaa+YdeWuuyH3Cvk23Q03PcQvqiZrDvt8AmQmjgg9v6Y9PHoxG7w==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.217.0", + "import-in-the-middle": "^3.0.0", + "require-in-the-middle": "^8.0.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-http": { + "version": "0.217.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-http/-/instrumentation-http-0.217.0.tgz", + "integrity": "sha512-B88Y7k5A9a60pHUboFoeJlgVwXq2T0rsZKj6dTwzSMKSOsNXR4Jz5ovwprVn3kHLAZrkyLEjQtBJ34DYHs1U4Q==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/instrumentation": "0.217.0", + "@opentelemetry/semantic-conventions": "^1.29.0", + "forwarded-parse": "2.1.2" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/otlp-exporter-base": { + "version": "0.217.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.217.0.tgz", + "integrity": "sha512-eYfqnB3UhKu/5frhd1R6+FprKygbhkomuaceMXDyzxbfXB9tKgZOVmjaJ02CkLA6Tdzumxl+e2H+vo2a8jiMPQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/otlp-transformer": "0.217.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/otlp-grpc-exporter-base": { + "version": "0.217.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-grpc-exporter-base/-/otlp-grpc-exporter-base-0.217.0.tgz", + "integrity": "sha512-7RTAdZuOsCDnsyqTCG4+bDzrfnsWdzkRs7z0AVi/V3tEQx0oKeyc+OuRWYxnRsmaJXgxcmB8vb/lfxn58Dj6Ag==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/grpc-js": "^1.14.3", + "@opentelemetry/core": "2.7.1", + "@opentelemetry/otlp-exporter-base": "0.217.0", + "@opentelemetry/otlp-transformer": "0.217.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/otlp-transformer": { + "version": "0.217.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.217.0.tgz", + "integrity": "sha512-MKK8UHKFUOGAvbZRWh90MhwHG+Fxm6OROBdjKPCF+HQobjuJ/Kuf8Chs8CR45X1aqotxrMj7OxTdsXe8sXuGVA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.217.0", + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/sdk-logs": "0.217.0", + "@opentelemetry/sdk-metrics": "2.7.1", + "@opentelemetry/sdk-trace-base": "2.7.1", + "protobufjs": "8.0.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/propagator-b3": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-b3/-/propagator-b3-2.7.1.tgz", + "integrity": "sha512-RJid6E2CKyeGfKBzXKF21ejabGMHypFkPAh3qZ+NvI+SGjuIye79t3PmiqcDgtRzdKH6ynXzbfslQ8DfpRUg2A==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/propagator-jaeger": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-jaeger/-/propagator-jaeger-2.7.1.tgz", + "integrity": "sha512-KMjVBHzP4N60bOzxja76M1F1hZZ43lGPga5ix+mkv9+kk1nx9SbkxSvJsMbuVUxdPQmsPTqGShmhN8ulrMOg6Q==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/resources": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.7.1.tgz", + "integrity": "sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-logs": { + "version": "0.217.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.217.0.tgz", + "integrity": "sha512-BB+PcHItcZDL63dPMW+mJvwN9rk37wuIDjRxbVlg6pPDvDR/7GL7UJHbGsllgoggOoTimsKgENaWPoGch/oE1A==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.217.0", + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.4.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-metrics": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.7.1.tgz", + "integrity": "sha512-MpDJdkiFDs3Pm1RHO3KByuZbuBdJEXEAkiC0+yJdsZGVCdf1RpHR6n+LHDcS7ffmfrt5kVCzJSCfm4z2C7v0uQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-node": { + "version": "0.217.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-node/-/sdk-node-0.217.0.tgz", + "integrity": "sha512-K/60pSv42+NQiZKy1pAH18nYDkxltsDV4O3SJ233J0E9raU1ksyL9gsKuS8p30bYBb4AMPCfDuutHQaHYpcv0Q==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.217.0", + "@opentelemetry/configuration": "0.217.0", + "@opentelemetry/context-async-hooks": "2.7.1", + "@opentelemetry/core": "2.7.1", + "@opentelemetry/exporter-logs-otlp-grpc": "0.217.0", + "@opentelemetry/exporter-logs-otlp-http": "0.217.0", + "@opentelemetry/exporter-logs-otlp-proto": "0.217.0", + "@opentelemetry/exporter-metrics-otlp-grpc": "0.217.0", + "@opentelemetry/exporter-metrics-otlp-http": "0.217.0", + "@opentelemetry/exporter-metrics-otlp-proto": "0.217.0", + "@opentelemetry/exporter-prometheus": "0.217.0", + "@opentelemetry/exporter-trace-otlp-grpc": "0.217.0", + "@opentelemetry/exporter-trace-otlp-http": "0.217.0", + "@opentelemetry/exporter-trace-otlp-proto": "0.217.0", + "@opentelemetry/exporter-zipkin": "2.7.1", + "@opentelemetry/instrumentation": "0.217.0", + "@opentelemetry/otlp-exporter-base": "0.217.0", + "@opentelemetry/propagator-b3": "2.7.1", + "@opentelemetry/propagator-jaeger": "2.7.1", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/sdk-logs": "0.217.0", + "@opentelemetry/sdk-metrics": "2.7.1", + "@opentelemetry/sdk-trace-base": "2.7.1", + "@opentelemetry/sdk-trace-node": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.7.1.tgz", + "integrity": "sha512-NAYIlsF8MPUsKqJMiDQJTMPOmlbawC1Iz/omMLygZ1C9am8fTKYjTaI+OZM+WTY3t3Glo0wnOg/6/pac6RGPPw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-node": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-node/-/sdk-trace-node-2.7.1.tgz", + "integrity": "sha512-pCpQxU68lV+I9s9svqMyVu5iHdDDUnqUpSxqwyCU8A9ejEsSnMPCbearwsUO4yk08ZJzAIUCFuReMdVQvHrdvg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/context-async-hooks": "2.7.1", + "@opentelemetry/core": "2.7.1", + "@opentelemetry/sdk-trace-base": "2.7.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/semantic-conventions": { + "version": "1.41.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.41.1.tgz", + "integrity": "sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/@pinojs/redact": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", + "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", + "license": "MIT" + }, + "node_modules/@preact/signals-core": { + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/@preact/signals-core/-/signals-core-1.14.2.tgz", + "integrity": "sha512-RZHdBj9ZF4n40Rp4jS052EHHjBWf96P9oNdXPfhQTovCuWY9iQn3Gq+gOTJSgBO9A/JBuPfMOWsSX/lIU9Pc/A==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/@protobuf-ts/protoc": { + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/@protobuf-ts/protoc/-/protoc-2.11.1.tgz", + "integrity": "sha512-mUZJaV0daGO6HUX90o/atzQ6A7bbN2RSuHtdwo8SSF2Qoe3zHwa4IHyCN1evftTeHfLmdz+45qo47sL+5P8nyg==", + "license": "Apache-2.0", + "bin": { + "protoc": "protoc.js" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", + "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz", + "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz", + "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.2.tgz", + "integrity": "sha512-pa0vFRuws4wkvaXKK1uXZMAwAX4/t8ANaJo45iw/oQHNQ9q5xUzwgFmVJGXiga2BeN+zpX7Vf9vmsiIa2J+MUw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz", + "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==", + "license": "BSD-3-Clause" + }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", + "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-arrow": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz", + "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", + "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-direction": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", + "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", + "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.16.tgz", + "integrity": "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-menu": "2.1.16", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz", + "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", + "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", + "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.16.tgz", + "integrity": "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popper": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz", + "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-rect": "1.1.1", + "@radix-ui/react-use-size": "1.1.1", + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", + "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", + "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", + "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz", + "integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.8.tgz", + "integrity": "sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-visually-hidden": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", + "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", + "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", + "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz", + "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz", + "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==", + "license": "MIT", + "dependencies": { + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-size": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz", + "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-visually-hidden": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz", + "integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz", + "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", + "license": "MIT" + }, + "node_modules/@remix-run/node-fetch-server": { + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/@remix-run/node-fetch-server/-/node-fetch-server-0.13.3.tgz", + "integrity": "sha512-UfjOXed/DQteaM5VyTfqTeGpHwyL2J5aoRGY6cydip4tt1ehNNeSwuXCC7AEGE0RWBs/7bgKxYkL/B/+UDe4AA==", + "license": "MIT" + }, + "node_modules/@repeaterjs/repeater": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@repeaterjs/repeater/-/repeater-3.0.6.tgz", + "integrity": "sha512-Javneu5lsuhwNCryN+pXH93VPQ8g0dBX7wItHFgYiwQmzE1sVdg5tWHiOgHywzL2W21XQopa7IwIEnNbmeUJYA==", + "license": "MIT" + }, + "node_modules/@scarf/scarf": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", + "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==", + "hasInstallScript": true, + "license": "Apache-2.0" + }, + "node_modules/@segment/analytics-core": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/@segment/analytics-core/-/analytics-core-1.8.2.tgz", + "integrity": "sha512-5FDy6l8chpzUfJcNlIcyqYQq4+JTUynlVoCeCUuVz+l+6W0PXg+ljKp34R4yLVCcY5VVZohuW+HH0VLWdwYVAg==", + "license": "MIT", + "dependencies": { + "@lukeed/uuid": "^2.0.0", + "@segment/analytics-generic-utils": "1.2.0", + "dset": "^3.1.4", + "tslib": "^2.4.1" + } + }, + "node_modules/@segment/analytics-generic-utils": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@segment/analytics-generic-utils/-/analytics-generic-utils-1.2.0.tgz", + "integrity": "sha512-DfnW6mW3YQOLlDQQdR89k4EqfHb0g/3XvBXkovH1FstUN93eL1kfW9CsDcVQyH3bAC5ZsFyjA/o/1Q2j0QeoWw==", + "license": "MIT", + "dependencies": { + "tslib": "^2.4.1" + } + }, + "node_modules/@segment/analytics-node": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@segment/analytics-node/-/analytics-node-2.3.0.tgz", + "integrity": "sha512-fOXLL8uY0uAWw/sTLmezze80hj8YGgXXlAfvSS6TUmivk4D/SP0C0sxnbpFdkUzWg2zT64qWIZj26afEtSnxUA==", + "license": "MIT", + "dependencies": { + "@lukeed/uuid": "^2.0.0", + "@segment/analytics-core": "1.8.2", + "@segment/analytics-generic-utils": "1.2.0", + "buffer": "^6.0.3", + "jose": "^5.1.0", + "node-fetch": "^2.6.7", + "tslib": "^2.4.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@segment/analytics-node/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/@segment/analytics-node/node_modules/jose": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/jose/-/jose-5.10.0.tgz", + "integrity": "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/@shikijs/core": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.23.0.tgz", + "integrity": "sha512-NSWQz0riNb67xthdm5br6lAkvpDJRTgB36fxlo37ZzM2yq0PQFFzbd8psqC2XMPgCzo1fW6cVi18+ArJ44wqgA==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.23.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4", + "hast-util-to-html": "^9.0.5" + } + }, + "node_modules/@shikijs/core/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@shikijs/engine-javascript": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-3.23.0.tgz", + "integrity": "sha512-aHt9eiGFobmWR5uqJUViySI1bHMqrAgamWE1TYSUoftkAeCCAiGawPMwM+VCadylQtF4V3VNOZ5LmfItH5f3yA==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.23.0", + "@shikijs/vscode-textmate": "^10.0.2", + "oniguruma-to-es": "^4.3.4" + } + }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.23.0.tgz", + "integrity": "sha512-1nWINwKXxKKLqPibT5f4pAFLej9oZzQTsby8942OTlsJzOBZ0MWKiwzMsd+jhzu8YPCHAswGnnN1YtQfirL35g==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.23.0", + "@shikijs/vscode-textmate": "^10.0.2" + } + }, + "node_modules/@shikijs/langs": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.23.0.tgz", + "integrity": "sha512-2Ep4W3Re5aB1/62RSYQInK9mM3HsLeB91cHqznAJMuylqjzNVAVCMnNWRHFtcNHXsoNRayP9z1qj4Sq3nMqYXg==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.23.0" + } + }, + "node_modules/@shikijs/themes": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.23.0.tgz", + "integrity": "sha512-5qySYa1ZgAT18HR/ypENL9cUSGOeI2x+4IvYJu4JgVJdizn6kG4ia5Q1jDEOi7gTbN4RbuYtmHh0W3eccOrjMA==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.23.0" + } + }, + "node_modules/@shikijs/types": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.23.0.tgz", + "integrity": "sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ==", + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/@shikijs/types/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@shikijs/vscode-textmate": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", + "license": "MIT" + }, + "node_modules/@smithy/core": { + "version": "3.24.5", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.24.5.tgz", + "integrity": "sha512-Kt8phUg45M15EjhYAbZ+fFikYneijLu9Liugz8ZsYz2i8j0hzGv27LWKpEHYRfvj+LyCOSijpcR/2i8RouV+cA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^4.14.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.3.5.tgz", + "integrity": "sha512-yiF8xHpdkaTfzLVqFzsP6WvNghEK+qZzLYWFD13L2SsFhbXwBGlxdocKF95qjr7s5lE5NRage+EJFK4mAsx88Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.5", + "@smithy/types": "^4.14.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.4.5.tgz", + "integrity": "sha512-SK3VMeH0fibgdTg2QeB+O4p7Yy/2E5HBOHJeC58FshkDdeuX8lOgO7PfjYfLyPLP1ch55j91cQqKBzDS0mRjSQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.5", + "@smithy/types": "^4.14.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/node-http-handler": { + "version": "4.7.5", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.7.5.tgz", + "integrity": "sha512-3dA9TQ+ybRSZ/m0wnbZhiBy4Dezjgq1Ib/ZZrYTpJDBgpoLLU/SDzZc/g0x0MNAdOJe1wPcM+x2PBRmoOur+Sw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.5", + "@smithy/types": "^4.14.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.4.5.tgz", + "integrity": "sha512-QBJKWGqIknH0dc9LWpfH1mkdokAx6iXYN3UcQ3eY6uIEyScuoQAhfl94ge7ozUy9WgFUdE8xsvwBjaYBbWmPNA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.5", + "@smithy/types": "^4.14.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/types": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.14.2.tgz", + "integrity": "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "license": "MIT" + }, + "node_modules/@swc/helpers": { + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@tanstack/devtools-event-client": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@tanstack/devtools-event-client/-/devtools-event-client-0.4.3.tgz", + "integrity": "sha512-OZI6QyULw0FI0wjgmeYzCIfbgPsOEzwJtCpa69XrfLMtNXLGnz3d/dIabk7frg0TmHo+Ah49w5I4KC7Tufwsvw==", + "license": "MIT", + "bin": { + "intent": "bin/intent.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/pacer": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@tanstack/pacer/-/pacer-0.20.1.tgz", + "integrity": "sha512-ZNQ1bIL6eUXVKdic0tiImvBVkWrg/IoSK6VIacTrO3d3HAGnd70qFJNJagR/YOJIOw4EKGWnodwpYZkN1pWuVQ==", + "license": "MIT", + "dependencies": { + "@tanstack/devtools-event-client": "^0.4.3", + "@tanstack/store": "^0.9.3" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-virtual": { + "version": "3.13.26", + "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.13.26.tgz", + "integrity": "sha512-DosdgjOxCLahkn0o+ilmZYwEjo1glfMGuRT/j3PQ18yr5XqA8N/BCaL9IJ3B5TRl+nnzyK2IOFgAILwzN3a9xQ==", + "license": "MIT", + "dependencies": { + "@tanstack/virtual-core": "3.16.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@tanstack/store": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/@tanstack/store/-/store-0.9.3.tgz", + "integrity": "sha512-8reSzl/qGWGGVKhBoxXPMWzATSbZLZFWhwBAFO9NAyp0TxzfBP0mIrGb8CP8KrQTmvzXlR/vFPPUrHTLBGyFyw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/virtual-core": { + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.16.0.tgz", + "integrity": "sha512-Er2N7q3WOiH6y2JLxsxNX+u2/sLqSsL0bxFgDjuiPiA7vKhZRm+IzcS17vRee3GNXr64UsesA5CAp9yTiIYw9A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@types/d3": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", + "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/d3-axis": "*", + "@types/d3-brush": "*", + "@types/d3-chord": "*", + "@types/d3-color": "*", + "@types/d3-contour": "*", + "@types/d3-delaunay": "*", + "@types/d3-dispatch": "*", + "@types/d3-drag": "*", + "@types/d3-dsv": "*", + "@types/d3-ease": "*", + "@types/d3-fetch": "*", + "@types/d3-force": "*", + "@types/d3-format": "*", + "@types/d3-geo": "*", + "@types/d3-hierarchy": "*", + "@types/d3-interpolate": "*", + "@types/d3-path": "*", + "@types/d3-polygon": "*", + "@types/d3-quadtree": "*", + "@types/d3-random": "*", + "@types/d3-scale": "*", + "@types/d3-scale-chromatic": "*", + "@types/d3-selection": "*", + "@types/d3-shape": "*", + "@types/d3-time": "*", + "@types/d3-time-format": "*", + "@types/d3-timer": "*", + "@types/d3-transition": "*", + "@types/d3-zoom": "*" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "license": "MIT" + }, + "node_modules/@types/d3-axis": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz", + "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-brush": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz", + "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-chord": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz", + "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-contour": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz", + "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==", + "license": "MIT" + }, + "node_modules/@types/d3-dispatch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz", + "integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==", + "license": "MIT" + }, + "node_modules/@types/d3-drag": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-dsv": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", + "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-fetch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", + "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", + "license": "MIT", + "dependencies": { + "@types/d3-dsv": "*" + } + }, + "node_modules/@types/d3-force": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz", + "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==", + "license": "MIT" + }, + "node_modules/@types/d3-format": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", + "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==", + "license": "MIT" + }, + "node_modules/@types/d3-geo": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-hierarchy": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", + "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-polygon": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", + "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==", + "license": "MIT" + }, + "node_modules/@types/d3-quadtree": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", + "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==", + "license": "MIT" + }, + "node_modules/@types/d3-random": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz", + "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==", + "license": "MIT" + }, + "node_modules/@types/d3-selection": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", + "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==", + "license": "MIT" + }, + "node_modules/@types/d3-shape": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz", + "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-time-format": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", + "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, + "node_modules/@types/d3-transition": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", + "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", + "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", + "license": "MIT", + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, + "node_modules/@types/debug": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "license": "MIT" + }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "license": "MIT" + }, + "node_modules/@types/hast": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz", + "integrity": "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "license": "MIT" + }, + "node_modules/@types/katex": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.8.tgz", + "integrity": "sha512-trgaNyfU+Xh2Tc+ABIb44a5AYUpicB3uwirOioeOkNPPbmgRNtcWyDeeFRzjPZENO9Vq8gvVqfhaaXWLlevVwg==", + "license": "MIT" + }, + "node_modules/@types/mdast": { + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.15.tgz", + "integrity": "sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.19.19", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.19.tgz", + "integrity": "sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/prismjs": { + "version": "1.26.6", + "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.6.tgz", + "integrity": "sha512-vqlvI7qlMvcCBbVe0AKAb4f97//Hy0EBTaiW8AalRnG/xAN5zOiWWyrNqNXeq8+KAuvRewjCVY1+IPxk4RdNYw==", + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.29", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.29.tgz", + "integrity": "sha512-ch0qJdr2JY0r04NXSprbK6TXOgnaJ1Tz23fm5W+z0/CBah6BSBc3n96h7K9GOtwh0HrilNWHIBzE1Ko4Dcw/Wg==", + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@types/react-syntax-highlighter": { + "version": "15.5.13", + "resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.13.tgz", + "integrity": "sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA==", + "license": "MIT", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==", + "license": "MIT" + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT" + }, + "node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, + "node_modules/@types/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", + "license": "MIT" + }, + "node_modules/@types/validator": { + "version": "13.15.10", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.15.10.tgz", + "integrity": "sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==", + "license": "MIT" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.1.tgz", + "integrity": "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==", + "license": "ISC" + }, + "node_modules/@upsetjs/venn.js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@upsetjs/venn.js/-/venn.js-2.0.0.tgz", + "integrity": "sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw==", + "license": "MIT", + "optionalDependencies": { + "d3-selection": "^3.0.0", + "d3-transition": "^3.0.1" + } + }, + "node_modules/@urql/core": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@urql/core/-/core-5.2.0.tgz", + "integrity": "sha512-/n0ieD0mvvDnVAXEQgX/7qJiVcvYvNkOHeBvkwtylfjydar123caCXcl58PXFY11oU1oquJocVXHxLAbtv4x1A==", + "license": "MIT", + "dependencies": { + "@0no-co/graphql.web": "^1.0.13", + "wonka": "^6.3.2" + } + }, + "node_modules/@vercel/oidc": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@vercel/oidc/-/oidc-3.2.0.tgz", + "integrity": "sha512-UycprH3T6n3jH0k44NHMa7pnFHGu/N05MjojYr+Mc6I7obkoLIJujSWwin1pCvdy/eOxrI/l3uDLQsmcrOb4ug==", + "license": "Apache-2.0", + "engines": { + "node": ">= 20" + } + }, + "node_modules/@whatwg-node/disposablestack": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@whatwg-node/disposablestack/-/disposablestack-0.0.6.tgz", + "integrity": "sha512-LOtTn+JgJvX8WfBVJtF08TGrdjuFzGJc4mkP8EdDI8ADbvO7kiexYep1o8dwnt0okb0jYclCDXF13xU7Ge4zSw==", + "license": "MIT", + "dependencies": { + "@whatwg-node/promise-helpers": "^1.0.0", + "tslib": "^2.6.3" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@whatwg-node/events": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@whatwg-node/events/-/events-0.1.2.tgz", + "integrity": "sha512-ApcWxkrs1WmEMS2CaLLFUEem/49erT3sxIVjpzU5f6zmVcnijtDSrhoK2zVobOIikZJdH63jdAXOrvjf6eOUNQ==", + "license": "MIT", + "dependencies": { + "tslib": "^2.6.3" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@whatwg-node/fetch": { + "version": "0.10.13", + "resolved": "https://registry.npmjs.org/@whatwg-node/fetch/-/fetch-0.10.13.tgz", + "integrity": "sha512-b4PhJ+zYj4357zwk4TTuF2nEe0vVtOrwdsrNo5hL+u1ojXNhh1FgJ6pg1jzDlwlT4oBdzfSwaBwMCtFCsIWg8Q==", + "license": "MIT", + "dependencies": { + "@whatwg-node/node-fetch": "^0.8.3", + "urlpattern-polyfill": "^10.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@whatwg-node/node-fetch": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.8.5.tgz", + "integrity": "sha512-4xzCl/zphPqlp9tASLVeUhB5+WJHbuWGYpfoC2q1qh5dw0AqZBW7L27V5roxYWijPxj4sspRAAoOH3d2ztaHUQ==", + "license": "MIT", + "dependencies": { + "@fastify/busboy": "^3.1.1", + "@whatwg-node/disposablestack": "^0.0.6", + "@whatwg-node/promise-helpers": "^1.3.2", + "tslib": "^2.6.3" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@whatwg-node/promise-helpers": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@whatwg-node/promise-helpers/-/promise-helpers-1.3.2.tgz", + "integrity": "sha512-Nst5JdK47VIl9UcGwtv2Rcgyn5lWtZ0/mhRQ4G8NN2isxpq2TO30iqHzmwoJycjWuyUfg3GFXqP/gFHXeV57IA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.6.3" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@whatwg-node/server": { + "version": "0.10.18", + "resolved": "https://registry.npmjs.org/@whatwg-node/server/-/server-0.10.18.tgz", + "integrity": "sha512-kMwLlxUbduttIgaPdSkmEarFpP+mSY8FEm+QWMBRJwxOHWkri+cxd8KZHO9EMrB9vgUuz+5WEaCawaL5wGVoXg==", + "license": "MIT", + "dependencies": { + "@envelop/instrumentation": "^1.0.0", + "@whatwg-node/disposablestack": "^0.0.6", + "@whatwg-node/fetch": "^0.10.13", + "@whatwg-node/promise-helpers": "^1.3.2", + "tslib": "^2.6.3" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-attributes": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", + "license": "MIT", + "peerDependencies": { + "acorn": "^8" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/agent-base/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/agent-base/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/ai": { + "version": "6.0.191", + "resolved": "https://registry.npmjs.org/ai/-/ai-6.0.191.tgz", + "integrity": "sha512-zAxvjKebQE7YkSyyNIl0OM7i6/zygnKeF+yNUjD4nWOelYrG+LpDd6RnH6mjySI4zUpZ7o4wbnmAy8jc6u98vQ==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/gateway": "3.0.120", + "@ai-sdk/provider": "3.0.10", + "@ai-sdk/provider-utils": "4.0.27", + "@opentelemetry/api": "^1.9.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/aria-hidden": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", + "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/axios": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.1.tgz", + "integrity": "sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.5", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/body-parser": { + "version": "1.20.5", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz", + "integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.15.1", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/bowser": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", + "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", + "license": "MIT" + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/buffer": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz", + "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==", + "license": "MIT", + "dependencies": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001793", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001793.tgz", + "integrity": "sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/cjs-module-lexer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz", + "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==", + "license": "MIT" + }, + "node_modules/clarinet": { + "version": "0.12.6", + "resolved": "https://registry.npmjs.org/clarinet/-/clarinet-0.12.6.tgz", + "integrity": "sha512-0FR+TrvLbYHLjhzs9oeIbd3yfZmd4u2DzYQEjUTm2dNfh4Y/9RIRWPjsm3aBtrVEpjKI7+lWa4ouqEXoml84mQ==", + "license": "BSD-2-Clause", + "engines": { + "chrome": ">=16.0.912", + "firefox": ">=0.8.0", + "node": ">=0.3.6" + } + }, + "node_modules/class-transformer": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", + "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==", + "license": "MIT" + }, + "node_modules/class-validator": { + "version": "0.14.4", + "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.4.tgz", + "integrity": "sha512-AwNusCCam51q703dW82x95tOqQp6oC9HNUl724KxJJOfnKscI8dOloXFgyez7LbTTKWuRBA37FScqVbJEoq8Yw==", + "license": "MIT", + "dependencies": { + "@types/validator": "^13.15.3", + "libphonenumber-js": "^1.11.1", + "validator": "^13.15.22" + } + }, + "node_modules/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + } + }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "license": "MIT" + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/compare-versions": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.1.tgz", + "integrity": "sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==", + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cose-base": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz", + "integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==", + "license": "MIT", + "dependencies": { + "layout-base": "^1.0.0" + } + }, + "node_modules/cross-inspect": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cross-inspect/-/cross-inspect-1.0.1.tgz", + "integrity": "sha512-Pcw1JTvZLSJH83iiGWt6fRcT+BjZlCDRVwYLbUcHzv/CRpB7r0MlSrGbIyQvVSNyGnbt7G4AXuyCiDR3POvZ1A==", + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/cytoscape": { + "version": "3.33.4", + "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.4.tgz", + "integrity": "sha512-HIN5Pmd9MrX9BkV7tDwnOcEJCSFvCpc8X97h3f508J6I5FsqAY65wKOCvgH2CuP42CaahWaz4tuh32SOOIH7ww==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/cytoscape-cose-bilkent": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz", + "integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==", + "license": "MIT", + "dependencies": { + "cose-base": "^1.0.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, + "node_modules/cytoscape-fcose": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz", + "integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==", + "license": "MIT", + "dependencies": { + "cose-base": "^2.2.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, + "node_modules/cytoscape-fcose/node_modules/cose-base": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-2.2.0.tgz", + "integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==", + "license": "MIT", + "dependencies": { + "layout-base": "^2.0.0" + } + }, + "node_modules/cytoscape-fcose/node_modules/layout-base": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz", + "integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==", + "license": "MIT" + }, + "node_modules/d3": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", + "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", + "license": "ISC", + "dependencies": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "license": "ISC", + "dependencies": { + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "license": "ISC", + "dependencies": { + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "license": "ISC", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "license": "ISC", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/d3-dsv/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "license": "ISC", + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-sankey": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz", + "integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "1 - 2", + "d3-shape": "^1.2.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "license": "BSD-3-Clause", + "dependencies": { + "internmap": "^1.0.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==", + "license": "BSD-3-Clause" + }, + "node_modules/d3-sankey/node_modules/d3-shape": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-path": "1" + } + }, + "node_modules/d3-sankey/node_modules/internmap": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", + "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==", + "license": "ISC" + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/dagre-d3-es": { + "version": "7.0.14", + "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.14.tgz", + "integrity": "sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg==", + "license": "MIT", + "dependencies": { + "d3": "^7.9.0", + "lodash-es": "^4.17.21" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/date-fns": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.3.0.tgz", + "integrity": "sha512-OYcL+3N/jyWbYdFGqoMAhytDgxP9pbYPUUiRCOgn4Fewaadk9l/Wam4Avciiyp2BgkpfQyBV9B+ehnVJych+eQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, + "node_modules/dateformat": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", + "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/dayjs": { + "version": "1.11.21", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.21.tgz", + "integrity": "sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/decode-named-character-reference": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/delaunator": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.1.0.tgz", + "integrity": "sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==", + "license": "ISC", + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", + "license": "MIT" + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/diff": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.2.tgz", + "integrity": "sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dompurify": { + "version": "3.4.7", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.7.tgz", + "integrity": "sha512-2jBxDJY4RR06tQNy4w5FlFH7kfxsQZlufd0sbv+chfHCxeJwrFw2baUDsSwvBISD4K4RDbd0PTfy3uNXsR6siA==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, + "node_modules/dotenv": { + "version": "17.4.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", + "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dset": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.4.tgz", + "integrity": "sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-toolkit": { + "version": "1.47.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.47.0.tgz", + "integrity": "sha512-n1GuoD0WEQZMBk5tttoZSqwgyLx01oqa5XsBmCHwPyNe1S9jPBEmtR2pSgp2kJuWE3ciFZ6yRHmY4pM4C3OOkw==", + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks" + ] + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "license": "MIT" + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.0.tgz", + "integrity": "sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/expand-tilde": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", + "integrity": "sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==", + "license": "MIT", + "dependencies": { + "homedir-polyfill": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/express": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz", + "integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.5", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.15.1", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "8.5.2", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.5.2.tgz", + "integrity": "sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==", + "license": "MIT", + "dependencies": { + "ip-address": "^10.2.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/fast-copy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.2.tgz", + "integrity": "sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-json-patch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-3.1.1.tgz", + "integrity": "sha512-vf6IHUX2SBcA+5/+4883dsIjpBTqmfBjmYiWK1savxQmFk4JfBMLa7ynTYOs1Rolp/T1betJxHiGD3g1Mn8lUQ==", + "license": "MIT" + }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", + "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fast-xml-builder": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.2.0.tgz", + "integrity": "sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "path-expression-matcher": "^1.5.0", + "xml-naming": "^0.1.0" + } + }, + "node_modules/fast-xml-parser": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.7.3.tgz", + "integrity": "sha512-C0AaNuC+mscy6vrAQKAc/rMq+zAPHodfHGZu4sGVehvAQt/JLG1O5zEcYcXSY5zSqr4YVgxsB+pHXTq0i7eDlg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "@nodable/entities": "^2.1.0", + "fast-xml-builder": "^1.1.7", + "path-expression-matcher": "^1.5.0", + "strnum": "^2.2.3" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fault": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/fault/-/fault-1.0.4.tgz", + "integrity": "sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==", + "license": "MIT", + "dependencies": { + "format": "^0.2.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/format": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", + "integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==", + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/forwarded-parse": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/forwarded-parse/-/forwarded-parse-2.1.2.tgz", + "integrity": "sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==", + "license": "MIT" + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gaxios": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.4.tgz", + "integrity": "sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA==", + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/gaxios/node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/gaxios/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/gaxios/node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/gaxios/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/gaxios/node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/gcp-metadata": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz", + "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==", + "license": "Apache-2.0", + "dependencies": { + "gaxios": "^7.0.0", + "google-logging-utils": "^1.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/google-auth-library": { + "version": "10.6.2", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.6.2.tgz", + "integrity": "sha512-e27Z6EThmVNNvtYASwQxose/G57rkRuaRbQyxM2bvYLLX/GqWZ5chWq2EBoUchJbCc57eC9ArzO5wMsEmWftCw==", + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^7.1.4", + "gcp-metadata": "8.1.2", + "google-logging-utils": "1.1.3", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/google-logging-utils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", + "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graphql": { + "version": "16.14.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.14.0.tgz", + "integrity": "sha512-BBvQ/406p+4CZbTpCbVPSxfzrZrbnuWSP1ELYgyS6B+hNeKzgrdB4JczCa5VZUBQrDa9hUngm0KnexY6pJRN5Q==", + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" + } + }, + "node_modules/graphql-query-complexity": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/graphql-query-complexity/-/graphql-query-complexity-0.12.0.tgz", + "integrity": "sha512-fWEyuSL6g/+nSiIRgIipfI6UXTI7bAxrpPlCY1c0+V3pAEUo1ybaKmSBgNr1ed2r+agm1plJww8Loig9y6s2dw==", + "license": "MIT", + "dependencies": { + "lodash.get": "^4.4.2" + }, + "peerDependencies": { + "graphql": "^14.6.0 || ^15.0.0 || ^16.0.0" + } + }, + "node_modules/graphql-scalars": { + "version": "1.25.0", + "resolved": "https://registry.npmjs.org/graphql-scalars/-/graphql-scalars-1.25.0.tgz", + "integrity": "sha512-b0xyXZeRFkne4Eq7NAnL400gStGqG/Sx9VqX0A05nHyEbv57UJnWKsjNnrpVqv5e/8N1MUxkt0wwcRXbiyKcFg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" + } + }, + "node_modules/graphql-yoga": { + "version": "5.21.0", + "resolved": "https://registry.npmjs.org/graphql-yoga/-/graphql-yoga-5.21.0.tgz", + "integrity": "sha512-PS37UoDihx8209RRl1ogttzWevNYDnGvP7beHkwHzUpUdfZTHsVRTVe1ysGXre1EjwUAePbpez302YSrq70Ngw==", + "license": "MIT", + "dependencies": { + "@envelop/core": "^5.5.1", + "@envelop/instrumentation": "^1.0.0", + "@graphql-tools/executor": "^1.5.0", + "@graphql-tools/schema": "^10.0.11", + "@graphql-tools/utils": "^10.11.0", + "@graphql-yoga/logger": "^2.0.1", + "@graphql-yoga/subscription": "^5.0.5", + "@whatwg-node/fetch": "^0.10.6", + "@whatwg-node/promise-helpers": "^1.3.2", + "@whatwg-node/server": "^0.10.14", + "lru-cache": "^10.0.0", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "graphql": "^15.2.0 || ^16.0.0" + } + }, + "node_modules/hachure-fill": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/hachure-fill/-/hachure-fill-0.5.2.tgz", + "integrity": "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==", + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hast": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hast/-/hast-1.0.0.tgz", + "integrity": "sha512-vFUqlRV5C+xqP76Wwq2SrM0kipnmpxJm7OfvVXpB35Fp+Fn4MV+ozr+JZr5qFvyR1q/U+Foim2x+3P+x9S1PLA==", + "deprecated": "Renamed to rehype", + "license": "MIT" + }, + "node_modules/hast-util-from-dom": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/hast-util-from-dom/-/hast-util-from-dom-5.0.1.tgz", + "integrity": "sha512-N+LqofjR2zuzTjCPzyDUdSshy4Ma6li7p/c3pA78uTwzFgENbgbUrm2ugwsOdcjI1muO+o6Dgzp9p8WHtn/39Q==", + "license": "ISC", + "dependencies": { + "@types/hast": "^3.0.0", + "hastscript": "^9.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-dom/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/hast-util-from-html": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz", + "integrity": "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "devlop": "^1.1.0", + "hast-util-from-parse5": "^8.0.0", + "parse5": "^7.0.0", + "vfile": "^6.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-html-isomorphic": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hast-util-from-html-isomorphic/-/hast-util-from-html-isomorphic-2.0.0.tgz", + "integrity": "sha512-zJfpXq44yff2hmE0XmwEOzdWin5xwH+QIhMLOScpX91e/NSGPsAzNCvLQDIEPyO2TXi+lBmU6hjLIhV8MwP2kw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-from-dom": "^5.0.0", + "hast-util-from-html": "^2.0.0", + "unist-util-remove-position": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-html-isomorphic/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/hast-util-from-html/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/hast-util-from-html/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/hast-util-from-html/node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-parse5": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz", + "integrity": "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "hastscript": "^9.0.0", + "property-information": "^7.0.0", + "vfile": "^6.0.0", + "vfile-location": "^5.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-parse5/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/hast-util-from-parse5/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/hast-util-from-parse5/node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/hast-util-from-parse5/node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-is-element": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz", + "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-is-element/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/hast-util-parse-selector": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", + "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-parse-selector/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/hast-util-raw": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.1.0.tgz", + "integrity": "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "@ungap/structured-clone": "^1.0.0", + "hast-util-from-parse5": "^8.0.0", + "hast-util-to-parse5": "^8.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "parse5": "^7.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/hast-util-raw/node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/hast-util-raw/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/hast-util-raw/node_modules/mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/hast-util-raw/node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/hast-util-raw/node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/hast-util-raw/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/hast-util-raw/node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/hast-util-raw/node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw/node_modules/unist-util-visit": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw/node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-sanitize": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/hast-util-sanitize/-/hast-util-sanitize-5.0.2.tgz", + "integrity": "sha512-3yTWghByc50aGS7JlGhk61SPenfE/p1oaFeNwkOOyrscaOkMGrcW9+Cy/QAIOBpZxP1yqDIzFMR0+Np0i0+usg==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@ungap/structured-clone": "^1.0.0", + "unist-util-position": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-sanitize/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/hast-util-sanitize/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/hast-util-sanitize/node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-html": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", + "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-html/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/hast-util-to-html/node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/hast-util-to-html/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/hast-util-to-html/node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-html/node_modules/mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-html/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/hast-util-to-html/node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/hast-util-to-html/node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/hast-util-to-html/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/hast-util-to-html/node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/hast-util-to-html/node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/hast-util-to-html/node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-html/node_modules/unist-util-visit": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-html/node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", + "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-jsx-runtime/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/hast-util-to-jsx-runtime/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/hast-util-to-jsx-runtime/node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-jsx-runtime/node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/hast-util-to-jsx-runtime/node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-parse5": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.1.tgz", + "integrity": "sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-parse5/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/hast-util-to-parse5/node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/hast-util-to-text": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz", + "integrity": "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "hast-util-is-element": "^3.0.0", + "unist-util-find-after": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-text/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/hast-util-to-text/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/hast-util-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-2.0.1.tgz", + "integrity": "sha512-nAxA0v8+vXSBDt3AnRUNjyRIQ0rD+ntpbAp4LnPkumc5M9yUbSMa4XDU9Q6etY4f1Wp4bNgvc1yjiZtsTTrSng==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz", + "integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^4.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/hastscript/node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/help-me": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz", + "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==", + "license": "MIT" + }, + "node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, + "node_modules/highlightjs-vue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/highlightjs-vue/-/highlightjs-vue-1.0.0.tgz", + "integrity": "sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==", + "license": "CC0-1.0" + }, + "node_modules/homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "license": "MIT", + "dependencies": { + "parse-passwd": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/hono": { + "version": "4.12.23", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.23.tgz", + "integrity": "sha512-eIaZ9qDgu7XV0pxOCrg7/WhnQ6Ivm22UcxhXx/A3dcbqbbYgBEkc6e/J/s7j2tS96zoB0S9VBdLwQNCWwUo4LA==", + "license": "MIT", + "engines": { + "node": ">=16.9.0" + } + }, + "node_modules/html-url-attributes": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", + "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/https-proxy-agent/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/import-in-the-middle": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-3.0.1.tgz", + "integrity": "sha512-pYkiyXVL2Mf3pozdlDGV6NAObxQx13Ae8knZk1UJRJ6uRW/ZRmTGHlQYtrsSl7ubuE5F8CD1z+s1n4RHNuTtuA==", + "license": "Apache-2.0", + "dependencies": { + "acorn": "^8.15.0", + "acorn-import-attributes": "^1.9.5", + "cjs-module-lexer": "^2.2.0", + "module-details-from-path": "^1.0.4" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/import-meta-resolve": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", + "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/inline-style-parser": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz", + "integrity": "sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==", + "license": "MIT" + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/ip-address": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", + "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-network-error": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.2.tgz", + "integrity": "sha512-PhBY86zaxNZUuWP6h13Vu5oFe0XY6/UlKzQnYFELzGVHygP3MxmvTfYSG7GN3aIab/iWudSMgjSnG9Dq+nHrgA==", + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/isomorphic-ws": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz", + "integrity": "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==", + "license": "MIT", + "peerDependencies": { + "ws": "*" + } + }, + "node_modules/jose": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", + "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/joycon": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", + "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/js-tiktoken": { + "version": "1.0.21", + "resolved": "https://registry.npmjs.org/js-tiktoken/-/js-tiktoken-1.0.21.tgz", + "integrity": "sha512-biOj/6M5qdgx5TKjDnFT1ymSpM5tbd3ylwDtrQvFQSu0Z7bBYko2dF+W/aUkXUPuk6IVpRxk/3Q2sHOzGlS36g==", + "license": "MIT", + "dependencies": { + "base64-js": "^1.5.1" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "license": "(AFL-2.1 OR BSD-3-Clause)" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "license": "BSD-2-Clause" + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/katex": { + "version": "0.16.47", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.47.tgz", + "integrity": "sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==", + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "license": "MIT", + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, + "node_modules/khroma": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz", + "integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==" + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/langchain": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/langchain/-/langchain-1.4.2.tgz", + "integrity": "sha512-SLGipy0r4nqQD0aiUOBYLMeGFfB/QiYnMndfZ8sGN89vXDCIXbYqcE7G/4QDDX3nZsM7/emQpoScmlxEX6sDnQ==", + "license": "MIT", + "dependencies": { + "@langchain/langgraph": "^1.3.2", + "@langchain/langgraph-checkpoint": "^1.0.1", + "langsmith": ">=0.5.0 <1.0.0", + "zod": "^3.25.76 || ^4" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@langchain/core": "^1.1.48" + } + }, + "node_modules/langsmith": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/langsmith/-/langsmith-0.7.2.tgz", + "integrity": "sha512-3dZUwQDJluxi2ih5eIygFODtlrQKrs3Tua0Ck3l+DAUkSGFkB1Dc0JPqkGbqiVSN+TQDElnkev/ydAOAz6jndA==", + "license": "MIT", + "dependencies": { + "p-queue": "6.6.2" + }, + "peerDependencies": { + "@opentelemetry/api": "*", + "@opentelemetry/exporter-trace-otlp-proto": "*", + "@opentelemetry/sdk-trace-base": "*", + "openai": "*", + "ws": ">=7" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@opentelemetry/exporter-trace-otlp-proto": { + "optional": true + }, + "@opentelemetry/sdk-trace-base": { + "optional": true + }, + "openai": { + "optional": true + }, + "ws": { + "optional": true + } + } + }, + "node_modules/layout-base": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz", + "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==", + "license": "MIT" + }, + "node_modules/libphonenumber-js": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.13.3.tgz", + "integrity": "sha512-xMkdAMqcyG7iN2WZZmGIfWbYxW4orRkny+0/AXIbwL0xll2zkDX0Vzo/BXFa6+7mh2UvJl9MbcTtHk0YXkFtBA==", + "license": "MIT" + }, + "node_modules/lit": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/lit/-/lit-3.3.3.tgz", + "integrity": "sha512-fycuvZg/hkpozL00lm1pEJH5nN/lr9ZXd6mJI2HSN4+Bzc+LDNdEApJ6HFbPkdFNHLvOplIIuJvxkS4XUxqirw==", + "license": "BSD-3-Clause", + "dependencies": { + "@lit/reactive-element": "^2.1.0", + "lit-element": "^4.2.0", + "lit-html": "^3.3.0" + } + }, + "node_modules/lit-element": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.2.2.tgz", + "integrity": "sha512-aFKhNToWxoyhkNDmWZwEva2SlQia+jfG0fjIWV//YeTaWrVnOxD89dPKfigCUspXFmjzOEUQpOkejH5Ly6sG0w==", + "license": "BSD-3-Clause", + "dependencies": { + "@lit-labs/ssr-dom-shim": "^1.5.0", + "@lit/reactive-element": "^2.1.0", + "lit-html": "^3.3.0" + } + }, + "node_modules/lit-html": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.3.3.tgz", + "integrity": "sha512-el8M6jK2o3RXBnrSHX3ZKrsN8zEV63pSExTO1wYJz7QndGYZ8353e2a5PPX+qHe2aGayfnchQmkAojaWAREOIA==", + "license": "BSD-3-Clause", + "dependencies": { + "@types/trusted-types": "^2.0.2" + } + }, + "node_modules/lodash-es": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz", + "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==", + "license": "MIT" + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "license": "MIT" + }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "deprecated": "This package is deprecated. Use the optional chaining (?.) operator instead.", + "license": "MIT" + }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0" + }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lowlight": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-1.20.0.tgz", + "integrity": "sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==", + "license": "MIT", + "dependencies": { + "fault": "^1.0.0", + "highlight.js": "~10.7.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/lucide": { + "version": "0.525.0", + "resolved": "https://registry.npmjs.org/lucide/-/lucide-0.525.0.tgz", + "integrity": "sha512-sfehWlaE/7NVkcEQ4T9JD3eID8RNMIGJBBUq9wF3UFiJIrcMKRbU3g1KGfDk4svcW7yw8BtDLXaXo02scDtUYQ==", + "license": "ISC" + }, + "node_modules/lucide-react": { + "version": "0.525.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.525.0.tgz", + "integrity": "sha512-Tm1txJ2OkymCGkvwoHt33Y2JpN5xucVq1slHcgE6Lk0WjDfjgKWor5CdVER8U6DvcfMwh4M8XxmpTiyzfmfDYQ==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/markdown-table": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/marked": { + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-12.0.2.tgz", + "integrity": "sha512-qXUm7e/YKFoqFPYPa3Ukg9xlI5cyAtGmyEIzMfW//m6kXwCy2Ps9DYf5ioijFKQ8qyuscrHoY04iJGctu2Kg0Q==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mdast-util-definitions": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-5.1.2.tgz", + "integrity": "sha512-8SVPMuHqlPME/z3gqVwWY4zVXn8lqKv/pAhC57FuJ40ImXyBpmO5ukh98zB2v7Blql2FiHjHv9LVztSIqjY+MA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^3.0.0", + "@types/unist": "^2.0.0", + "unist-util-visit": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", + "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace/node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.1.tgz", + "integrity": "sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^3.0.0", + "@types/unist": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "mdast-util-to-string": "^3.1.0", + "micromark": "^3.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-decode-string": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "unist-util-stringify-position": "^3.0.0", + "uvu": "^0.5.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", + "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.0", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal/node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/mdast-util-gfm-autolink-literal/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-autolink-literal/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mdast-util-gfm-autolink-literal/node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mdast-util-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote/node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/mdast-util-gfm-footnote/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/mdast-util-gfm-footnote/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/mdast-util-gfm-footnote/node_modules/mdast-util-from-markdown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", + "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote/node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote/node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-footnote/node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-footnote/node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-footnote/node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-footnote/node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-footnote/node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-footnote/node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-footnote/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-footnote/node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-footnote/node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-footnote/node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-footnote/node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-footnote/node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-footnote/node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mdast-util-gfm-footnote/node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mdast-util-gfm-footnote/node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-footnote/node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-footnote/node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-footnote/node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-footnote/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mdast-util-gfm-footnote/node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mdast-util-gfm-footnote/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/mdast-util-gfm-footnote/node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-strikethrough/node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/mdast-util-gfm-strikethrough/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/mdast-util-gfm-strikethrough/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/mdast-util-gfm-strikethrough/node_modules/mdast-util-from-markdown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", + "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-strikethrough/node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-strikethrough/node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-strikethrough/node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-strikethrough/node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-strikethrough/node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-strikethrough/node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-strikethrough/node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-strikethrough/node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-strikethrough/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-strikethrough/node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-strikethrough/node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-strikethrough/node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-strikethrough/node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-strikethrough/node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-strikethrough/node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mdast-util-gfm-strikethrough/node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mdast-util-gfm-strikethrough/node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-strikethrough/node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-strikethrough/node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-strikethrough/node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-strikethrough/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mdast-util-gfm-strikethrough/node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mdast-util-gfm-strikethrough/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/mdast-util-gfm-strikethrough/node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table/node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/mdast-util-gfm-table/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/mdast-util-gfm-table/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/mdast-util-gfm-table/node_modules/mdast-util-from-markdown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", + "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table/node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table/node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-table/node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-table/node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-table/node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-table/node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-table/node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-table/node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-table/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-table/node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-table/node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-table/node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-table/node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-table/node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-table/node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mdast-util-gfm-table/node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mdast-util-gfm-table/node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-table/node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-table/node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-table/node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-table/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mdast-util-gfm-table/node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mdast-util-gfm-table/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/mdast-util-gfm-table/node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item/node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/mdast-util-gfm-task-list-item/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/mdast-util-gfm-task-list-item/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/mdast-util-gfm-task-list-item/node_modules/mdast-util-from-markdown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", + "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item/node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item/node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-task-list-item/node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-task-list-item/node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-task-list-item/node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-task-list-item/node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-task-list-item/node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-task-list-item/node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-task-list-item/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-task-list-item/node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-task-list-item/node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-task-list-item/node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-task-list-item/node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-task-list-item/node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-task-list-item/node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mdast-util-gfm-task-list-item/node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mdast-util-gfm-task-list-item/node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-task-list-item/node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-task-list-item/node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-task-list-item/node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-task-list-item/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mdast-util-gfm-task-list-item/node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mdast-util-gfm-task-list-item/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/mdast-util-gfm-task-list-item/node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm/node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/mdast-util-gfm/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/mdast-util-gfm/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/mdast-util-gfm/node_modules/mdast-util-from-markdown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", + "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm/node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm/node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm/node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm/node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm/node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm/node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm/node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm/node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm/node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm/node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm/node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm/node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm/node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm/node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mdast-util-gfm/node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mdast-util-gfm/node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm/node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm/node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm/node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mdast-util-gfm/node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mdast-util-gfm/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/mdast-util-gfm/node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-math": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-math/-/mdast-util-math-3.0.0.tgz", + "integrity": "sha512-Tl9GBNeG/AhJnQM221bJR2HPvLOSnLE/T9cJI9tlc6zwQk2nPk/4f0cHkOdEixQPC/j8UtKDdITswvLAy1OZ1w==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "longest-streak": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.1.0", + "unist-util-remove-position": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-math/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/mdast-util-math/node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/mdast-util-math/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/mdast-util-math/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/mdast-util-math/node_modules/mdast-util-from-markdown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", + "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-math/node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-math/node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-math/node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-math/node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-math/node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-math/node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-math/node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-math/node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-math/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-math/node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/mdast-util-math/node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-math/node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-math/node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/mdast-util-math/node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/mdast-util-math/node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mdast-util-math/node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mdast-util-math/node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/mdast-util-math/node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-math/node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/mdast-util-math/node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-math/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mdast-util-math/node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mdast-util-math/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/mdast-util-math/node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/mdast-util-mdx-expression/node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/mdast-util-mdx-expression/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/mdast-util-mdx-expression/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/mdast-util-mdx-expression/node_modules/mdast-util-from-markdown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", + "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression/node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression/node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-expression/node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-expression/node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-expression/node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-expression/node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-expression/node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-expression/node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-expression/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-expression/node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-expression/node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-expression/node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-expression/node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-expression/node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-expression/node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mdast-util-mdx-expression/node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mdast-util-mdx-expression/node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-expression/node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-expression/node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-expression/node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-expression/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mdast-util-mdx-expression/node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mdast-util-mdx-expression/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/mdast-util-mdx-expression/node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/mdast-util-mdx-jsx/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/mdast-util-from-markdown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", + "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mdast-util-mdx-jsx/node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mdast-util-mdx-jsx/node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mdast-util-mdx-jsx/node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mdast-util-mdx-jsx/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/mdast-util-mdx-jsx/node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/mdast-util-mdxjs-esm/node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/mdast-util-mdxjs-esm/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/mdast-util-mdxjs-esm/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/mdast-util-mdxjs-esm/node_modules/mdast-util-from-markdown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", + "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm/node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm/node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdxjs-esm/node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdxjs-esm/node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdxjs-esm/node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdxjs-esm/node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdxjs-esm/node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdxjs-esm/node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdxjs-esm/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdxjs-esm/node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/mdast-util-mdxjs-esm/node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdxjs-esm/node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdxjs-esm/node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/mdast-util-mdxjs-esm/node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/mdast-util-mdxjs-esm/node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mdast-util-mdxjs-esm/node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mdast-util-mdxjs-esm/node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/mdast-util-mdxjs-esm/node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdxjs-esm/node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/mdast-util-mdxjs-esm/node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdxjs-esm/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mdast-util-mdxjs-esm/node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mdast-util-mdxjs-esm/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/mdast-util-mdxjs-esm/node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing/node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-12.3.0.tgz", + "integrity": "sha512-pits93r8PhnIoU4Vy9bjW39M2jJ6/tdHyja9rrot9uujkN7UTU9SDnE6WNJz/IGyQk3XHX6yNNtrBH6cQzm8Hw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^2.0.0", + "@types/mdast": "^3.0.0", + "mdast-util-definitions": "^5.0.0", + "micromark-util-sanitize-uri": "^1.1.0", + "trim-lines": "^3.0.0", + "unist-util-generated": "^2.0.0", + "unist-util-position": "^4.0.0", + "unist-util-visit": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown/node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/mdast-util-to-markdown/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/mdast-util-to-markdown/node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-to-markdown/node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-to-markdown/node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/mdast-util-to-markdown/node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/mdast-util-to-markdown/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mdast-util-to-markdown/node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mdast-util-to-markdown/node_modules/unist-util-visit": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.2.0.tgz", + "integrity": "sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/mermaid": { + "version": "11.15.0", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.15.0.tgz", + "integrity": "sha512-pTMbcf3rWdtLiYGpmoTjHEpeY8seiy6sR+9nD7LOs8KfUbHE4lOUAprTRqRAcWSQ6MQpdX+YEsxShtGsINtPtw==", + "license": "MIT", + "dependencies": { + "@braintree/sanitize-url": "^7.1.1", + "@iconify/utils": "^3.0.2", + "@mermaid-js/parser": "^1.1.1", + "@types/d3": "^7.4.3", + "@upsetjs/venn.js": "^2.0.0", + "cytoscape": "^3.33.1", + "cytoscape-cose-bilkent": "^4.1.0", + "cytoscape-fcose": "^2.2.0", + "d3": "^7.9.0", + "d3-sankey": "^0.12.3", + "dagre-d3-es": "7.0.14", + "dayjs": "^1.11.19", + "dompurify": "^3.3.1", + "es-toolkit": "^1.45.1", + "katex": "^0.16.25", + "khroma": "^2.1.0", + "marked": "^16.3.0", + "roughjs": "^4.6.6", + "stylis": "^4.3.6", + "ts-dedent": "^2.2.0", + "uuid": "^11.1.0 || ^12 || ^13 || ^14.0.0" + } + }, + "node_modules/mermaid/node_modules/marked": { + "version": "16.4.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-16.4.2.tgz", + "integrity": "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromark": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-3.2.0.tgz", + "integrity": "sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "micromark-core-commonmark": "^1.0.1", + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-chunked": "^1.0.0", + "micromark-util-combine-extensions": "^1.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-encode": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-sanitize-uri": "^1.0.0", + "micromark-util-subtokenize": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.1", + "uvu": "^0.5.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-1.1.0.tgz", + "integrity": "sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-factory-destination": "^1.0.0", + "micromark-factory-label": "^1.0.0", + "micromark-factory-space": "^1.0.0", + "micromark-factory-title": "^1.0.0", + "micromark-factory-whitespace": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-chunked": "^1.0.0", + "micromark-util-classify-character": "^1.0.0", + "micromark-util-html-tag-name": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-subtokenize": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.1", + "uvu": "^0.5.0" + } + }, + "node_modules/micromark-extension-cjk-friendly-util": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-extension-cjk-friendly-util/-/micromark-extension-cjk-friendly-util-2.1.1.tgz", + "integrity": "sha512-egs6+12JU2yutskHY55FyR48ZiEcFOJFyk9rsiyIhcJ6IvWB6ABBqVrBw8IobqJTDZ/wdSr9eoXDPb5S2nW1bg==", + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.0", + "micromark-util-character": "^2.1.1", + "micromark-util-symbol": "^2.0.1" + }, + "engines": { + "node": ">=16" + }, + "peerDependenciesMeta": { + "micromark-util-types": { + "optional": true + } + } + }, + "node_modules/micromark-extension-cjk-friendly-util/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-cjk-friendly-util/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-extension-cjk-friendly-util/node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "license": "MIT", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal/node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-extension-gfm-autolink-literal/node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-extension-gfm-autolink-literal/node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm-strikethrough/node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm-strikethrough/node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm-strikethrough/node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm-strikethrough/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-extension-gfm-strikethrough/node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", + "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table/node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm-table/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm-table/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-extension-gfm-table/node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-tagfilter/node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item/node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm-task-list-item/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm-task-list-item/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-extension-gfm-task-list-item/node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-extension-gfm/node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm/node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-extension-gfm/node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-extension-math": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-math/-/micromark-extension-math-3.1.0.tgz", + "integrity": "sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==", + "license": "MIT", + "dependencies": { + "@types/katex": "^0.16.0", + "devlop": "^1.0.0", + "katex": "^0.16.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-math/node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-math/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-math/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-extension-math/node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-factory-destination": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.1.0.tgz", + "integrity": "sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-1.1.0.tgz", + "integrity": "sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz", + "integrity": "sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-1.1.0.tgz", + "integrity": "sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-1.1.0.tgz", + "integrity": "sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz", + "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-1.1.0.tgz", + "integrity": "sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-1.1.0.tgz", + "integrity": "sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.1.0.tgz", + "integrity": "sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.1.0.tgz", + "integrity": "sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-1.1.0.tgz", + "integrity": "sha512-YphLGCK8gM1tG1bd54azwyrQRjCFcmgj2S2GoJDNnh4vYtnL38JS8M4gpxzOPNyHdNEpheyWXCTnnTDY3N+NVQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-1.1.0.tgz", + "integrity": "sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.2.0.tgz", + "integrity": "sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.1.0.tgz", + "integrity": "sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-1.1.0.tgz", + "integrity": "sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.2.0.tgz", + "integrity": "sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-encode": "^1.0.0", + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-1.1.0.tgz", + "integrity": "sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", + "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", + "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/micromark/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minizlib": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/module-details-from-path": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.4.tgz", + "integrity": "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==", + "license": "MIT" + }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/mustache": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", + "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", + "license": "MIT", + "bin": { + "mustache": "bin/mustache" + } + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/next": { + "version": "15.5.18", + "resolved": "https://registry.npmjs.org/next/-/next-15.5.18.tgz", + "integrity": "sha512-eKL8zUJkX9Y5lE+RX/2YJoItVdGlIscyVyboeD9wSpp0PaGqjoA4tTpT2qPqz9ax+5IzGESyLSeZ/RCwbSZ2uQ==", + "license": "MIT", + "dependencies": { + "@next/env": "15.5.18", + "@swc/helpers": "0.5.15", + "caniuse-lite": "^1.0.30001579", + "postcss": "8.4.31", + "styled-jsx": "5.1.6" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": "^18.18.0 || ^19.8.0 || >= 20.0.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "15.5.18", + "@next/swc-darwin-x64": "15.5.18", + "@next/swc-linux-arm64-gnu": "15.5.18", + "@next/swc-linux-arm64-musl": "15.5.18", + "@next/swc-linux-x64-gnu": "15.5.18", + "@next/swc-linux-x64-musl": "15.5.18", + "@next/swc-win32-arm64-msvc": "15.5.18", + "@next/swc-win32-x64-msvc": "15.5.18", + "sharp": "^0.34.3" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.51.1", + "babel-plugin-react-compiler": "*", + "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/oniguruma-parser": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.2.tgz", + "integrity": "sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw==", + "license": "MIT" + }, + "node_modules/oniguruma-to-es": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.6.tgz", + "integrity": "sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA==", + "license": "MIT", + "dependencies": { + "oniguruma-parser": "^0.12.2", + "regex": "^6.1.0", + "regex-recursion": "^6.0.2" + } + }, + "node_modules/openai": { + "version": "6.39.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-6.39.0.tgz", + "integrity": "sha512-O61LIsimY3acVabwvomwFhwrnN36yvHY2quIfy9keEcFytGgWeV35yLHQ6NVMLSBxRpHmcg2yuhCnlu2HT4pLQ==", + "license": "Apache-2.0", + "bin": { + "openai": "bin/cli" + }, + "peerDependencies": { + "ws": "^8.18.0", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "ws": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/p-queue": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", + "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^4.0.4", + "p-timeout": "^3.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-retry": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-7.1.1.tgz", + "integrity": "sha512-J5ApzjyRkkf601HpEeykoiCvzHQjWxPAHhyjFcEUP2SWq0+35NKh8TLhpLw+Dkq5TZBFvUM6UigdE9hIVYTl5w==", + "license": "MIT", + "dependencies": { + "is-network-error": "^1.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "license": "MIT", + "dependencies": { + "p-finally": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/package-manager-detector": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.6.0.tgz", + "integrity": "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==", + "license": "MIT" + }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/partial-json": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/partial-json/-/partial-json-0.1.7.tgz", + "integrity": "sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA==", + "license": "MIT" + }, + "node_modules/path-data-parser": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/path-data-parser/-/path-data-parser-0.1.0.tgz", + "integrity": "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==", + "license": "MIT" + }, + "node_modules/path-expression-matcher": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.5.0.tgz", + "integrity": "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", + "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", + "license": "MIT" + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "license": "MIT" + }, + "node_modules/phoenix": { + "version": "1.8.7", + "resolved": "https://registry.npmjs.org/phoenix/-/phoenix-1.8.7.tgz", + "integrity": "sha512-mQfiO4PNEjToj5iUfka6OjRWtCNe+0JsVrW6x+q94kPN8VX8taulJPTa4/u2ZPb2b8+hzmBAvMxQWnuTc0T4KQ==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pino": { + "version": "9.14.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-9.14.0.tgz", + "integrity": "sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w==", + "license": "MIT", + "dependencies": { + "@pinojs/redact": "^0.4.0", + "atomic-sleep": "^1.0.0", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^2.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^5.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^3.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", + "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", + "license": "MIT", + "dependencies": { + "split2": "^4.0.0" + } + }, + "node_modules/pino-pretty": { + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-11.3.0.tgz", + "integrity": "sha512-oXwn7ICywaZPHmu3epHGU2oJX4nPmKvHvB/bwrJHlGcbEWaVcotkpyVHMKLKmiVryWYByNp0jpgAcXpFJDXJzA==", + "license": "MIT", + "dependencies": { + "colorette": "^2.0.7", + "dateformat": "^4.6.3", + "fast-copy": "^3.0.2", + "fast-safe-stringify": "^2.1.1", + "help-me": "^5.0.0", + "joycon": "^3.1.1", + "minimist": "^1.2.6", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^2.0.0", + "pump": "^3.0.0", + "readable-stream": "^4.0.0", + "secure-json-parse": "^2.4.0", + "sonic-boom": "^4.0.1", + "strip-json-comments": "^3.1.1" + }, + "bin": { + "pino-pretty": "bin.js" + } + }, + "node_modules/pino-std-serializers": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.1.0.tgz", + "integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==", + "license": "MIT" + }, + "node_modules/pkce-challenge": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/points-on-curve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/points-on-curve/-/points-on-curve-0.2.0.tgz", + "integrity": "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==", + "license": "MIT" + }, + "node_modules/points-on-path": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/points-on-path/-/points-on-path-0.2.1.tgz", + "integrity": "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==", + "license": "MIT", + "dependencies": { + "path-data-parser": "0.1.0", + "points-on-curve": "0.2.0" + } + }, + "node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prismjs": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-warning": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", + "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/property-information": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz", + "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/protobufjs": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-8.0.1.tgz", + "integrity": "sha512-NWWCCscLjs+cOKF/s/XVNFRW7Yih0fdH+9brffR5NZCy8k42yRdl5KlWKMVXuI1vfCoy4o1z80XR/W/QUb3V3w==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/qs": { + "version": "6.15.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", + "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", + "license": "MIT" + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" + }, + "node_modules/react-markdown": { + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-8.0.7.tgz", + "integrity": "sha512-bvWbzG4MtOU62XqBx3Xx+zB2raaFFsq4mYiAzfjXJMEz2sixgeAfraA3tvzULF02ZdOMUOKTBFFaZJDDrq+BJQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^2.0.0", + "@types/prop-types": "^15.0.0", + "@types/unist": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^2.0.0", + "prop-types": "^15.0.0", + "property-information": "^6.0.0", + "react-is": "^18.0.0", + "remark-parse": "^10.0.0", + "remark-rehype": "^10.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-object": "^0.4.0", + "unified": "^10.0.0", + "unist-util-visit": "^4.0.0", + "vfile": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=16", + "react": ">=16" + } + }, + "node_modules/react-remove-scroll": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", + "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.7", + "react-style-singleton": "^2.2.3", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.3" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", + "license": "MIT", + "dependencies": { + "react-style-singleton": "^2.2.2", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-style-singleton": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", + "license": "MIT", + "dependencies": { + "get-nonce": "^1.0.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-syntax-highlighter": { + "version": "16.1.1", + "resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-16.1.1.tgz", + "integrity": "sha512-PjVawBGy80C6YbC5DDZJeUjBmC7skaoEUdvfFQediQHgCL7aKyVHe57SaJGfQsloGDac+gCpTfRdtxzWWKmCXA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "highlight.js": "^10.4.1", + "highlightjs-vue": "^1.0.0", + "lowlight": "^1.17.0", + "prismjs": "^1.30.0", + "refractor": "^5.0.0" + }, + "engines": { + "node": ">= 16.20.2" + }, + "peerDependencies": { + "react": ">= 0.14.0" + } + }, + "node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/readable-stream/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "license": "MIT", + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", + "license": "Apache-2.0" + }, + "node_modules/refractor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/refractor/-/refractor-5.0.0.tgz", + "integrity": "sha512-QXOrHQF5jOpjjLfiNk5GFnWhRXvxjUVnlFxkeDmewR5sXkr3iM46Zo+CnRR8B+MDVqkULW4EcLVcRBNOPXHosw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/prismjs": "^1.0.0", + "hastscript": "^9.0.0", + "parse-entities": "^4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/regex/-/regex-6.1.0.tgz", + "integrity": "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==", + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-recursion": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz", + "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-utilities": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz", + "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", + "license": "MIT" + }, + "node_modules/rehype-harden": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/rehype-harden/-/rehype-harden-1.1.8.tgz", + "integrity": "sha512-Qn7vR1xrf6fZCrkm9TDWi/AB4ylrHy+jqsNm1EHOAmbARYA6gsnVJBq/sdBh6kmT4NEZxH5vgIjrscefJAOXcw==", + "license": "MIT", + "dependencies": { + "unist-util-visit": "^5.0.0" + } + }, + "node_modules/rehype-harden/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/rehype-harden/node_modules/unist-util-visit": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-katex": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/rehype-katex/-/rehype-katex-7.0.1.tgz", + "integrity": "sha512-OiM2wrZ/wuhKkigASodFoo8wimG3H12LWQaH8qSPVJn9apWKFSH3YOCtbKpBorTVw/eI7cuT21XBbvwEswbIOA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/katex": "^0.16.0", + "hast-util-from-html-isomorphic": "^2.0.0", + "hast-util-to-text": "^4.0.0", + "katex": "^0.16.0", + "unist-util-visit-parents": "^6.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-katex/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/rehype-katex/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/rehype-katex/node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-raw": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz", + "integrity": "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-raw": "^9.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-raw/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/rehype-raw/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/rehype-raw/node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-sanitize": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/rehype-sanitize/-/rehype-sanitize-6.0.0.tgz", + "integrity": "sha512-CsnhKNsyI8Tub6L4sm5ZFsme4puGfc6pYylvXo1AeqaGbjOYyzNv3qZPwvs0oMJ39eryyeOdmxwUIo94IpEhqg==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-sanitize": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-sanitize/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/remark-gfm": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", + "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-gfm": "^3.0.0", + "micromark-extension-gfm": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-gfm/node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/remark-gfm/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/remark-gfm/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/remark-gfm/node_modules/mdast-util-from-markdown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", + "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-gfm/node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-gfm/node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/remark-gfm/node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/remark-gfm/node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/remark-gfm/node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/remark-gfm/node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/remark-gfm/node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/remark-gfm/node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/remark-gfm/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/remark-gfm/node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/remark-gfm/node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/remark-gfm/node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/remark-gfm/node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/remark-gfm/node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/remark-gfm/node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/remark-gfm/node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/remark-gfm/node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/remark-gfm/node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/remark-gfm/node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/remark-gfm/node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/remark-gfm/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/remark-gfm/node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/remark-gfm/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/remark-gfm/node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-gfm/node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-gfm/node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-gfm/node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-math": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/remark-math/-/remark-math-6.0.0.tgz", + "integrity": "sha512-MMqgnP74Igy+S3WwnhQ7kqGlEerTETXMvJhrUzDikVZ2/uogJCb+WHUg97hK9/jcfc0dkD73s3LN8zU49cTEtA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-math": "^3.0.0", + "micromark-extension-math": "^3.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-math/node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/remark-math/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/remark-math/node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-math/node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-10.0.2.tgz", + "integrity": "sha512-3ydxgHa/ZQzG8LvC7jTXccARYDcRld3VfcgIIFs7bI6vbRSxJJmzgLEIIoYKyrfhaY+ujuWaf/PJiMZXoiCXgw==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^3.0.0", + "mdast-util-from-markdown": "^1.0.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-10.1.0.tgz", + "integrity": "sha512-EFmR5zppdBp0WQeDVZ/b66CWJipB2q2VLNFMabzDSGR66Z2fQii83G5gTBbgGEnEEA0QRussvrFHxk1HWGJskw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^2.0.0", + "@types/mdast": "^3.0.0", + "mdast-util-to-hast": "^12.1.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-stringify/node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/remark-stringify/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/remark-stringify/node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-stringify/node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remend": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/remend/-/remend-1.0.1.tgz", + "integrity": "sha512-152puVH0qMoRJQFnaMG+rVDdf01Jq/CaED+MBuXExurJgdbkLp0c3TIe4R12o28Klx8uyGsjvFNG05aFG69G9w==", + "license": "Apache-2.0" + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-in-the-middle": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-8.0.1.tgz", + "integrity": "sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "module-details-from-path": "^1.0.3" + }, + "engines": { + "node": ">=9.3.0 || >=8.10.0 <9.0.0" + } + }, + "node_modules/require-in-the-middle/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/require-in-the-middle/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/robust-predicates": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.3.tgz", + "integrity": "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==", + "license": "Unlicense" + }, + "node_modules/roughjs": { + "version": "4.6.6", + "resolved": "https://registry.npmjs.org/roughjs/-/roughjs-4.6.6.tgz", + "integrity": "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==", + "license": "MIT", + "dependencies": { + "hachure-fill": "^0.5.2", + "path-data-parser": "^0.1.0", + "points-on-curve": "^0.2.0", + "points-on-path": "^0.2.1" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/router/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/router/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/router/node_modules/path-to-regexp": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "license": "BSD-3-Clause" + }, + "node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "license": "MIT", + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/secure-json-parse": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", + "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==", + "license": "BSD-3-Clause" + }, + "node_modules/semver": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", + "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.4.tgz", + "integrity": "sha512-VsC6n6vz1ihYYyZZwX7YZSF5l5x36ca17OC+a69h94YqB7X6XLwf+5MOgynYir2SLFUbl8gIYvBo8K8RoNQ6bQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/shiki": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-3.23.0.tgz", + "integrity": "sha512-55Dj73uq9ZXL5zyeRPzHQsK7Nbyt6Y10k5s7OjuFZGMhpp4r/rsLBH0o/0fstIzX1Lep9VxefWljK/SKCzygIA==", + "license": "MIT", + "dependencies": { + "@shikijs/core": "3.23.0", + "@shikijs/engine-javascript": "3.23.0", + "@shikijs/engine-oniguruma": "3.23.0", + "@shikijs/langs": "3.23.0", + "@shikijs/themes": "3.23.0", + "@shikijs/types": "3.23.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/shiki/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sonic-boom": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz", + "integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==", + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stream-browserify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz", + "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==", + "license": "MIT", + "dependencies": { + "inherits": "~2.0.4", + "readable-stream": "^3.5.0" + } + }, + "node_modules/stream-browserify/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/streamdown": { + "version": "1.6.11", + "resolved": "https://registry.npmjs.org/streamdown/-/streamdown-1.6.11.tgz", + "integrity": "sha512-Y38fwRx5kCKTluwM+Gf27jbbi9q6Qy+WC9YrC1YbCpMkktT3PsRBJHMWiqYeF8y/JzLpB1IzDoeaB6qkQEDnAA==", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1", + "hast": "^1.0.0", + "hast-util-to-jsx-runtime": "^2.3.6", + "html-url-attributes": "^3.0.1", + "katex": "^0.16.22", + "lucide-react": "^0.542.0", + "marked": "^16.2.1", + "mermaid": "^11.11.0", + "rehype-harden": "^1.1.6", + "rehype-katex": "^7.0.1", + "rehype-raw": "^7.0.0", + "rehype-sanitize": "^6.0.0", + "remark-cjk-friendly": "^1.2.3", + "remark-cjk-friendly-gfm-strikethrough": "^1.2.3", + "remark-gfm": "^4.0.1", + "remark-math": "^6.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.1.2", + "remend": "1.0.1", + "shiki": "^3.12.2", + "tailwind-merge": "^3.3.1", + "unified": "^11.0.5", + "unist-util-visit": "^5.0.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/streamdown/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/streamdown/node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/streamdown/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/streamdown/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/streamdown/node_modules/lucide-react": { + "version": "0.542.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.542.0.tgz", + "integrity": "sha512-w3hD8/SQB7+lzU2r4VdFyzzOzKnUjTZIF/MQJGSSvni7Llewni4vuViRppfRAa2guOsY5k4jZyxw/i9DQHv+dw==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/streamdown/node_modules/marked": { + "version": "16.4.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-16.4.2.tgz", + "integrity": "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/streamdown/node_modules/mdast-util-from-markdown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", + "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/streamdown/node_modules/mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/streamdown/node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/streamdown/node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/streamdown/node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/streamdown/node_modules/micromark-extension-cjk-friendly": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/micromark-extension-cjk-friendly/-/micromark-extension-cjk-friendly-1.2.3.tgz", + "integrity": "sha512-gRzVLUdjXBLX6zNPSnHGDoo+ZTp5zy+MZm0g3sv+3chPXY7l9gW+DnrcHcZh/jiPR6MjPKO4AEJNp4Aw6V9z5Q==", + "license": "MIT", + "dependencies": { + "devlop": "^1.1.0", + "micromark-extension-cjk-friendly-util": "2.1.1", + "micromark-util-chunked": "^2.0.1", + "micromark-util-resolve-all": "^2.0.1", + "micromark-util-symbol": "^2.0.1" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "micromark": "^4.0.0", + "micromark-util-types": "^2.0.0" + }, + "peerDependenciesMeta": { + "micromark-util-types": { + "optional": true + } + } + }, + "node_modules/streamdown/node_modules/micromark-extension-cjk-friendly-gfm-strikethrough": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/micromark-extension-cjk-friendly-gfm-strikethrough/-/micromark-extension-cjk-friendly-gfm-strikethrough-1.2.3.tgz", + "integrity": "sha512-gSPnxgHDDqXYOBvQRq6lerrq9mjDhdtKn+7XETuXjxWcL62yZEfUdA28Ml1I2vDIPfAOIKLa0h2XDSGkInGHFQ==", + "license": "MIT", + "dependencies": { + "devlop": "^1.1.0", + "get-east-asian-width": "^1.3.0", + "micromark-extension-cjk-friendly-util": "2.1.1", + "micromark-util-character": "^2.1.1", + "micromark-util-chunked": "^2.0.1", + "micromark-util-resolve-all": "^2.0.1", + "micromark-util-symbol": "^2.0.1" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "micromark": "^4.0.0", + "micromark-util-types": "^2.0.0" + }, + "peerDependenciesMeta": { + "micromark-util-types": { + "optional": true + } + } + }, + "node_modules/streamdown/node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/streamdown/node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/streamdown/node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/streamdown/node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/streamdown/node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/streamdown/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/streamdown/node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/streamdown/node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/streamdown/node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/streamdown/node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/streamdown/node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/streamdown/node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/streamdown/node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/streamdown/node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/streamdown/node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/streamdown/node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/streamdown/node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/streamdown/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/streamdown/node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/streamdown/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/streamdown/node_modules/remark-cjk-friendly": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/remark-cjk-friendly/-/remark-cjk-friendly-1.2.3.tgz", + "integrity": "sha512-UvAgxwlNk+l9Oqgl/9MWK2eWRS7zgBW/nXX9AthV7nd/3lNejF138E7Xbmk9Zs4WjTJGs721r7fAEc7tNFoH7g==", + "license": "MIT", + "dependencies": { + "micromark-extension-cjk-friendly": "1.2.3" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "@types/mdast": "^4.0.0", + "unified": "^11.0.0" + }, + "peerDependenciesMeta": { + "@types/mdast": { + "optional": true + } + } + }, + "node_modules/streamdown/node_modules/remark-cjk-friendly-gfm-strikethrough": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/remark-cjk-friendly-gfm-strikethrough/-/remark-cjk-friendly-gfm-strikethrough-1.2.3.tgz", + "integrity": "sha512-bXfMZtsaomK6ysNN/UGRIcasQAYkC10NtPmP0oOHOV8YOhA2TXmwRXCku4qOzjIFxAPfish5+XS0eIug2PzNZA==", + "license": "MIT", + "dependencies": { + "micromark-extension-cjk-friendly-gfm-strikethrough": "1.2.3" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "@types/mdast": "^4.0.0", + "unified": "^11.0.0" + }, + "peerDependenciesMeta": { + "@types/mdast": { + "optional": true + } + } + }, + "node_modules/streamdown/node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/streamdown/node_modules/remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/streamdown/node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/streamdown/node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/streamdown/node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/streamdown/node_modules/unist-util-visit": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/streamdown/node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strnum": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.3.0.tgz", + "integrity": "sha512-ums3KNd42PGyx5xaoVTO1mjU1bH3NpY4vsrVlnv9PNGqQj8wd7rJ6nEypLrJ7z5vxK5RP0yMLo6J/Gsm62DI5Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/style-to-js": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", + "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==", + "license": "MIT", + "dependencies": { + "style-to-object": "1.0.14" + } + }, + "node_modules/style-to-js/node_modules/inline-style-parser": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", + "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", + "license": "MIT" + }, + "node_modules/style-to-js/node_modules/style-to-object": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz", + "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.7" + } + }, + "node_modules/style-to-object": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.4.4.tgz", + "integrity": "sha512-HYNoHZa2GorYNyqiCaBgsxvcJIn7OHq6inEga+E6Ke3m5JkoqpQbnFssk4jwe+K7AhGa2fcha4wSOf1Kn01dMg==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.1.1" + } + }, + "node_modules/styled-jsx": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", + "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", + "license": "MIT", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/stylis": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.4.0.tgz", + "integrity": "sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==", + "license": "MIT" + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tailwind-merge": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.6.0.tgz", + "integrity": "sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tar": { + "version": "7.5.15", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.15.tgz", + "integrity": "sha512-dzGK0boVlC4W5QFuQN1EFSl3bIDYsk7Tj40U6eIBnK2k/8ml7TZ5agbI5j5+qnoVcAA+rNtBml8SEiLxZpNqRQ==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/thread-stream": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz", + "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==", + "license": "MIT", + "dependencies": { + "real-require": "^0.2.0" + } + }, + "node_modules/tinyexec": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.2.tgz", + "integrity": "sha512-M/Q0B2cp4K7kynaT/vnED1j8TlLY+Pp7C6Wl2bl/7u/F0mUVwdyOpwomQb8JpYLitHUssAJRmLZdMCGsrx7i+g==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/ts-dedent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", + "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", + "license": "MIT", + "engines": { + "node": ">=6.10" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/tw-animate-css": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.4.0.tgz", + "integrity": "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Wombosvideo" + } + }, + "node_modules/type-graphql": { + "version": "2.0.0-rc.1", + "resolved": "https://registry.npmjs.org/type-graphql/-/type-graphql-2.0.0-rc.1.tgz", + "integrity": "sha512-HCu4j3jR0tZvAAoO7DMBT3MRmah0DFRe5APymm9lXUghXA0sbhiMf6SLRafRYfk0R0KiUQYRduuGP3ap1RnF1Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/TypeGraphQL" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/typegraphql" + } + ], + "license": "MIT", + "dependencies": { + "@graphql-yoga/subscription": "^5.0.0", + "@types/node": "*", + "@types/semver": "^7.5.6", + "graphql-query-complexity": "^0.12.0", + "semver": "^7.5.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">= 18.12.0" + }, + "peerDependencies": { + "class-validator": ">=0.14.0", + "graphql": "^16.8.1", + "graphql-scalars": "^1.22.4" + }, + "peerDependenciesMeta": { + "class-validator": { + "optional": true + } + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT" + }, + "node_modules/unified": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/unified/-/unified-10.1.2.tgz", + "integrity": "sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "bail": "^2.0.0", + "extend": "^3.0.0", + "is-buffer": "^2.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-find-after": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz", + "integrity": "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-find-after/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/unist-util-generated": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-2.0.1.tgz", + "integrity": "sha512-qF72kLmPxAw0oN2fwpWIqbXAVyEqUzDHMsbtPvOudIlUzXYFIeQIuxXQCRCFh22B7cixvU0MG7m3MW8FTq/S+A==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/unist-util-position": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-4.0.4.tgz", + "integrity": "sha512-kUBE91efOWfIVBo8xzh/uZQ7p9ffYRtUbMRZBNFYwf0RK8koUMx6dGUfwylLOKmaT2cs4wSW96QoYUSXAyEtpg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-remove-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-5.0.0.tgz", + "integrity": "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-remove-position/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/unist-util-remove-position/node_modules/unist-util-visit": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz", + "integrity": "sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.2.tgz", + "integrity": "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0", + "unist-util-visit-parents": "^5.1.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/unist-util-visit/node_modules/unist-util-is": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-5.2.1.tgz", + "integrity": "sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit/node_modules/unist-util-visit-parents": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz", + "integrity": "sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/untruncate-json": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/untruncate-json/-/untruncate-json-0.0.1.tgz", + "integrity": "sha512-4W9enDK4X1y1s2S/Rz7ysw6kDuMS3VmRjMFg7GZrNO+98OSe+x5Lh7PKYoVjy3lW/1wmhs6HW0lusnQRHgMarA==", + "license": "MIT" + }, + "node_modules/urlpattern-polyfill": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.1.0.tgz", + "integrity": "sha512-IGjKp/o0NL3Bso1PymYURCJxMPNAf/ILOpendP9f5B6e1rTJgdgiOvgfoT8VxCAdY+Wisb9uhGaJJf3yZ2V9nw==", + "license": "MIT" + }, + "node_modules/urql": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/urql/-/urql-4.2.2.tgz", + "integrity": "sha512-3GgqNa6iF7bC4hY/ImJKN4REQILcSU9VKcKL8gfELZM8mM5BnLH1BsCc8kBdnVGD1LIFOs4W3O2idNHhON1r0w==", + "license": "MIT", + "dependencies": { + "@urql/core": "^5.1.1", + "wonka": "^6.3.2" + }, + "peerDependencies": { + "@urql/core": "^5.0.0", + "react": ">= 16.8.0" + } + }, + "node_modules/use-callback-ref": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", + "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", + "license": "MIT", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-stick-to-bottom": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/use-stick-to-bottom/-/use-stick-to-bottom-1.1.4.tgz", + "integrity": "sha512-2w/lydkrwhWMv1vCaEhYbzMDhgbwIodHpAHPV0/xKJErRkbjDEUe1EWmvr6Fwb+qhiERjc1EWgAEZaSaF69CpA==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.1.tgz", + "integrity": "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/uvu": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz", + "integrity": "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0", + "diff": "^5.0.0", + "kleur": "^4.0.3", + "sade": "^1.7.3" + }, + "bin": { + "uvu": "bin.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/validator": { + "version": "13.15.35", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.35.tgz", + "integrity": "sha512-TQ5pAGhd5whStmqWvYF4OjQROlmv9SMFVt37qoCBdqRffuuklWYQlCNnEs2ZaIBD1kZRNnikiZOS1eqgkar0iw==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vfile": { + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-5.3.7.tgz", + "integrity": "sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "is-buffer": "^2.0.0", + "unist-util-stringify-position": "^3.0.0", + "vfile-message": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-location": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz", + "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-location/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/vfile-location/node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/vfile-message/node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile/node_modules/vfile-message": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.4.tgz", + "integrity": "sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-stringify-position": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/web-namespaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", + "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wonka": { + "version": "6.3.6", + "resolved": "https://registry.npmjs.org/wonka/-/wonka-6.3.6.tgz", + "integrity": "sha512-MXH+6mDHAZ2GuMpgKS055FR6v0xVP3XwquxIMYXgiW+FejHQlMGlvVRZT4qMCxR+bEo/FCtIdKxwej9WV3YQag==", + "license": "MIT" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-naming": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/xml-naming/-/xml-naming-0.1.0.tgz", + "integrity": "sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/yaml": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.25.28 || ^4" + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + } +} diff --git a/examples/showcases/daytona-runcode/package.json b/examples/showcases/daytona-runcode/package.json new file mode 100644 index 00000000000..9fbac58bf84 --- /dev/null +++ b/examples/showcases/daytona-runcode/package.json @@ -0,0 +1,25 @@ +{ + "name": "daytona-runcode-showcase", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start -H 0.0.0.0 -p ${PORT:-3000}" + }, + "dependencies": { + "@copilotkit/react-core": "1.58.0", + "@copilotkit/runtime": "1.58.0", + "@daytonaio/sdk": "latest", + "@types/node": "^22.10.5", + "@types/react": "^18.3.18", + "@types/react-dom": "^18.3.5", + "@types/react-syntax-highlighter": "^15.5.13", + "hono": "^4.6.14", + "next": "^15.5.18", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-syntax-highlighter": "^16.1.1", + "typescript": "^5.7.3", + "zod": "^3.23.8" + } +} diff --git a/examples/showcases/daytona-runcode/tsconfig.json b/examples/showcases/daytona-runcode/tsconfig.json new file mode 100644 index 00000000000..cae02ec5339 --- /dev/null +++ b/examples/showcases/daytona-runcode/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [{ "name": "next" }] + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/packages/react-ui/oxlint-rules/copilotkit-plugin.mjs b/packages/react-ui/oxlint-rules/copilotkit-plugin.mjs index 71f72323cbe..8e5d93a56af 100644 --- a/packages/react-ui/oxlint-rules/copilotkit-plugin.mjs +++ b/packages/react-ui/oxlint-rules/copilotkit-plugin.mjs @@ -1,11 +1,13 @@ import requireCpkPrefix from "./require-cpk-prefix.mjs"; import noSingleArgZodRecord from "./no-single-arg-zod-record.mjs"; +import noPublicEnvShellRead from "./no-public-env-shell-read.mjs"; const plugin = { meta: { name: "copilotkit" }, rules: { "require-cpk-prefix": requireCpkPrefix, "no-single-arg-zod-record": noSingleArgZodRecord, + "no-public-env-shell-read": noPublicEnvShellRead, }, }; diff --git a/packages/react-ui/oxlint-rules/no-public-env-shell-read.mjs b/packages/react-ui/oxlint-rules/no-public-env-shell-read.mjs new file mode 100644 index 00000000000..2c854c19666 --- /dev/null +++ b/packages/react-ui/oxlint-rules/no-public-env-shell-read.mjs @@ -0,0 +1,221 @@ +/** + * Oxlint rule: no-public-env-shell-read + * + * Bans direct READS of a specific, enumerated set of `process.env.NEXT_PUBLIC_*` + * URL/analytics keys (see `BANNED_KEYS` below) in showcase shell client/server + * code. Those values are now served at runtime via `getRuntimeConfig()` (see + * workstream B / Option-B migration); a stray `process.env.NEXT_PUBLIC_` + * read would silently re-freeze the value at build time and regress the + * migration. + * + * IMPORTANT: this rule does NOT ban every `NEXT_PUBLIC_*` read — only the + * explicit banned-key set. Build-stamp keys (NEXT_PUBLIC_COMMIT_SHA, + * NEXT_PUBLIC_BRANCH) and the local-dev computed key + * (NEXT_PUBLIC_LOCAL_BACKENDS) are intentionally NOT banned. + * + * Allowed (intentionally NOT in the banned set): + * - `NEXT_PUBLIC_COMMIT_SHA` — build-stamped artifact identifier + * - `NEXT_PUBLIC_BRANCH` — build-stamped artifact identifier + * - `NEXT_PUBLIC_LOCAL_BACKENDS` — local-dev only, computed from + * `shared/local-ports.json` at build time (not a real env var) + * + * Forms detected as READS (each will be flagged): + * - dotted member: process.env.NEXT_PUBLIC_SHELL_URL + * - string-bracket member: process.env["NEXT_PUBLIC_SHELL_URL"] + * - no-expression template: process.env[`NEXT_PUBLIC_SHELL_URL`] + * - optional chaining: process.env?.NEXT_PUBLIC_SHELL_URL + * process.env?.["NEXT_PUBLIC_SHELL_URL"] + * - destructuring read: const { NEXT_PUBLIC_SHELL_URL } = process.env + * const { NEXT_PUBLIC_SHELL_URL: aliased } = process.env + * + * Forms intentionally NOT flagged (writes/deletes are not reads): + * - assignment LHS: process.env.NEXT_PUBLIC_X = "..." + * - delete: delete process.env.NEXT_PUBLIC_X + * + * Out of scope (deliberately NOT covered — documented here so the gaps are + * auditable rather than implicit): + * - aliasing: const e = process.env; e.NEXT_PUBLIC_X + * (requires scope/flow tracking) + * - bulk-iteration reads: Object.keys(process.env), Object.values(process.env), + * Object.entries(process.env), for-in over process.env, + * spread `{...process.env}` — these read the whole + * env object without naming a key statically + * - rest-pattern destructure: `const { ...rest } = process.env` — same shape + * as the iteration case (no static key in source) + * - compound-assignment LHS: `process.env.X += "..."` — currently treated as + * an AssignmentExpression with operator !== "=" + * (i.e. NOT flagged); fine in practice because + * showcase code does not append to env vars + * - update operators: `process.env.X++` / `process.env.X--` — + * defensively bailed; not flagged + * + * Scope is enforced via `overrides[].files` in `.oxlintrc.json` (shell + * source trees only; `.mdx` content, runtime-config implementation files, + * and tests are excluded by a follow-up override that turns this rule + * back off). + * + * Note: plan-B's original spec called for oxlint's `eslint/no-restricted-syntax` + * with an AST selector regex. oxlint 1.x does not implement that rule + * (only `no-restricted-globals` / `no-restricted-imports`), so we + * implement the equivalent guard as a focused custom rule in the existing + * copilotkit oxlint plugin instead. + */ + +// Exported so the table-driven test in +// `showcase/scripts/__tests__/lint-rule-no-public-env.test.ts` can iterate +// the rule's own banned set rather than hand-mirroring it (any drift would +// silently weaken coverage). The exported value is the same Set the rule +// uses internally — they cannot diverge. +export const BANNED_KEYS = new Set([ + "NEXT_PUBLIC_POCKETBASE_URL", + "NEXT_PUBLIC_SHELL_URL", + "NEXT_PUBLIC_BASE_URL", + "NEXT_PUBLIC_OPS_BASE_URL", + "NEXT_PUBLIC_INTELLIGENCE_SIGNUP_URL", + "NEXT_PUBLIC_POSTHOG_KEY", + "NEXT_PUBLIC_POSTHOG_HOST", + "NEXT_PUBLIC_SCARF_PIXEL_ID", + "NEXT_PUBLIC_GOOGLE_ANALYTICS_TRACKING_ID", + "NEXT_PUBLIC_REB2B_KEY", + "NEXT_PUBLIC_REO_KEY", +]); + +/** + * True iff `node` (after unwrapping a wrapping `ChainExpression`) is the + * `process.env` member expression. All read forms hang off this anchor: + * - bare: process.env.X + * - optional chain: process?.env.X / process.env?.X / process?.env?.X + * Some parser flavors wrap the whole optional chain in a `ChainExpression` + * whose `.expression` is the MemberExpression we want to match; others + * surface the MemberExpression directly with `optional: true`. We accept + * both shapes by stripping the wrapper first. + */ +function isProcessEnv(node) { + if (node && node.type === "ChainExpression") node = node.expression; + if (!node || node.type !== "MemberExpression") return false; + if (node.computed) return false; + if (!node.object || node.object.type !== "Identifier") return false; + if (node.object.name !== "process") return false; + if (!node.property || node.property.type !== "Identifier") return false; + return node.property.name === "env"; +} + +/** + * Given a `node.property` from a MemberExpression read off `process.env`, + * return the static key name if we can determine one — or null if the key + * is dynamic (and therefore out of scope for a static lint). + * + * Handled key forms: + * - Identifier (dotted: process.env.FOO) + * - Literal string (bracket: process.env["FOO"]) + * - TemplateLiteral with no expressions (process.env[`FOO`]) + */ +function staticKeyName(property, computed) { + if (!property) return null; + if (!computed && property.type === "Identifier") { + return property.name; + } + if ( + computed && + property.type === "Literal" && + typeof property.value === "string" + ) { + return property.value; + } + if ( + computed && + property.type === "TemplateLiteral" && + Array.isArray(property.expressions) && + property.expressions.length === 0 && + Array.isArray(property.quasis) && + property.quasis.length === 1 + ) { + const cooked = property.quasis[0]?.value?.cooked; + return typeof cooked === "string" ? cooked : null; + } + return null; +} + +const rule = { + meta: { + type: "problem", + docs: { + description: + "Disallow shell-side reads of a banned set of NEXT_PUBLIC_* URL/analytics keys (process.env., process.env[''], destructuring, optional chaining) — use getRuntimeConfig() instead", + }, + schema: [], + messages: { + forbiddenRead: + "Do not read process.env.NEXT_PUBLIC_* directly in shell code. Use getRuntimeConfig() from @/lib/runtime-config.client (client) or @/lib/runtime-config (server). See workstream B.", + }, + }, + + create(context) { + return { + // Member-expression reads: dotted, bracket-string, bracket-template, + // and optional-chained equivalents. We skip writes (assignment LHS) + // and `delete` targets — those are not reads of the value. + MemberExpression(node) { + if (!isProcessEnv(node.object)) return; + + // Write target: `process.env.X = ...`. The MemberExpression is the + // LHS of an AssignmentExpression — not a read. + const parent = node.parent; + if ( + parent && + parent.type === "AssignmentExpression" && + parent.left === node + ) { + return; + } + // `delete process.env.X` — not a read either. + if ( + parent && + parent.type === "UnaryExpression" && + parent.operator === "delete" + ) { + return; + } + // `update` operators (++/--): also a write, but extremely unlikely + // on a string env var. Defensive bail anyway. + if (parent && parent.type === "UpdateExpression") { + return; + } + + const keyName = staticKeyName(node.property, node.computed); + if (!keyName) return; + if (!BANNED_KEYS.has(keyName)) return; + + context.report({ node, messageId: "forbiddenRead" }); + }, + + // Destructuring reads: `const { NEXT_PUBLIC_X } = process.env` and the + // aliased form `const { NEXT_PUBLIC_X: y } = process.env`. We catch + // this at the VariableDeclarator level so we can confirm the init is + // exactly `process.env` (not some other object with same-named props). + // + // Note: this does NOT cover the aliasing case + // `const e = process.env; e.NEXT_PUBLIC_X` — that requires scope + // tracking and is intentionally out of scope; see file header. + VariableDeclarator(node) { + if (!node.init || !isProcessEnv(node.init)) return; + if (!node.id || node.id.type !== "ObjectPattern") return; + for (const prop of node.id.properties) { + if (!prop || prop.type !== "Property") continue; + // The `key` is the source name on process.env; that's what we + // test against BANNED_KEYS regardless of any local alias. We + // route through staticKeyName() so the computed-string-key form + // `{ ["NEXT_PUBLIC_X"]: y } = process.env` and the no-expression + // template form `{ [`NEXT_PUBLIC_X`]: y } = process.env` are + // caught with the same parity as the bracket-member read. + const keyName = staticKeyName(prop.key, prop.computed); + if (!keyName) continue; + if (!BANNED_KEYS.has(keyName)) continue; + context.report({ node: prop, messageId: "forbiddenRead" }); + } + }, + }; + }, +}; + +export default rule; diff --git a/sdk-python/tests/test_agui_agent.py b/sdk-python/tests/test_agui_agent.py index 9cd770dd8b2..afd9c123f2f 100644 --- a/sdk-python/tests/test_agui_agent.py +++ b/sdk-python/tests/test_agui_agent.py @@ -650,9 +650,7 @@ def test_remote_endpoint_info_serializes_langgraph_agui_agent(self): ) sdk = CopilotKitRemoteEndpoint(agents=[agent], actions=[]) - info = sdk.info( - context={"properties": {}, "frontend_url": None, "headers": {}} - ) + info = sdk.info(context={"properties": {}, "frontend_url": None, "headers": {}}) assert info["agents"] == [ { diff --git a/showcase/bin/railway b/showcase/bin/railway index 1bf96a2b045..a3d47b87d72 100755 --- a/showcase/bin/railway +++ b/showcase/bin/railway @@ -70,22 +70,38 @@ module Railway GOOGLE_API_KEY ].freeze - EXPECTED_DOMAINS = { - PRODUCTION_ENV_ID => %w[ - showcase.copilotkit.ai - dashboard.showcase.copilotkit.ai - dojo.showcase.copilotkit.ai - docs.copilotkit.ai - hooks.showcase.copilotkit.ai - ].freeze, - STAGING_ENV_ID => %w[ - showcase.staging.copilotkit.ai - dashboard.showcase.staging.copilotkit.ai - dojo.showcase.staging.copilotkit.ai - docs.staging.copilotkit.ai - hooks.staging.copilotkit.ai - ].freeze, - }.freeze + # EXPECTED_DOMAINS is derived from showcase/scripts/railway-envs.generated.json + # (canonical source: showcase/scripts/railway-envs.ts). The CI guard + # `npx tsx showcase/scripts/emit-railway-envs-json.ts --check` ensures the + # JSON artifact stays in sync with the TS SSOT on every PR. Only public + # (non-`*.up.railway.app`) hosts are included, preserving the original set. + # Shared SSOT load — read once at class-load, derive both EXPECTED_DOMAINS + # and STAGING_SERVICES from the same generated.json so they cannot drift. + SSOT_DATA = begin + ssot_json = File.expand_path("../scripts/railway-envs.generated.json", __dir__) + unless File.exist?(ssot_json) + raise "railway-envs.generated.json not found at #{ssot_json}. " \ + "Re-run: npx tsx showcase/scripts/emit-railway-envs-json.ts" + end + JSON.parse(File.read(ssot_json)).freeze + end + + EXPECTED_DOMAINS = begin + by_env = { PRODUCTION_ENV_ID => [], STAGING_ENV_ID => [] } + SSOT_DATA.fetch("services").each do |svc| + domains = svc.fetch("domains") + prod = domains.fetch("prod") + staging = domains.fetch("staging") + by_env[PRODUCTION_ENV_ID] << prod unless prod.end_with?(".up.railway.app") + by_env[STAGING_ENV_ID] << staging unless staging.end_with?(".up.railway.app") + end + by_env.transform_values { |v| v.sort.uniq.freeze }.freeze + end + + # Canonical staging service names — used to validate the optional + # positional `bin/railway promote ` argument. Sourced from the + # same SSOT as EXPECTED_DOMAINS so they cannot drift. + STAGING_SERVICES = SSOT_DATA.fetch("services").map { |s| s.fetch("name") }.sort.freeze # Heuristic markers for env-scoped URLs (ignored in env-diff and promote). ENV_SCOPED_URL_MARKERS = [ @@ -134,6 +150,21 @@ module Railway end t end + + # GHCR bearer. Distinct from the Railway token (Auth.token). + # Resolution order: + # 1. GHCR_TOKEN — explicit PAT (local dev or CI override). + # 2. GITHUB_TOKEN — GitHub Actions automatic token (needs packages:read). + # Returns nil if no token is available; caller MUST refuse rather than + # silently fall through to anonymous (which works for public images but + # not for digest-existence verification against private repos). + def ghcr_token + t = ENV["GHCR_TOKEN"] + return t.strip if t && !t.strip.empty? + t = ENV["GITHUB_TOKEN"] + return t.strip if t && !t.strip.empty? + nil + end end # ── GraphQL Client ───────────────────────────────────────────────────────── @@ -197,7 +228,7 @@ module Railway "application/vnd.docker.distribution.manifest.v2+json", ].join(", ").freeze - def initialize(token: ENV["GHCR_TOKEN"], http: nil) + def initialize(token: Railway::Auth.ghcr_token, http: nil) @token = token @http = http end @@ -232,6 +263,44 @@ module Railway digest end + # Verify a digest-pinned image exists in GHCR. Returns: + # :exists — HEAD returned 200. + # :missing — HEAD returned 404 (digest GC'd or never built). + # :auth_failed — HEAD returned 401/403 (token missing/insufficient). + # Raises GHCR::Error on 5xx or transport failure. + # + # The caller MUST pass an image ref already pinned to @sha256:; + # this is the "P1 precondition" check from the showcase deploy spec, not + # a tag-resolution. We do not chase a tag here — we verify the exact + # bytes about to be pinned to prod. + def manifest_exists(image_ref) + parts = parse_image_ref(image_ref) + digest = parts[:digest] + raise ArgumentError, "manifest_exists requires a digest-pinned ref, got #{image_ref}" if digest.nil? + + org = parts[:org] + name = parts[:name] + + bearer = bearer_for(org, name) + url = "https://ghcr.io/v2/#{org}/#{name}/manifests/#{digest}" + + resp = + if @http + @http.call(method: :head, url: url, headers: headers(bearer)) + else + http_head(url, headers: headers(bearer)) + end + + status = resp[:status] + case status + when 200 then :exists + when 404 then :missing + when 401, 403 then :auth_failed + else + raise Error, "GHCR manifest HEAD #{status} for #{image_ref}" + end + end + # Parse image ref into { registry, org, name, tag, digest }. def parse_image_ref(ref) r = ref.to_s.strip @@ -380,7 +449,17 @@ module Railway # We capture KEYS only for env vars (never values) so snapshots are safe # to commit/share. class SnapshotIO - SCHEMA_VERSION = 1 + SCHEMA_VERSION = 2 + + # v1: initial fields (name, service_id, image, image_tag, digest, + # start_command, auto_updates_disabled, latest_deployment_id, + # env_keys, custom_domains). + # v2: adds healthcheck_path, region, replicas, restart_policy + # (for the P6 parity matrix in promote). We accept v1 reads + # for backwards-compat with historical committed snapshots — + # promote always uses live snapshots, but rollback-commit + # replays snapshots from arbitrary SHAs. + SUPPORTED_VERSIONS = [1, SCHEMA_VERSION].freeze def self.write(path, snapshot) FileUtils.mkdir_p(File.dirname(path)) unless path == "-" @@ -395,8 +474,8 @@ module Railway def self.read(path) raw = path == "-" ? $stdin.read : File.read(path) data = YAML.safe_load(raw, permitted_classes: [Time, Symbol], aliases: false) - unless data.is_a?(Hash) && data["version"] == SCHEMA_VERSION - Railway.die!("Snapshot schema mismatch (expected version=#{SCHEMA_VERSION}).") + unless data.is_a?(Hash) && SUPPORTED_VERSIONS.include?(data["version"]) + Railway.die!("Snapshot schema mismatch (expected version in #{SUPPORTED_VERSIONS.inspect}).") end data end @@ -448,6 +527,10 @@ module Railway serviceId environmentId startCommand + healthcheckPath + region + numReplicas + restartPolicyType source { image repo } latestDeployment { id status } domains { @@ -596,6 +679,10 @@ module Railway "image_tag" => tag_ref, "digest" => digest, "start_command" => inst["startCommand"], + "healthcheck_path" => inst["healthcheckPath"], + "region" => inst["region"], + "replicas" => inst["numReplicas"], + "restart_policy" => inst["restartPolicyType"], "auto_updates_disabled" => nil, "latest_deployment_id" => inst.dig("latestDeployment", "id"), "env_keys" => (keys_by_service[node["id"]] || []).sort.uniq, @@ -845,24 +932,133 @@ module Railway # promote — copy staging digests into production with prechecks. class PromoteCommand < BaseCommand + class MutationError < StandardError; end + + SERVICE_INSTANCE_RECHECK_QUERY = <<~GQL + query ServiceInstanceRecheck($serviceId: String!, $envId: String!) { + serviceInstance(serviceId: $serviceId, environmentId: $envId) { + id + source { image } + updatedAt + } + } + GQL + + RETRY_COUNT = 3 + RETRY_DELAY_SEC = 10 + + # Pin + verify: confirms the boolean mutation result AND re-queries + # serviceInstance to confirm BOTH source.image advanced to the new + # digest AND updatedAt strictly advanced past the pre-mutation value. + # 3 retries 10s apart absorb Railway's eventual consistency. + # `sleeper:` is injected for tests. + def self.pin_and_verify(gql, service_id:, env_id:, image:, sleeper: ->(n) { sleep(n) }) + # Upfront guard: this method's contract is to pin a DIGEST-form + # image. Pre-fix, a stray tag ref would fall through to retries + # (since expected_digest stayed nil and image_ok was always false) + # and ultimately surface as a misleading "did not observe image + # advance" error after 30s of futile waits. + unless image.include?("@sha256:") + raise ArgumentError, + "pin_and_verify requires an @sha256:-pinned image, got #{image.inspect}" + end + + # Capture pre-update serviceInstance.updatedAt so we can gate on a + # strict advance after the mutation (spec §7.2 P5). Image-equality + # alone is insufficient — a no-op re-pin to the current value would + # otherwise appear green. nil pre_ts means "no prior instance" and + # is treated as a permanent advance below. + pre_data = gql.query(SERVICE_INSTANCE_RECHECK_QUERY, + serviceId: service_id, envId: env_id) + pre_inst = pre_data && pre_data["serviceInstance"] + pre_update_ts = pre_inst && pre_inst["updatedAt"] + + updated = gql.query(RestoreCommand::UPDATE_IMAGE_MUTATION, + serviceId: service_id, envId: env_id, image: image) + unless updated && updated["serviceInstanceUpdate"] == true + raise MutationError, + "P5: serviceInstanceUpdate returned #{updated.inspect} (expected true) for #{service_id} -> #{image}" + end + + # Symmetric assertion: serviceInstanceUpdate has already advanced + # source.image+updatedAt, so a failed redeploy could otherwise + # sneak through verification. Require truthy redeploy result. + redeployed = gql.query(RestoreCommand::REDEPLOY_MUTATION, + serviceId: service_id, envId: env_id) + unless redeployed && redeployed["serviceInstanceRedeploy"] + raise MutationError, + "P5: serviceInstanceRedeploy returned #{redeployed.inspect} (expected truthy) for #{service_id} -> #{image}" + end + + expected_digest = image.include?("@") ? image.split("@", 2).last : nil + last_seen_image = nil + last_seen_ts = nil + + RETRY_COUNT.times do |i| + data = gql.query(SERVICE_INSTANCE_RECHECK_QUERY, + serviceId: service_id, envId: env_id) + inst = data && data["serviceInstance"] + actual_image = inst && inst.dig("source", "image") + actual_ts = inst && inst["updatedAt"] + actual_digest = actual_image && actual_image.include?("@") ? actual_image.split("@", 2).last : nil + last_seen_image = actual_image + last_seen_ts = actual_ts + + image_ok = expected_digest && actual_digest == expected_digest + # Non-vacuous timestamp gate: a non-nil observed updatedAt is + # ALWAYS required. Pre-fix this collapsed to (pre_ts.nil? || + # ...), which made the gate vacuous for new prod services and + # fell back to digest-equality alone — the exact weakness this + # gate was added to prevent. + ts_ok = actual_ts && (pre_update_ts.nil? || actual_ts > pre_update_ts) + return inst if image_ok && ts_ok + + sleeper.call(RETRY_DELAY_SEC) if i < RETRY_COUNT - 1 + end + + raise MutationError, + "P5: re-query did not observe image advance to #{image} AND updatedAt > #{pre_update_ts.inspect} " \ + "after #{RETRY_COUNT} retries (last seen image=#{last_seen_image.inspect}, " \ + "updatedAt=#{last_seen_ts.inspect}). Refusing to declare promote successful." + end + + def default_options + super.merge( + confirm_divergence: false, + require_staging_green: true, # default-on per spec §7.2 P3 + service: nil, # optional positional: restrict to ONE service + digest: nil, # override resolved prod-image ref (single-service only) + ) + end + def parser OptionParser.new do |o| o.banner = <<~BANNER - Usage: bin/railway promote [--include-startcommand] [--yes] [--non-interactive] [--dry-run] + Usage: bin/railway promote [SERVICE] [--digest REF] [--yes] [--non-interactive] [--dry-run] Promote staging snapshot to production. - MOVES: image digests; startCommand (only if --include-startcommand); + SERVICE (optional positional): restrict the promote to a single + staging service by name. Without it, the entire staging fleet + is promoted (interactive operator use). + --digest REF: pin SERVICE's prod image to REF instead of the + digest resolved from staging's :latest tag. Only valid with a + positional SERVICE; errors otherwise. + + MOVES: image digests (resolved to @sha256: at promote time); autoUpdate=disabled flag. VERIFY-REFUSE: service-set parity, critical env-key parity, - PB superuser auth, PB collection parity, - cross-env URL leak scan. + startCommand parity, PB superuser auth, + PB collection parity, cross-env URL leak scan. WARN: missing/extra custom domains, sealed-var heuristics. IGNORE: env-scoped URLs, volumes. Exit 0 on clean promotion, 1 on refuse/findings, 2 on error. BANNER - o.on("--include-startcommand") { options[:include_startcommand] = true } + o.on("--confirm-divergence", "Proceed past WARN findings (region/replicas/etc.)") { options[:confirm_divergence] = true } + o.on("--require-staging-green", "Require live staging probe green at promote time (default)") { options[:require_staging_green] = true } + o.on("--no-require-staging-green", "Skip live staging probe (NOT recommended)") { options[:require_staging_green] = false } + o.on("--digest REF", "Pin SERVICE's prod image to REF (requires positional SERVICE).") { |v| options[:digest] = v } o.on("--yes") { options[:yes] = true } o.on("--non-interactive") { options[:non_interactive] = true } o.on("--dry-run") { options[:dry_run] = true } @@ -873,47 +1069,140 @@ module Railway def run parser.parse!(argv) - # Capture both env snapshots. - staging = SnapshotCommand.new(["--env", "staging", "--dry-run"]).build_snapshot(STAGING_ENV_ID) - prod = SnapshotCommand.new(["--env", "production", "--dry-run"]).build_snapshot(PRODUCTION_ENV_ID) + # Optional positional service name; remaining argv after parse! is + # whatever the OptionParser left behind. The workflow shape is + # `promote [--digest REF]` — one positional only. + if argv.length > 1 + Railway.die!("promote: too many positional args #{argv.inspect}; expected at most one service name.") + end + options[:service] = argv.first if argv.first && !argv.first.empty? + + # --digest is meaningful only with a positional service. Without + # one it would otherwise silently promote the whole fleet pinned + # to one (likely wrong) digest — fail fast instead. + if options[:digest] && options[:service].nil? + Railway.die!("promote: --digest requires a positional service argument " \ + "(e.g. `bin/railway promote --digest `). " \ + "Refusing to promote the fleet with a single digest.") + end - findings = [] + # Validate the positional service against the SSOT + # (railway-envs.generated.json). Unknown names must hard-fail with + # the valid set listed so the operator can self-correct; falling + # through would silently fleet-promote (the original bug). + if options[:service] && !STAGING_SERVICES.include?(options[:service]) + Railway.die!("promote: unknown service #{options[:service].inspect}. " \ + "Valid staging services: #{STAGING_SERVICES.join(', ')}.") + end - # VERIFY: service-set parity. - s_names = (staging["services"] || []).map { |s| s["name"] }.sort - p_names = (prod["services"] || []).map { |s| s["name"] }.sort - missing_in_prod = s_names - p_names - missing_in_stg = p_names - s_names - findings << "REFUSE: services in staging not in prod: #{missing_in_prod.join(', ')}" unless missing_in_prod.empty? - findings << "REFUSE: services in prod not in staging: #{missing_in_stg.join(', ')}" unless missing_in_stg.empty? + capture_snapshots + + # When restricted to a single service, narrow PER-SERVICE views + # of both snapshots BEFORE preflight so per-service checks + # (P1..P3/P6, critical env keys, execute_promotion) operate + # only on the targeted service. FLEET-SCOPED invariants + # (check_expected_prod_domains, check_service_set_parity) MUST + # continue to evaluate against the FULL un-narrowed snapshots + # — they describe properties of the fleet itself, not the + # promote target. Co-narrowing them produces two failure + # modes that the per-service workflow can't tolerate: + # + # (1) check_expected_prod_domains diffs the fleet-wide + # EXPECTED_DOMAINS against the union of custom_domains + # in `prod["services"]`. Narrowed prod carries at most + # one service's domains → the other ~4 fleet hosts look + # "missing" → spurious WARN → run_with_preflight_only + # refuses (workflow doesn't pass --confirm-divergence). + # (2) check_service_set_parity is a fleet-shape invariant + # (no env has services the other lacks); evaluating it + # on the narrowed pair makes it tautological when both + # contain the same single name, and only accidentally + # fires in the target-absent-from-prod case. + # + # We therefore retain references to the full snapshots and + # only narrow when the per-service branch is taken. The + # fleet-scoped checks always read @full_*_snapshot; everything + # else reads @staging_snapshot/@prod_snapshot (narrowed when + # a positional service is given, full otherwise). + @full_staging_snapshot = @staging_snapshot + @full_prod_snapshot = @prod_snapshot + if options[:service] + narrow_snapshots_to_single_service!(options[:service]) + end - # VERIFY: critical env-key parity per service. - (staging["services"] || []).each do |svc| - pmatch = Railway.find_service(prod, svc["name"]) - next unless pmatch - missing_keys = (CRITICAL_ENV_KEYS & svc["env_keys"]) - pmatch["env_keys"] - unless missing_keys.empty? - findings << "REFUSE: #{svc['name']}: critical keys missing in prod: #{missing_keys.join(', ')}" - end + run_with_preflight_only + end + + # Narrow @staging_snapshot and @prod_snapshot to only the named + # service. Staging presence is mandatory (validated against SSOT + # already); prod absence is tolerated here so the fleet-scoped + # service-set-parity REFUSE (now reading @full_prod_snapshot) can + # surface as the user-facing error rather than a silent rc=0 + # 'success' (find_service returns nil, so execute_promotion would + # otherwise skip the absent service with no mutation). + def narrow_snapshots_to_single_service!(name) + staging_match = (@staging_snapshot["services"] || []).select { |s| s["name"] == name } + if staging_match.empty? + Railway.die!("promote: service #{name.inspect} not present in staging snapshot " \ + "(SSOT lists it but Railway env returned no instance).") end + @staging_snapshot = @staging_snapshot.merge("services" => staging_match) + prod_match = (@prod_snapshot["services"] || []).select { |s| s["name"] == name } + @prod_snapshot = @prod_snapshot.merge("services" => prod_match) + end + + # Test seam — capture_snapshots may be skipped by injecting + # @staging_snapshot / @prod_snapshot directly. + def capture_snapshots + @staging_snapshot ||= SnapshotCommand.new(["--env", "staging", "--dry-run"]).build_snapshot(STAGING_ENV_ID) + @prod_snapshot ||= SnapshotCommand.new(["--env", "production", "--dry-run"]).build_snapshot(PRODUCTION_ENV_ID) + end - # WARN: domain audits. - expected_prod_domains = EXPECTED_DOMAINS[PRODUCTION_ENV_ID] - actual_prod_domains = (prod["services"] || []).flat_map { |s| s["custom_domains"] || [] }.uniq.sort - missing_domains = expected_prod_domains - actual_prod_domains - findings << "WARN: production missing expected custom domains: #{missing_domains.join(', ')}" unless missing_domains.empty? + # All preconditions, then (if clean) the actual promote. + def run_with_preflight_only + findings = [] - # WARN: cross-env URL leak in env-key NAMES is impossible (we have names only); - # we cannot read values without an explicit per-key fetch. Log as IGNORE note. + findings.concat(check_p1_ghcr_digests(@staging_snapshot)) + findings.concat(check_p2_staging_deployments(@staging_snapshot)) + findings.concat(check_p3_staging_live_green(@staging_snapshot)) + findings.concat(check_p6_parity(@staging_snapshot, @prod_snapshot)) + # FLEET-SCOPED invariants — must see the FULL fleet so they + # produce the same verdict regardless of whether this run is + # full-fleet or restricted to a single service. See `run` for + # the @full_*_snapshot rationale. When `run` was bypassed + # (test seam — preflight invoked directly without capture), + # fall back to the current snapshot views so legacy tests + # that only set @staging_snapshot/@prod_snapshot continue to + # work (full-fleet semantics = full == narrowed). + full_staging = @full_staging_snapshot || @staging_snapshot + full_prod = @full_prod_snapshot || @prod_snapshot + findings.concat(check_service_set_parity(full_staging, full_prod)) + findings.concat(check_critical_env_key_parity(@staging_snapshot, @prod_snapshot)) + findings.concat(check_expected_prod_domains(full_prod)) + + # Mandatory parity-NOTE that env var VALUES are not compared. + puts "NOTE: env var VALUES are not compared between staging and prod " \ + "(intentional — staging and prod hold different secrets/URLs). " \ + "Only the set of keys is compared." refuses = findings.select { |f| f.start_with?("REFUSE") } + warns = findings.select { |f| f.start_with?("WARN") } + unless refuses.empty? refuses.each { |f| puts f } puts "Promote refused due to #{refuses.size} REFUSE finding(s)." return 1 end - findings.each { |f| puts f } + unless warns.empty? + warns.each { |f| puts f } + unless options[:confirm_divergence] + puts "Promote refused: #{warns.size} WARN finding(s). " \ + "Re-run with --confirm-divergence after inspecting." + return 1 + end + puts "[--confirm-divergence set] proceeding past #{warns.size} WARN finding(s)." + end Railway.confirm_destructive!( env_label: "production", @@ -922,23 +1211,373 @@ module Railway yes: options[:yes], ) - # Execute promotion: redeploy each prod service with staging's image. + execute_promotion(@staging_snapshot, @prod_snapshot) + end + + # Resolve a staging service's image to the DIGEST-form ref that should + # be pinned to prod. The showcase deploy model is STAGING = mutable + # `:latest` tag, PROD = immutable `@sha256:`. Pinning prod to + # a tag would defeat the entire pipeline invariant — and verify-railway- + # image-refs PROD_SHAPE would later REFUSE. + # + # Returns the canonical `@sha256:` ref. + # Returns nil if the staging tag cannot be resolved (e.g. GHCR 404 / + # auth failed); the caller MUST refuse rather than pin a tag. + def resolved_prod_image(svc) + # --digest override: when the operator supplied an explicit ref AND + # we are restricted to a single service, use that ref verbatim. The + # `run` entry-point already enforces that --digest requires a + # positional service, and snapshots are narrowed to that service + # before preflight, so `svc["name"] == options[:service]` here. + if options[:digest] && options[:service] && svc["name"] == options[:service] + return options[:digest] + end + + image = svc["image"] || svc["image_tag"] + return nil if image.nil? || image.empty? + return image if image.include?("@sha256:") # already pinned + + digest = ghcr.resolve_digest(image) + return nil if digest.nil? + + # Strip the tag (`:latest`) and append the digest. parse_image_ref + # returns the structured parts; rebuild the canonical pinned ref. + parts = ghcr.parse_image_ref(image) + registry = parts[:registry] || "ghcr.io" + "#{registry}/#{parts[:org]}/#{parts[:name]}@#{digest}" + end + + # P1 — every staging digest about to be promoted must exist in GHCR. + # Verifies the DIGEST-form ref that will actually be pinned to prod + # (resolves tag refs first; refuses if the tag can't be resolved). + # + # Side-effect: populates @promote_refs (service_name => digest-pinned + # ref) so execute_promotion pins the EXACT ref P1 verified — staging + # `:latest` is mutable, so a second resolve_digest could return a + # different digest (TOCTOU) and prod would be pinned to a digest P1 + # never verified. + def check_p1_ghcr_digests(staging) + findings = [] + # RESET (not memoize): a reused PromoteCommand instance running a + # second preflight against a different snapshot must not carry + # stale A-era refs into a B-era promote. + @promote_refs = {} + (staging["services"] || []).each do |svc| + image = svc["image"] || svc["image_tag"] + if image.nil? || image.empty? + # Make this loud — pre-fix this was a silent `next`, and + # execute_promotion later emitted a misleading "internal + # error" for the same service. + findings << "REFUSE: P1 (#{svc['name']}): no image recorded in staging snapshot; " \ + "cannot promote." + next + end + + # Per-service rescue: an error on one service must not + # discard findings already accumulated for earlier services. + # Broadened to StandardError — a non-GHCR error (e.g. an + # ArgumentError from parse_image_ref, or a network error + # wrapped as something else) would otherwise bypass the + # per-service rescue and crash the whole loop. + begin + resolved = resolved_prod_image(svc) + if resolved.nil? + findings << "REFUSE: P1 (#{svc['name']}): cannot resolve #{image} to a GHCR digest; " \ + "refusing to pin prod to a mutable tag." + next + end + + case ghcr.manifest_exists(resolved) + when :exists + # Record the verified ref for use by execute_promotion + # — guarantees pin parity with what P1 verified. + @promote_refs[svc["name"]] = resolved + when :missing + findings << "REFUSE: P1 (#{svc['name']}): #{resolved} not found in GHCR " \ + "(digest may have been garbage-collected; staging build may not have pushed)." + when :auth_failed + findings << "REFUSE: P1 (#{svc['name']}): GHCR auth failed for #{resolved}. " \ + "Set GHCR_TOKEN (local: 'gh auth token') or GITHUB_TOKEN (CI: workflow token with packages:read)." + end + rescue StandardError => e + findings << "REFUSE: P1 (#{svc['name']}): unexpected #{e.class}: #{e.message}" + end + end + findings + end + + # P2 — for each staging service we are promoting, the most recent + # staging deployment must be SUCCESS AND its image digest must match + # the digest we are about to promote (otherwise a newer build is + # in flight; refuse to avoid the race). + def check_p2_staging_deployments(staging) + findings = [] + (staging["services"] || []).each do |svc| + next if svc["service_id"].nil? || svc["image"].nil? + deployments = fetch_latest_staging_deployments(svc["service_id"]) + latest = deployments.first + if latest.nil? + findings << "REFUSE: P2 (#{svc['name']}): no staging deployments found." + next + end + status = latest["status"] + if status != "SUCCESS" + findings << "REFUSE: P2 (#{svc['name']}): latest staging deployment status is #{status}, not SUCCESS." + next + end + # Railway's Deployment.meta is a JSON scalar that may deserialize + # as a String (not a Hash). Parse it first; fall back to the + # WARN branch only if it is still non-Hash after the parse. + meta = latest["meta"] + meta = (JSON.parse(meta) rescue meta) if meta.is_a?(String) + if meta.is_a?(Hash) + deployed_image = meta["image"].to_s + deployed_digest = deployed_image.include?("@") ? deployed_image.split("@", 2).last : nil + # Compare against the digest P1 resolved+verified (recorded + # in @promote_refs), NOT svc["digest"] — staging images are + # tag-form, so svc["digest"] is nil and the race-check would + # otherwise be dead code. P2 runs after P1 in the preflight, + # so the entry must exist; if it doesn't, P1 has already + # REFUSEd this service and we skip the race comparison. + promote_ref = (@promote_refs || {})[svc["name"]] + promote_digest = promote_ref && promote_ref.include?("@sha256:") ? promote_ref.split("@", 2).last : nil + if promote_digest && deployed_digest && deployed_digest != promote_digest + findings << "REFUSE: P2 (#{svc['name']}): in-flight race — latest staging deployment is " \ + "#{deployed_digest} but P1 resolved #{promote_digest}. Re-snapshot and retry." + end + else + findings << "WARN: P2 (#{svc['name']}): in-flight race check skipped — " \ + "deployment meta is #{meta.class}, expected Hash." + end + end + findings + end + + def fetch_latest_staging_deployments(service_id) + data = gql.query(RollbackCommand::DEPLOYMENTS_QUERY, + serviceId: service_id, envId: STAGING_ENV_ID) + nodes = (data.dig("deployments", "edges") || []).map { |e| e["node"] } + # Railway's deployments query has no explicit order; sort client-side + # by createdAt descending so `.first` is genuinely the newest. nil + # createdAt sorts last (treated as oldest). + nodes.sort_by { |n| n["createdAt"].to_s }.reverse + end + + # P3 — re-probe staging live at promote time. Authoritative; CI history + # is not, because showcase_deploy.yml uses cancel-in-progress. + # Default-on; can be disabled with --no-require-staging-green. + def check_p3_staging_live_green(staging) + unless options[:require_staging_green] + puts "P3 SKIPPED (--no-require-staging-green set; staging is NOT being live-re-probed)." + return [] + end + services = (staging["services"] || []).map { |s| s["name"] }.compact.uniq + return [] if services.empty? + result = run_staging_probe(services: services) + return [] if result[:ok] + ["REFUSE: P3: staging is not green for #{services.join(', ')}: #{result[:summary]}"] + end + + # Shell out to Workstream A's parameterized probe entrypoint. + # Contract: exit 0 = green, non-zero = red; stdout is the human summary. + # + # Explicitly forward RAILWAY_TOKEN (the probe enumerates Railway state + # over GraphQL) and GHCR_TOKEN / GITHUB_TOKEN (the probe may need to + # cross-check GHCR for the digest under test). All three are inherited + # from the parent process env when set; the explicit hash below makes + # the dependency visible and survives any future child-env sanitizing. + def run_staging_probe(services:) + probe_bin = File.expand_path("../scripts/verify-deploy.ts", __dir__) + unless File.exist?(probe_bin) + return { ok: false, summary: "verify-deploy.ts not found at #{probe_bin} — Workstream A dependency missing." } + end + services_arg = services.join(",") + child_env = { + "RAILWAY_TOKEN" => ENV["RAILWAY_TOKEN"], + "GHCR_TOKEN" => ENV["GHCR_TOKEN"], + "GITHUB_TOKEN" => ENV["GITHUB_TOKEN"], + "PATH" => ENV["PATH"], + "HOME" => ENV["HOME"], + }.compact + # Use `tsx` (workspace dev dependency) for stdlib-free TS execution. + # IO.popen preserves a clean child env (Kernel#`` would inherit the + # parent shell verbatim — fine, but explicit is better for audit). + # Wrap the launch in a rescue: a missing `npx` (Errno::ENOENT) or + # any other spawn-time failure should produce a clean REFUSE + # rather than a raw stack trace bubbling up out of P3. + begin + output = IO.popen(child_env, ["npx", "--yes", "tsx", probe_bin, + "--env", "staging", + "--services", services_arg, + err: [:child, :out]]) { |io| io.read } + rescue Errno::ENOENT, StandardError => e + return { ok: false, summary: "staging probe failed to launch: #{e.class}: #{e.message}" } + end + ok = $?.exitstatus == 0 + { ok: ok, summary: output.lines.last(10).join.strip } + end + + # P6 — parity matrix. + # REFUSE: startCommand, healthcheckPath, image shape. + # WARN: region, replicas, restartPolicy, env-var KEY set. + # IGNORE: env-var VALUES (printed as NOTE every run; see + # run_with_preflight_only). + def check_p6_parity(staging, prod) + findings = [] + (staging["services"] || []).each do |svc| + pmatch = Railway.find_service(prod, svc["name"]) + next unless pmatch # service-set parity catches this separately. + name = svc["name"] + + # REFUSE — startCommand + if svc["start_command"] != pmatch["start_command"] + findings << "REFUSE: P6 (#{name}): startCommand divergence " \ + "(staging=#{svc['start_command'].inspect} prod=#{pmatch['start_command'].inspect})" + end + + # REFUSE — healthcheckPath + if svc["healthcheck_path"] != pmatch["healthcheck_path"] + findings << "REFUSE: P6 (#{name}): healthcheckPath divergence " \ + "(staging=#{svc['healthcheck_path'].inspect} prod=#{pmatch['healthcheck_path'].inspect})" + end + + # REFUSE — image shape (staging=:tag mutable, prod=@sha256 pinned). + staging_shape = image_shape(svc["image"]) + prod_shape = image_shape(pmatch["image"]) + expected_staging_shape = :tag + expected_prod_shape = :digest + if staging_shape != expected_staging_shape || prod_shape != expected_prod_shape + findings << "REFUSE: P6 (#{name}): image shape wrong " \ + "(staging=#{staging_shape} expected=#{expected_staging_shape}; " \ + "prod=#{prod_shape} expected=#{expected_prod_shape})" + end + + # WARN — region + if svc["region"] != pmatch["region"] + findings << "WARN: P6 (#{name}): region divergence " \ + "(staging=#{svc['region'].inspect} prod=#{pmatch['region'].inspect})" + end + + # WARN — replicas + if svc["replicas"] != pmatch["replicas"] + findings << "WARN: P6 (#{name}): replicas divergence " \ + "(staging=#{svc['replicas']} prod=#{pmatch['replicas']})" + end + + # WARN — restartPolicy + if svc["restart_policy"] != pmatch["restart_policy"] + findings << "WARN: P6 (#{name}): restartPolicy divergence " \ + "(staging=#{svc['restart_policy'].inspect} prod=#{pmatch['restart_policy'].inspect})" + end + + # WARN — env var KEY set + staging_keys = (svc["env_keys"] || []).sort + prod_keys = (pmatch["env_keys"] || []).sort + if staging_keys != prod_keys + only_staging = staging_keys - prod_keys + only_prod = prod_keys - staging_keys + findings << "WARN: P6 (#{name}): env key set divergence " \ + "(only-in-staging=#{only_staging.inspect} only-in-prod=#{only_prod.inspect})" + end + end + findings + end + + # Classify an image ref. :tag (mutable), :digest (immutable @sha256:), + # :missing, or :other. + def image_shape(ref) + return :missing if ref.nil? || ref.empty? + return :digest if ref.include?("@sha256:") + return :tag if ref.include?(":") && ref.split(":", 2).last !~ /\A\s*\z/ + :other + end + + def check_service_set_parity(staging, prod) + findings = [] + s_names = (staging["services"] || []).map { |s| s["name"] }.sort + p_names = (prod["services"] || []).map { |s| s["name"] }.sort + findings << "REFUSE: services in staging not in prod: #{(s_names - p_names).join(', ')}" unless (s_names - p_names).empty? + findings << "REFUSE: services in prod not in staging: #{(p_names - s_names).join(', ')}" unless (p_names - s_names).empty? + findings + end + + def check_critical_env_key_parity(staging, prod) + findings = [] (staging["services"] || []).each do |svc| pmatch = Railway.find_service(prod, svc["name"]) next unless pmatch - image = svc["image"] || svc["image_tag"] + missing = (CRITICAL_ENV_KEYS & (svc["env_keys"] || [])) - (pmatch["env_keys"] || []) + findings << "REFUSE: #{svc['name']}: critical env keys missing in prod: #{missing.join(', ')}" unless missing.empty? + end + findings + end + + def check_expected_prod_domains(prod) + expected = EXPECTED_DOMAINS[PRODUCTION_ENV_ID] || [] + actual = (prod["services"] || []).flat_map { |s| s["custom_domains"] || [] }.uniq.sort + missing = expected - actual + return [] if missing.empty? + ["WARN: production missing expected custom domains: #{missing.join(', ')}"] + end + + def execute_promotion(staging, prod) + # Hard guard: @promote_refs is populated by check_p1_ghcr_digests. + # If it is nil, preflight did not run — refusing to silently pin + # nothing (which a memoize default `||= {}` would have done). + raise "internal error: execute_promotion invoked without preflight (@promote_refs nil)" if @promote_refs.nil? + + # Pre-validation pass: every prod-matched staging service MUST + # have a digest-shaped @promote_refs entry BEFORE we pin anything. + # Pre-fix, a missing entry was detected lazily mid-loop, which + # could leave production partially-promoted (some services pinned + # to new digest, others still on prior digest). Fail fast here. + missing = [] + (staging["services"] || []).each do |svc| + next unless Railway.find_service(prod, svc["name"]) + ref = @promote_refs[svc["name"]] + if ref.nil? || !ref.include?("@sha256:") + missing << "#{svc['name']}=#{ref.inspect}" + end + end + unless missing.empty? + warn "REFUSE: promote: P1-verified digest ref missing or non-digest for " \ + "#{missing.join(', ')} (internal error — preflight did not capture these services). " \ + "Refusing to pin (no mutations issued)." + return 1 + end + + already_pinned = [] + (staging["services"] || []).each do |svc| + pmatch = Railway.find_service(prod, svc["name"]) + next unless pmatch + # Use the EXACT ref P1 resolved+verified. Re-resolving here + # would risk TOCTOU on staging's mutable `:latest`. + image = @promote_refs[svc["name"]] if options[:dry_run] puts "[dry-run] promote #{svc['name']} -> #{image}" next end - - RestoreCommand.pin_and_redeploy(gql, - service_id: pmatch["service_id"], - env_id: PRODUCTION_ENV_ID, - image: image) - puts "promoted #{svc['name']} -> #{image}" + begin + PromoteCommand.pin_and_verify(gql, + service_id: pmatch["service_id"], + env_id: PRODUCTION_ENV_ID, + image: image) + puts "promoted #{svc['name']} -> #{image}" + already_pinned << svc["name"] + # Broadened rescue: a transient GraphQL or network error + # mid-loop must NOT crash the script (which would lose the + # PARTIAL-PROMOTION report — its entire reason for existing). + rescue PromoteCommand::MutationError, Railway::GraphQL::Error, StandardError => e + warn "PARTIAL PROMOTION: already pinned #{already_pinned.inspect}; " \ + "FAILED on #{svc['name']}: #{e.class}: #{e.message}. " \ + "Production is in a mixed state — note that serviceInstanceUpdate " \ + "runs BEFORE serviceInstanceRedeploy, so #{svc['name']}'s prod " \ + "source.image may already be partially advanced on Railway's side. " \ + "Run 'bin/railway rollback-commit' against the prior snapshot to revert, " \ + "or re-run promote to retry." + return 1 + end end - 0 end end diff --git a/showcase/bin/spec/test_cli_parsing.rb b/showcase/bin/spec/test_cli_parsing.rb index a086b55f41f..5f3e257e168 100644 --- a/showcase/bin/spec/test_cli_parsing.rb +++ b/showcase/bin/spec/test_cli_parsing.rb @@ -58,9 +58,9 @@ def test_envdiff_requires_two_args end def test_promote_flags_parse - c = Railway::PromoteCommand.new(["--include-startcommand", "--yes", "--dry-run"]) + c = Railway::PromoteCommand.new(["--confirm-divergence", "--yes", "--dry-run"]) c.parser.parse!(c.argv) - assert c.options[:include_startcommand] + assert c.options[:confirm_divergence] assert c.options[:yes] assert c.options[:dry_run] end diff --git a/showcase/bin/spec/test_expected_domains_parity.rb b/showcase/bin/spec/test_expected_domains_parity.rb new file mode 100644 index 00000000000..3c5b3172989 --- /dev/null +++ b/showcase/bin/spec/test_expected_domains_parity.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +require_relative "spec_helper" +require "json" + +# Parity test: Ruby's Railway::EXPECTED_DOMAINS (derived at load time from +# showcase/scripts/railway-envs.generated.json) MUST equal the public-host +# subset computed directly from the same TS SSOT artifact. If this test fails, +# either the JSON artifact is stale or bin/railway's derivation logic drifted +# from the TS SSOT shape. +class ExpectedDomainsParityTest < Minitest::Test + SSOT_JSON = File.expand_path("../../scripts/railway-envs.generated.json", __dir__) + + def test_generated_json_exists + assert File.exist?(SSOT_JSON), + "expected SSOT artifact at #{SSOT_JSON} — run " \ + "`npx tsx showcase/scripts/emit-railway-envs-json.ts`" + end + + def test_ruby_expected_domains_matches_ts_ssot_public_hosts + data = JSON.parse(File.read(SSOT_JSON)) + prod_env_id = data.fetch("envIds").fetch("prod") + staging_env_id = data.fetch("envIds").fetch("staging") + + expected_prod = data.fetch("services") + .map { |s| s.fetch("domains").fetch("prod") } + .reject { |h| h.end_with?(".up.railway.app") } + .sort + .uniq + expected_staging = data.fetch("services") + .map { |s| s.fetch("domains").fetch("staging") } + .reject { |h| h.end_with?(".up.railway.app") } + .sort + .uniq + + # Sanity: the env-id constants in the Ruby file must match the SSOT. + assert_equal prod_env_id, Railway::PRODUCTION_ENV_ID, + "PRODUCTION_ENV_ID drifted from SSOT envIds.prod" + assert_equal staging_env_id, Railway::STAGING_ENV_ID, + "STAGING_ENV_ID drifted from SSOT envIds.staging" + + actual = Railway::EXPECTED_DOMAINS + assert_equal expected_prod, actual.fetch(prod_env_id).sort, + "Ruby EXPECTED_DOMAINS[prod] != TS SSOT public prod hosts" + assert_equal expected_staging, actual.fetch(staging_env_id).sort, + "Ruby EXPECTED_DOMAINS[staging] != TS SSOT public staging hosts" + end + + def test_expected_domains_keys_are_only_prod_and_staging_env_ids + keys = Railway::EXPECTED_DOMAINS.keys.sort + assert_equal [Railway::PRODUCTION_ENV_ID, Railway::STAGING_ENV_ID].sort, keys + end +end diff --git a/showcase/bin/spec/test_ghcr_manifest_exists.rb b/showcase/bin/spec/test_ghcr_manifest_exists.rb new file mode 100644 index 00000000000..fc2b92e25ff --- /dev/null +++ b/showcase/bin/spec/test_ghcr_manifest_exists.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +require_relative "spec_helper" + +class GHCRManifestExistsTest < Minitest::Test + class FakeHTTP + def initialize(responses) + @responses = responses + end + + def call(method:, url:, headers: {}) + key = [method, url] + r = @responses[key] || @responses[url] + raise "no fake response for #{key.inspect}" unless r + r + end + end + + DIGEST = "sha256:cafef00dcafef00dcafef00dcafef00dcafef00dcafef00dcafef00dcafef00d" + REF = "ghcr.io/copilotkit/showcase-shell@#{DIGEST}" + URL = "https://ghcr.io/v2/copilotkit/showcase-shell/manifests/#{DIGEST}" + + def test_manifest_exists_returns_exists_on_200 + fake = FakeHTTP.new(URL => { status: 200, headers: {}, body: "" }) + g = Railway::GHCR.new(token: "x", http: fake) + assert_equal :exists, g.manifest_exists(REF) + end + + def test_manifest_exists_returns_missing_on_404 + fake = FakeHTTP.new(URL => { status: 404, headers: {}, body: "" }) + g = Railway::GHCR.new(token: "x", http: fake) + assert_equal :missing, g.manifest_exists(REF) + end + + def test_manifest_exists_returns_auth_failed_on_401_403 + fake401 = FakeHTTP.new(URL => { status: 401, headers: {}, body: "" }) + fake403 = FakeHTTP.new(URL => { status: 403, headers: {}, body: "" }) + assert_equal :auth_failed, Railway::GHCR.new(token: "x", http: fake401).manifest_exists(REF) + assert_equal :auth_failed, Railway::GHCR.new(token: "x", http: fake403).manifest_exists(REF) + end + + def test_manifest_exists_raises_on_5xx + fake = FakeHTTP.new(URL => { status: 500, headers: {}, body: "boom" }) + g = Railway::GHCR.new(token: "x", http: fake) + assert_raises(Railway::GHCR::Error) { g.manifest_exists(REF) } + end + + def test_manifest_exists_requires_pinned_digest + # An unpinned tag is a programmer error here — we are verifying the + # CONCRETE digest we are about to promote, not resolving a tag. + g = Railway::GHCR.new(token: "x") + assert_raises(ArgumentError) do + g.manifest_exists("ghcr.io/copilotkit/showcase-shell:latest") + end + end +end diff --git a/showcase/bin/spec/test_ghcr_token.rb b/showcase/bin/spec/test_ghcr_token.rb new file mode 100644 index 00000000000..f8d363a4e88 --- /dev/null +++ b/showcase/bin/spec/test_ghcr_token.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +require_relative "spec_helper" + +class GHCRTokenTest < Minitest::Test + def setup + @prior_github = ENV.delete("GITHUB_TOKEN") + @prior_ghcr = ENV.delete("GHCR_TOKEN") + @prior_railway = ENV.delete("RAILWAY_TOKEN") + end + + def teardown + # Unconditionally delete any test-set values so they don't leak across + # tests, THEN re-set the saved priors if those were present. + ENV.delete("GITHUB_TOKEN") + ENV.delete("GHCR_TOKEN") + ENV.delete("RAILWAY_TOKEN") + ENV["GITHUB_TOKEN"] = @prior_github if @prior_github + ENV["GHCR_TOKEN"] = @prior_ghcr if @prior_ghcr + ENV["RAILWAY_TOKEN"] = @prior_railway if @prior_railway + end + + def test_prefers_explicit_ghcr_token + ENV["GITHUB_TOKEN"] = "ci-token" + ENV["GHCR_TOKEN"] = "explicit-pat" + assert_equal "explicit-pat", Railway::Auth.ghcr_token + end + + def test_falls_back_to_github_token_in_ci + ENV["GITHUB_TOKEN"] = "ci-token" + assert_equal "ci-token", Railway::Auth.ghcr_token + end + + def test_returns_nil_when_no_token_available + # No GH_AUTH_TOKEN shim, no env vars: nil (caller decides to refuse). + assert_nil Railway::Auth.ghcr_token + end + + def test_does_not_return_railway_token + ENV["RAILWAY_TOKEN"] = "railway-bearer" + assert_nil Railway::Auth.ghcr_token + ensure + ENV.delete("RAILWAY_TOKEN") + end +end diff --git a/showcase/bin/spec/test_promote_cr_fixes.rb b/showcase/bin/spec/test_promote_cr_fixes.rb new file mode 100644 index 00000000000..fcf4f1d2366 --- /dev/null +++ b/showcase/bin/spec/test_promote_cr_fixes.rb @@ -0,0 +1,401 @@ +# frozen_string_literal: true + +require_relative "spec_helper" + +# Covers nine CR fixes against PromoteCommand: +# FIX-1: @promote_refs is RESET per check_p1_ghcr_digests run (not memoized +# across A→B preflight invocations), and execute_promotion HARD-GUARDS +# against a nil @promote_refs. +# FIX-2: P2 in-flight race compares deployed_digest vs the digest portion of +# @promote_refs[name] (not the snapshot's tag-form nil `digest`). +# Also: JSON-string `meta` is parsed (not WARN-skipped); and +# fetch_latest_staging_deployments sorts createdAt-desc so `.first` +# is genuinely latest. +# FIX-3: execute_promotion pre-validates ALL prod-matched services have a +# digest-shaped @promote_refs entry BEFORE pinning anything. +# FIX-4: execute_promotion rescue broadens to MutationError + GraphQL::Error +# + StandardError, retains PARTIAL-PROMOTION report, and drops the +# duplicate `warn e.message`. +# FIX-5: check_p1_ghcr_digests emits REFUSE: P1 ... "no image" when staging +# service has nil/empty image (instead of silent skip). +# FIX-6: P1 per-service rescue broadens to StandardError so non-GHCR errors +# don't bypass the loop. +# FIX-7: pin_and_verify raises ArgumentError immediately when called with a +# tag-form (non-digest) image. +# FIX-8: pin_and_verify timestamp gate is non-vacuous: a nil observed +# updatedAt does NOT declare success, even when pre_update_ts is nil. +# FIX-9: run_staging_probe rescues launch failures (Errno::ENOENT, etc.) +# and returns ok:false with a descriptive summary. +class PromoteCRFixesTest < Minitest::Test + # ----- shared fakes ----- + + class NullGQL + def query(*); {}; end + end + + # GQL that records calls and returns canned responses for P2/preflight only + # (no mutations). + class P2GQL + attr_reader :calls + def initialize(deployments_by_svc) + @deployments_by_svc = deployments_by_svc + @calls = [] + end + def query(q, vars = {}) + @calls << [q, vars] + if q.include?("query Deployments") + edges = (@deployments_by_svc[vars[:serviceId]] || []).map { |n| { "node" => n } } + return { "deployments" => { "edges" => edges } } + end + {} + end + end + + # GHCR fake that always reports :exists and resolves any :latest -> a digest. + class PassGHCR + def initialize(resolve_map: {}); @resolve_map = resolve_map; end + def resolve_digest(ref) + return ref.split("@", 2).last if ref.include?("@sha256:") + @resolve_map[ref] || "sha256:resolved_for_#{ref}" + end + def manifest_exists(_); :exists; end + def parse_image_ref(ref); Railway::GHCR.allocate.parse_image_ref(ref); end + end + + def make_svc(name, image:) + { + "name" => name, "service_id" => "svc-stg-#{name}", + "image" => image, + "env_keys" => [], + "start_command" => "node server.js", "healthcheck_path" => "/health", + "region" => "us-west", "replicas" => 1, "restart_policy" => "ON_FAILURE", + } + end + + # =================== FIX-1: @promote_refs is reset, not memoized =================== + + def test_fix1_promote_refs_resets_between_p1_runs + # Reuse a single PromoteCommand instance across two distinct snapshots + # (snapshot A and snapshot B). After the second P1 run, @promote_refs + # must reflect ONLY snapshot B's services — no stale A entries. + cmd = Railway::PromoteCommand.new([]) + cmd.instance_variable_set(:@ghcr, PassGHCR.new) + + snapshot_a = { "services" => [make_svc("alpha", image: "ghcr.io/copilotkit/alpha:latest")] } + snapshot_b = { "services" => [make_svc("beta", image: "ghcr.io/copilotkit/beta:latest")] } + + cmd.send(:check_p1_ghcr_digests, snapshot_a) + refs_after_a = cmd.instance_variable_get(:@promote_refs).keys.sort + assert_equal ["alpha"], refs_after_a + + cmd.send(:check_p1_ghcr_digests, snapshot_b) + refs_after_b = cmd.instance_variable_get(:@promote_refs).keys.sort + assert_equal ["beta"], refs_after_b, + "stale entries from snapshot A must be cleared; got #{refs_after_b.inspect}" + end + + def test_fix1_execute_promotion_raises_when_promote_refs_nil + # execute_promotion called WITHOUT a prior preflight must raise an + # internal-error exception (not silently treat refs as empty). + cmd = Railway::PromoteCommand.new([]) + cmd.instance_variable_set(:@ghcr, PassGHCR.new) + # Deliberately do NOT call check_p1_ghcr_digests; @promote_refs stays nil. + staging = { "services" => [make_svc("x", image: "ghcr.io/copilotkit/x:latest")] } + prod = { "services" => [{ "name" => "x", "service_id" => "svc-prod-x", + "image" => "ghcr.io/copilotkit/x@sha256:OLD" }] } + err = assert_raises(RuntimeError) do + cmd.send(:execute_promotion, staging, prod) + end + assert_match(/internal error.*execute_promotion.*preflight/i, err.message) + end + + # =================== FIX-2: P2 race-check is alive =================== + + def test_fix2_p2_race_check_uses_promote_refs_not_snapshot_digest + # Staging service is tag-form (so svc["digest"] is nil — pre-fix the + # race-check was DEAD CODE). After fix: P2 compares against the digest + # captured in @promote_refs[name]. + cmd = Railway::PromoteCommand.new([]) + cmd.instance_variable_set(:@promote_refs, { + "x" => "ghcr.io/copilotkit/x@sha256:YYY", + }) + cmd.define_singleton_method(:fetch_latest_staging_deployments) do |_svc_id| + [{ "id" => "d1", "status" => "SUCCESS", + "meta" => { "image" => "ghcr.io/copilotkit/x@sha256:XXX" }, + "createdAt" => "2026-05-28T01:00:00Z" }] + end + staging = { "services" => [{ + "name" => "x", "service_id" => "svc-1", + "image" => "ghcr.io/copilotkit/x:latest", # tag-form; no "digest" + "env_keys" => [], + }] } + findings = cmd.send(:check_p2_staging_deployments, staging) + assert(findings.any? { |f| f =~ /REFUSE: P2 \(x\).*in-flight.*sha256:XXX.*sha256:YYY/ }, + "expected REFUSE: P2 comparing deployed XXX vs P1-resolved YYY; got: #{findings.inspect}") + end + + def test_fix2_p2_parses_meta_when_it_is_a_json_string + # Some Railway responses deserialize Deployment.meta as a JSON String + # (not a Hash). P2 must parse it before falling back to the WARN branch. + cmd = Railway::PromoteCommand.new([]) + cmd.instance_variable_set(:@promote_refs, { + "x" => "ghcr.io/copilotkit/x@sha256:abc", + }) + cmd.define_singleton_method(:fetch_latest_staging_deployments) do |_svc_id| + [{ "id" => "d1", "status" => "SUCCESS", + "meta" => '{"image":"ghcr.io/copilotkit/x@sha256:abc"}', + "createdAt" => "2026-05-28T01:00:00Z" }] + end + staging = { "services" => [{ + "name" => "x", "service_id" => "svc-1", + "image" => "ghcr.io/copilotkit/x:latest", + "env_keys" => [], + }] } + findings = cmd.send(:check_p2_staging_deployments, staging) + refute(findings.any? { |f| f =~ /WARN: P2 \(x\)/ }, + "JSON-string meta must be parsed (not WARN-skipped); got: #{findings.inspect}") + refute(findings.any? { |f| f =~ /REFUSE: P2/ }, + "matching digest in parsed meta must not REFUSE; got: #{findings.inspect}") + end + + def test_fix2_fetch_latest_staging_deployments_sorts_newest_first + # Stub gql.query to return deployments in OLDEST-first order — the + # helper must sort by createdAt DESC so `.first` is the newest. + cmd = Railway::PromoteCommand.new([]) + nodes = [ + { "id" => "d-old", "status" => "SUCCESS", "createdAt" => "2026-05-01T00:00:00Z" }, + { "id" => "d-new", "status" => "SUCCESS", "createdAt" => "2026-05-28T00:00:00Z" }, + { "id" => "d-mid", "status" => "FAILED", "createdAt" => "2026-05-15T00:00:00Z" }, + ] + edges = nodes.map { |n| { "node" => n } } + fake_gql = Object.new + fake_gql.define_singleton_method(:query) do |_q, _vars = {}| + { "deployments" => { "edges" => edges } } + end + cmd.instance_variable_set(:@gql, fake_gql) + deployments = cmd.send(:fetch_latest_staging_deployments, "svc-1") + assert_equal "d-new", deployments.first["id"], + "fetch_latest_staging_deployments must sort newest-first; got: #{deployments.map { |d| d['id'] }.inspect}" + end + + # =================== FIX-3: execute_promotion pre-validation =================== + + def test_fix3_execute_promotion_pre_validates_all_refs_before_any_pin + # Two prod-matched services; ONE missing from @promote_refs. The + # pre-validation must REFUSE+return 1 BEFORE any serviceInstanceUpdate + # mutation is issued. + cmd = Railway::PromoteCommand.new([]) + recorded = [] + fake_gql = Object.new + fake_gql.define_singleton_method(:query) do |q, vars = {}| + recorded << [q, vars] + { "serviceInstanceUpdate" => true, "serviceInstanceRedeploy" => true } + end + cmd.instance_variable_set(:@gql, fake_gql) + cmd.instance_variable_set(:@promote_refs, { + "a" => "ghcr.io/copilotkit/a@sha256:aaa", + # "b" is MISSING + }) + staging = { "services" => [make_svc("a", image: "ghcr.io/copilotkit/a:latest"), + make_svc("b", image: "ghcr.io/copilotkit/b:latest")] } + prod = { "services" => [ + { "name" => "a", "service_id" => "svc-prod-a", "image" => "ghcr.io/copilotkit/a@sha256:OLD" }, + { "name" => "b", "service_id" => "svc-prod-b", "image" => "ghcr.io/copilotkit/b@sha256:OLD" }, + ] } + _out, _err = capture_io { @rc = cmd.send(:execute_promotion, staging, prod) } + assert_equal 1, @rc, "execute_promotion must return 1 when a ref is missing" + assert(recorded.none? { |q, _| q.include?("serviceInstanceUpdate") }, + "no serviceInstanceUpdate mutations should be issued; got: #{recorded.map { |q, _| q[0, 30] }.inspect}") + end + + # =================== FIX-4: broadened rescue + partial-promotion report =================== + + def test_fix4_execute_promotion_rescues_graphql_error_with_partial_report + # First service pins successfully; second raises Railway::GraphQL::Error + # on its serviceInstanceUpdate mutation. The broadened rescue must + # catch it and still emit the PARTIAL-PROMOTION report. + cmd = Railway::PromoteCommand.new([]) + call_count = 0 + # FakeGQL that records calls and raises GraphQL::Error on the SECOND + # serviceInstanceUpdate mutation. + fake_gql = Object.new + @pinned = nil + @pre_ts = "2026-05-28T00:00:00Z" + pinned_ref = nil + fake_gql.define_singleton_method(:query) do |q, vars = {}| + if q.include?("serviceInstanceUpdate") + call_count += 1 + raise Railway::GraphQL::Error, "boom on update #2" if call_count == 2 + pinned_ref = vars[:image] + { "serviceInstanceUpdate" => true } + elsif q.include?("serviceInstanceRedeploy") + { "serviceInstanceRedeploy" => true } + elsif q.include?("ServiceInstanceRecheck") + if pinned_ref + { "serviceInstance" => { "id" => "i", + "source" => { "image" => pinned_ref }, + "updatedAt" => "2026-05-29T00:00:01Z" } } + else + { "serviceInstance" => { "id" => "i", + "source" => { "image" => "ghcr.io/copilotkit/x@sha256:OLD" }, + "updatedAt" => "2026-05-28T00:00:00Z" } } + end + else + {} + end + end + cmd.instance_variable_set(:@gql, fake_gql) + cmd.instance_variable_set(:@promote_refs, { + "a" => "ghcr.io/copilotkit/a@sha256:aaa", + "b" => "ghcr.io/copilotkit/b@sha256:bbb", + }) + # Silence pin_and_verify retries. + original = Railway::PromoteCommand.const_get(:RETRY_DELAY_SEC) + Railway::PromoteCommand.send(:remove_const, :RETRY_DELAY_SEC) + Railway::PromoteCommand.const_set(:RETRY_DELAY_SEC, 0) + begin + staging = { "services" => [make_svc("a", image: "ghcr.io/copilotkit/a:latest"), + make_svc("b", image: "ghcr.io/copilotkit/b:latest")] } + prod = { "services" => [ + { "name" => "a", "service_id" => "svc-prod-a", "image" => "ghcr.io/copilotkit/a@sha256:OLD" }, + { "name" => "b", "service_id" => "svc-prod-b", "image" => "ghcr.io/copilotkit/b@sha256:OLD" }, + ] } + out, err = capture_io { @rc = cmd.send(:execute_promotion, staging, prod) } + combined = out + err + assert_equal 1, @rc + assert_match(/PARTIAL PROMOTION/i, combined, + "must emit partial-promotion report on GraphQL::Error; combined=#{combined}") + assert_match(/already pinned.*\ba\b/m, combined, + "report must name 'a' as already-pinned; combined=#{combined}") + assert_match(/FAILED on b/, combined, + "report must name 'b' as failed; combined=#{combined}") + # Dedup check: the inner e.message should appear ONLY inside the + # composed PARTIAL-PROMOTION line, not on its own line as well. + assert_equal 1, combined.scan(/boom on update #2/).size, + "duplicate `warn e.message` line must be removed; combined=#{combined}" + ensure + Railway::PromoteCommand.send(:remove_const, :RETRY_DELAY_SEC) + Railway::PromoteCommand.const_set(:RETRY_DELAY_SEC, original) + end + end + + # =================== FIX-5: P1 REFUSE on imageless service =================== + + def test_fix5_p1_refuses_when_staging_service_has_no_image + cmd = Railway::PromoteCommand.new([]) + cmd.instance_variable_set(:@ghcr, PassGHCR.new) + staging = { "services" => [ + { "name" => "x", "service_id" => "svc-1", "image" => nil, + "env_keys" => [] }, + ] } + findings = cmd.send(:check_p1_ghcr_digests, staging) + assert(findings.any? { |f| f =~ /REFUSE: P1 \(x\).*no image/i }, + "expected REFUSE: P1 (x) about missing image; got: #{findings.inspect}") + end + + # =================== FIX-6: P1 per-service rescue broadens to StandardError =================== + + class ArgumentErrorGHCR + def resolve_digest(_); "sha256:fake"; end + def manifest_exists(_); raise ArgumentError, "non-ghcr error"; end + def parse_image_ref(ref); Railway::GHCR.allocate.parse_image_ref(ref); end + end + + def test_fix6_p1_rescue_catches_non_ghcr_errors + # ArgumentError raised inside manifest_exists must be caught by the + # per-service rescue (broadened to StandardError) — the loop must + # continue and the service must get a per-service REFUSE. + cmd = Railway::PromoteCommand.new([]) + cmd.instance_variable_set(:@ghcr, ArgumentErrorGHCR.new) + staging = { "services" => [ + make_svc("a", image: "ghcr.io/copilotkit/a:latest"), + make_svc("b", image: "ghcr.io/copilotkit/b:latest"), + ] } + findings = cmd.send(:check_p1_ghcr_digests, staging) + # Two services, each one should fail with ArgumentError-bearing REFUSE. + assert(findings.any? { |f| f =~ /REFUSE: P1 \(a\).*ArgumentError.*non-ghcr error/ }, + "service 'a' must record a per-service REFUSE for ArgumentError; got: #{findings.inspect}") + assert(findings.any? { |f| f =~ /REFUSE: P1 \(b\).*ArgumentError.*non-ghcr error/ }, + "service 'b' must record a per-service REFUSE for ArgumentError; got: #{findings.inspect}") + end + + # =================== FIX-7: pin_and_verify upfront digest guard =================== + + def test_fix7_pin_and_verify_raises_arg_error_on_tag_form_image + # Pre-fix: pin_and_verify would dutifully attempt N retries before + # raising a misleading MutationError. After fix: ArgumentError fires + # immediately, no retries. + gql = Object.new + gql.define_singleton_method(:query) { |*| raise "no query should be issued" } + err = assert_raises(ArgumentError) do + Railway::PromoteCommand.pin_and_verify(gql, + service_id: "svc-x", env_id: "env-prod", + image: "ghcr.io/copilotkit/x:latest", + sleeper: ->(_) {}) + end + assert_match(/pin_and_verify.*@sha256.*pinned/i, err.message, + "ArgumentError message must explain the digest requirement; got: #{err.message}") + end + + # =================== FIX-8: pin_and_verify ts gate is non-vacuous =================== + + def test_fix8_pin_and_verify_requires_non_nil_updated_at_even_when_pre_ts_nil + # pre_update_ts is nil (new prod instance). Recheck returns matching + # digest but a nil updatedAt. Pre-fix: ts_ok was vacuously true → success. + # After fix: ts_ok requires actual_ts to be non-nil → kept retrying → + # MutationError after RETRY_COUNT attempts. + gql = Object.new + pinned = nil + gql.define_singleton_method(:query) do |q, vars = {}| + if q.include?("serviceInstanceUpdate") + pinned = vars[:image] + { "serviceInstanceUpdate" => true } + elsif q.include?("serviceInstanceRedeploy") + { "serviceInstanceRedeploy" => true } + elsif q.include?("ServiceInstanceRecheck") + if pinned.nil? + # Pre-update: brand-new instance — both fields are nil. + { "serviceInstance" => nil } + else + { "serviceInstance" => { "id" => "i", + "source" => { "image" => pinned }, + "updatedAt" => nil } } + end + else + {} + end + end + err = assert_raises(Railway::PromoteCommand::MutationError) do + Railway::PromoteCommand.pin_and_verify(gql, + service_id: "svc-x", env_id: "env-prod", + image: "ghcr.io/copilotkit/x@sha256:abc", + sleeper: ->(_) {}) + end + assert_match(/did not observe image advance/i, err.message, + "expected timeout-style MutationError; got: #{err.message}") + end + + # =================== FIX-9: run_staging_probe rescue on launch failure =================== + + def test_fix9_run_staging_probe_returns_clean_failure_on_io_popen_error + cmd = Railway::PromoteCommand.new([]) + # The probe binary must APPEAR present so we reach the IO.popen call. + # (Skip the File.exist? early-return.) + original_exist = File.method(:exist?) + File.define_singleton_method(:exist?) { |_path| true } + # Stub IO.popen to raise Errno::ENOENT (npx missing on PATH). + original_popen = IO.method(:popen) + IO.define_singleton_method(:popen) do |*_args, **_kw, &_blk| + raise Errno::ENOENT, "npx" + end + begin + result = cmd.send(:run_staging_probe, services: ["x"]) + assert_equal false, result[:ok], "must return ok:false on launch failure" + assert_match(/staging probe failed to launch.*ENOENT/i, result[:summary], + "summary must describe the launch failure; got: #{result[:summary].inspect}") + ensure + IO.define_singleton_method(:popen, &original_popen) + File.define_singleton_method(:exist?, &original_exist) + end + end +end diff --git a/showcase/bin/spec/test_promote_execute.rb b/showcase/bin/spec/test_promote_execute.rb new file mode 100644 index 00000000000..117c5e8220b --- /dev/null +++ b/showcase/bin/spec/test_promote_execute.rb @@ -0,0 +1,179 @@ +# frozen_string_literal: true + +require_relative "spec_helper" + +# execute_promotion must resolve any staging tag (e.g. ":latest") to its +# concrete GHCR digest and pin THAT to prod — never a mutable tag. This is +# the core invariant of the showcase deploy model (P6 enforces shape). +class PromoteExecuteTest < Minitest::Test + # A FakeGQL that returns the most-recently-pinned image on recheck, + # so pin_and_verify sees the advance. Records all calls for assertions. + class RecordingGQL + def initialize(pre_ts: "2026-05-28T00:00:00Z") + @calls = [] + @pinned_image = nil + @pre_ts = pre_ts + end + attr_reader :calls + + def query(q, vars = {}) + @calls << [q, vars] + if q.include?("serviceInstanceUpdate") + @pinned_image = vars[:image] + { "serviceInstanceUpdate" => true } + elsif q.include?("serviceInstanceRedeploy") + { "serviceInstanceRedeploy" => true } + elsif q.include?("ServiceInstanceRecheck") + if @pinned_image.nil? + # Pre-update snapshot. + { + "serviceInstance" => { + "id" => "i", + "source" => { "image" => "ghcr.io/copilotkit/x@sha256:OLD" }, + "updatedAt" => @pre_ts, + }, + } + else + { + "serviceInstance" => { + "id" => "i", + "source" => { "image" => @pinned_image }, + "updatedAt" => "2026-05-29T00:00:01Z", + }, + } + end + else + {} + end + end + + # Find the `image:` arg passed to the serviceInstanceUpdate mutation. + def pinned_image + row = @calls.find { |q, _| q.include?("serviceInstanceUpdate") } + row && row[1][:image] + end + end + + # FakeGHCR that maps tag-form refs to a digest, and reports :exists for + # the corresponding digest-pinned ref so P1 passes. + class FakeGHCR + # `resolve_map` is { "ghcr.io/org/name:tag" => "sha256:abc..." } or nil for unresolvable. + # `exists_set` is the set of digest-pinned refs that report :exists. + def initialize(resolve_map: {}, exists_set: nil) + @resolve_map = resolve_map + @exists_set = exists_set + end + + def resolve_digest(ref) + # Pass-through for already-pinned refs. + return ref.split("@", 2).last if ref.include?("@sha256:") + @resolve_map[ref] + end + + def manifest_exists(ref) + return :missing if @exists_set && !@exists_set.include?(ref) + :exists + end + + # Delegate to the real GHCR parser — pure function, no I/O. + def parse_image_ref(ref) + Railway::GHCR.allocate.parse_image_ref(ref) + end + end + + # Build a command with staging-tag service, snapshot the prod target, and + # inject fakes. Returns [cmd, gql]. + def build_cmd(staging_image:, resolve_map:, exists_set: nil) + # --confirm-divergence: bypass the (test-irrelevant) WARN that prod is + # missing the EXPECTED_DOMAINS set; we are testing pin behavior. + cmd = Railway::PromoteCommand.new(["--non-interactive", "--yes", "--confirm-divergence"]) + cmd.parser.parse!(cmd.argv) + cmd.instance_variable_set(:@staging_snapshot, { + "services" => [{ + "name" => "x", "service_id" => "svc-staging", + "image" => staging_image, + "env_keys" => [], + "start_command" => "node server.js", "healthcheck_path" => "/health", + "region" => "us-west", "replicas" => 1, "restart_policy" => "ON_FAILURE", + }], + }) + cmd.instance_variable_set(:@prod_snapshot, { + "services" => [{ + "name" => "x", "service_id" => "svc-prod", + "image" => "ghcr.io/copilotkit/x@sha256:OLD", + "env_keys" => [], + "start_command" => "node server.js", "healthcheck_path" => "/health", + "region" => "us-west", "replicas" => 1, "restart_policy" => "ON_FAILURE", + }], + }) + gql = RecordingGQL.new + cmd.instance_variable_set(:@gql, gql) + cmd.instance_variable_set(:@ghcr, FakeGHCR.new(resolve_map: resolve_map, exists_set: exists_set)) + # Skip P2 deployments query — make fetch_latest_staging_deployments return + # a SUCCESS deployment whose digest matches whatever we will resolve. + # Fall back to a placeholder digest so the fixture is never a malformed + # "...@" — the unresolvable-tag test path will REFUSE at P1 before P2 + # consults this stub, so the placeholder value is benign. + resolved_digest = resolve_map.values.first || + (staging_image.include?("@") ? staging_image.split("@", 2).last : "sha256:placeholder") + cmd.define_singleton_method(:fetch_latest_staging_deployments) do |_svc_id| + [{ "id" => "d", "status" => "SUCCESS", "meta" => { "image" => "ghcr.io/copilotkit/x@#{resolved_digest}" } }] + end + cmd.define_singleton_method(:run_staging_probe) { |services:| { ok: true, summary: "" } } + [cmd, gql] + end + + # Silence pin_and_verify's 10s-per-retry waits. RETRY_DELAY_SEC is a + # constant on PromoteCommand; remap to 0 around the test body and restore. + def with_fast_sleeper + original = Railway::PromoteCommand.const_get(:RETRY_DELAY_SEC) + Railway::PromoteCommand.send(:remove_const, :RETRY_DELAY_SEC) + Railway::PromoteCommand.const_set(:RETRY_DELAY_SEC, 0) + yield + ensure + Railway::PromoteCommand.send(:remove_const, :RETRY_DELAY_SEC) + Railway::PromoteCommand.const_set(:RETRY_DELAY_SEC, original) + end + + def test_resolves_staging_tag_to_digest_and_pins_digest_to_prod + # (a) staging image is `:latest`; resolve_map maps it to a digest. + cmd, gql = build_cmd( + staging_image: "ghcr.io/copilotkit/x:latest", + resolve_map: { "ghcr.io/copilotkit/x:latest" => "sha256:abc123" }, + ) + out, _ = with_fast_sleeper { capture_io { @rc = cmd.run_with_preflight_only } } + assert_equal 0, @rc, "promote should succeed when staging tag resolves cleanly; got out=#{out}" + pinned = gql.pinned_image + assert_equal "ghcr.io/copilotkit/x@sha256:abc123", pinned, + "must pin the DIGEST-form ref, not the :latest tag; pinned=#{pinned.inspect}" + refute_includes pinned.to_s, ":latest", "must not pin a mutable tag" + assert_match(/promoted x -> ghcr\.io\/copilotkit\/x@sha256:abc123/, out) + end + + def test_refuses_when_staging_tag_cannot_be_resolved_to_digest + # (b) staging :latest that GHCR cannot resolve (resolve_digest returns nil) + # → REFUSE; serviceInstanceUpdate is NEVER called. + cmd, gql = build_cmd( + staging_image: "ghcr.io/copilotkit/x:latest", + resolve_map: {}, # unresolvable + ) + out, _ = with_fast_sleeper { capture_io { @rc = cmd.run_with_preflight_only } } + assert_equal 1, @rc, "must refuse when staging tag is unresolvable" + assert_match(/cannot resolve .*:latest.* GHCR digest/i, out) + refuses_update = gql.calls.any? { |q, _| q.include?("serviceInstanceUpdate") } + refute refuses_update, "must NOT call serviceInstanceUpdate when refusing on unresolvable tag" + end + + def test_already_digest_pinned_staging_image_is_used_as_is + # (c) Unit test of the resolved-prod-image helper. A staging service + # whose image is already digest-pinned must be passed through unchanged + # (no GHCR tag lookup needed, no rewrite). (This shape is irregular for + # showcase staging — P6 would normally REFUSE staging != :tag — but the + # helper itself must be safe and idempotent.) + cmd = Railway::PromoteCommand.new([]) + cmd.instance_variable_set(:@ghcr, FakeGHCR.new(resolve_map: {})) + svc = { "name" => "x", "image" => "ghcr.io/copilotkit/x@sha256:def456" } + assert_equal "ghcr.io/copilotkit/x@sha256:def456", + cmd.send(:resolved_prod_image, svc) + end +end diff --git a/showcase/bin/spec/test_promote_p1.rb b/showcase/bin/spec/test_promote_p1.rb new file mode 100644 index 00000000000..30433b23da7 --- /dev/null +++ b/showcase/bin/spec/test_promote_p1.rb @@ -0,0 +1,86 @@ +# frozen_string_literal: true + +require_relative "spec_helper" + +class PromoteP1Test < Minitest::Test + # Stub gql + ghcr clients used by PromoteCommand. + # Preflight checks accumulate findings before any short-circuit, so even + # on a P1 REFUSE the P2 deployments query is still issued. Provide a + # benign empty-deployments shape so the test focuses solely on P1. + class FakeGQLEmpty + def query(*); { "deployments" => { "edges" => [] } }; end + end + + class FakeGHCR + def initialize(result); @result = result; end + def manifest_exists(_ref); @result; end + # Tag refs resolve to a synthetic digest; digest refs pass through. + # Returning a stable digest makes the resolved ref deterministic so + # the existing P1 tests (which staged digest-pinned images) continue + # to verify the same code path. + def resolve_digest(ref) + return ref.split("@", 2).last if ref.include?("@sha256:") + "sha256:fake_resolved_digest" + end + def parse_image_ref(ref); Railway::GHCR.allocate.parse_image_ref(ref); end + end + + def test_refuses_when_digest_missing_in_ghcr + cmd = Railway::PromoteCommand.new(["--non-interactive", "--yes"]) + # Inject deterministic precondition input — bypass live snapshot capture. + cmd.instance_variable_set(:@staging_snapshot, { + "services" => [{ + "name" => "showcase-shell", + "service_id" => "svc-1", + "image" => "ghcr.io/copilotkit/showcase-shell@sha256:deadbeef", + "image_tag" => "ghcr.io/copilotkit/showcase-shell:latest", + "digest" => "sha256:deadbeef", + "env_keys" => [], + }], + }) + cmd.instance_variable_set(:@prod_snapshot, { "services" => [] }) + cmd.instance_variable_set(:@gql, FakeGQLEmpty.new) + cmd.instance_variable_set(:@ghcr, FakeGHCR.new(:missing)) + + cmd.define_singleton_method(:run_staging_probe) { |services:| { ok: true, summary: "" } } + out, _err = capture_io { @rc = cmd.run_with_preflight_only } + assert_equal 1, @rc, "promote must exit 1 on REFUSE" + assert_match(/REFUSE: P1.*ghcr\.io.*not found in GHCR/i, out) + end + + def test_refuses_with_clear_message_on_auth_failed + cmd = Railway::PromoteCommand.new(["--non-interactive", "--yes"]) + cmd.instance_variable_set(:@staging_snapshot, { + "services" => [{ "name" => "x", "service_id" => "s", "image" => "ghcr.io/copilotkit/x@sha256:abc", "env_keys" => [] }], + }) + cmd.instance_variable_set(:@prod_snapshot, { "services" => [] }) + # All preflight checks accumulate findings before any short-circuit; + # P2 will still query gql, so use the benign empty fake. + cmd.instance_variable_set(:@gql, FakeGQLEmpty.new) + cmd.instance_variable_set(:@ghcr, FakeGHCR.new(:auth_failed)) + + cmd.define_singleton_method(:run_staging_probe) { |services:| { ok: true, summary: "" } } + out, _err = capture_io { @rc = cmd.run_with_preflight_only } + assert_equal 1, @rc + assert_match(/REFUSE: P1.*GHCR.*auth/i, out) + assert_match(/GHCR_TOKEN.*or.*GITHUB_TOKEN/i, out) + end + + def test_passes_p1_when_digest_exists + cmd = Railway::PromoteCommand.new(["--non-interactive", "--yes"]) + cmd.instance_variable_set(:@staging_snapshot, { + "services" => [{ "name" => "x", "service_id" => "s", "image" => "ghcr.io/copilotkit/x@sha256:abc", "env_keys" => [] }], + }) + cmd.instance_variable_set(:@prod_snapshot, { "services" => [] }) + # P1 passes here so P2 will run — use the benign empty fake so + # the deployments query returns []. + cmd.instance_variable_set(:@gql, FakeGQLEmpty.new) + cmd.instance_variable_set(:@ghcr, FakeGHCR.new(:exists)) + + # P1 alone should not refuse; later checks (service-set parity) will, + # but P1's own gate is clean for this fixture. + cmd.define_singleton_method(:run_staging_probe) { |services:| { ok: true, summary: "" } } + out, _err = capture_io { cmd.run_with_preflight_only } + refute_match(/REFUSE: P1/, out) + end +end diff --git a/showcase/bin/spec/test_promote_p2.rb b/showcase/bin/spec/test_promote_p2.rb new file mode 100644 index 00000000000..063e8cc0721 --- /dev/null +++ b/showcase/bin/spec/test_promote_p2.rb @@ -0,0 +1,106 @@ +# frozen_string_literal: true + +require_relative "spec_helper" + +class PromoteP2Test < Minitest::Test + class FakeGQL + def initialize(deployments_by_svc) + @deployments_by_svc = deployments_by_svc + @calls = [] + end + + attr_reader :calls + + def query(q, vars = {}) + @calls << [q, vars] + if q.include?("query Deployments") + edges = (@deployments_by_svc[vars[:serviceId]] || []).map { |n| { "node" => n } } + return { "deployments" => { "edges" => edges } } + end + raise "unexpected query: #{q[0,40]}" + end + end + + def cmd_with(staging:, deployments:) + c = Railway::PromoteCommand.new(["--non-interactive", "--yes"]) + c.instance_variable_set(:@staging_snapshot, staging) + c.instance_variable_set(:@prod_snapshot, { "services" => [] }) + c.instance_variable_set(:@gql, FakeGQL.new(deployments)) + c.instance_variable_set(:@ghcr, Object.new.tap do |o| + def o.manifest_exists(_); :exists; end + def o.resolve_digest(ref); ref.include?("@sha256:") ? ref.split("@", 2).last : "sha256:fake"; end + def o.parse_image_ref(ref); Railway::GHCR.allocate.parse_image_ref(ref); end + end) + # Stub P3 probe so the test does not shell out to verify-deploy.ts. + c.define_singleton_method(:run_staging_probe) { |services:| { ok: true, summary: "" } } + c + end + + def test_refuses_when_latest_deployment_not_success + staging = { "services" => [{ + "name" => "x", "service_id" => "svc-1", + "image" => "ghcr.io/copilotkit/x@sha256:abc", "digest" => "sha256:abc", + "env_keys" => [], + }] } + deps = { "svc-1" => [ + { "id" => "d2", "status" => "FAILED", "meta" => { "image" => "ghcr.io/copilotkit/x@sha256:abc" }, "createdAt" => "2026-05-28T01:00:00Z" }, + { "id" => "d1", "status" => "SUCCESS", "meta" => { "image" => "ghcr.io/copilotkit/x@sha256:abc" }, "createdAt" => "2026-05-28T00:00:00Z" }, + ] } + out, err = capture_io { @rc = cmd_with(staging: staging, deployments: deps).run_with_preflight_only } + combined = out + err + assert_equal 1, @rc + assert_match(/REFUSE: P2.*x.*latest staging deployment.*FAILED/i, combined) + end + + def test_refuses_when_latest_success_image_does_not_match + # In-flight race: newer build with a different digest is the latest. + staging = { "services" => [{ + "name" => "x", "service_id" => "svc-1", + "image" => "ghcr.io/copilotkit/x@sha256:OLD", "digest" => "sha256:OLD", + "env_keys" => [], + }] } + deps = { "svc-1" => [ + { "id" => "d2", "status" => "SUCCESS", "meta" => { "image" => "ghcr.io/copilotkit/x@sha256:NEW" }, "createdAt" => "2026-05-28T01:00:00Z" }, + ] } + out, err = capture_io { @rc = cmd_with(staging: staging, deployments: deps).run_with_preflight_only } + combined = out + err + assert_equal 1, @rc + assert_match(/REFUSE: P2.*in-flight.*sha256:NEW.*sha256:OLD/i, combined) + end + + def test_does_not_crash_when_deployment_meta_is_a_string + # Railway's Deployment.meta is a JSON scalar that can come back as a + # String (not a Hash). After FIX-2 we PARSE it first; a non-JSON + # string falls back to a WARN, but MUST NOT crash and MUST NOT + # produce a P2 REFUSE. + staging = { "services" => [{ + "name" => "x", "service_id" => "svc-1", + "image" => "ghcr.io/copilotkit/x@sha256:abc", "digest" => "sha256:abc", + "env_keys" => [], + }] } + deps = { "svc-1" => [ + { "id" => "d1", "status" => "SUCCESS", + "meta" => "raw-string-not-a-hash", + "createdAt" => "2026-05-28T01:00:00Z" }, + ] } + out, err = capture_io { cmd_with(staging: staging, deployments: deps).run_with_preflight_only } + combined = out + err + # MUST NOT crash. P2 race-check is best-effort; SUCCESS is the real gate. + refute_match(/REFUSE: P2/, combined) + refute_match(/NoMethodError/, combined) + end + + def test_passes_p2_when_latest_is_success_and_image_matches + staging = { "services" => [{ + "name" => "x", "service_id" => "svc-1", + "image" => "ghcr.io/copilotkit/x@sha256:abc", "digest" => "sha256:abc", + "env_keys" => [], + }] } + deps = { "svc-1" => [ + { "id" => "d1", "status" => "SUCCESS", "meta" => { "image" => "ghcr.io/copilotkit/x@sha256:abc" }, "createdAt" => "2026-05-28T01:00:00Z" }, + ] } + out, err = capture_io { cmd_with(staging: staging, deployments: deps).run_with_preflight_only } + combined = out + err + refute_match(/REFUSE: P2/, combined) + end +end diff --git a/showcase/bin/spec/test_promote_p3.rb b/showcase/bin/spec/test_promote_p3.rb new file mode 100644 index 00000000000..1e06a9ca36b --- /dev/null +++ b/showcase/bin/spec/test_promote_p3.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +require_relative "spec_helper" + +class PromoteP3Test < Minitest::Test + def make_cmd(probe_result:, flag:) + argv = ["--non-interactive", "--yes"] + argv << flag if flag + c = Railway::PromoteCommand.new(argv) + # run_with_preflight_only skips parser.parse!; parse eagerly so the + # --no-require-staging-green / --confirm-divergence flags land. + c.parser.parse!(c.argv) + c.instance_variable_set(:@staging_snapshot, { + "services" => [{ + "name" => "x", "service_id" => "svc-1", + "image" => "ghcr.io/copilotkit/x:latest", "digest" => "sha256:abc", + "env_keys" => [], + "start_command" => "node server.js", "healthcheck_path" => "/health", + "region" => "us-west", "replicas" => 1, "restart_policy" => "ON_FAILURE", + }], + }) + c.instance_variable_set(:@prod_snapshot, { + "services" => [{ + "name" => "x", "service_id" => "svc-1", + "image" => "ghcr.io/copilotkit/x@sha256:abc", "digest" => "sha256:abc", + "env_keys" => [], + "start_command" => "node server.js", "healthcheck_path" => "/health", + "region" => "us-west", "replicas" => 1, "restart_policy" => "ON_FAILURE", + }], + }) + c.instance_variable_set(:@gql, Object.new.tap { |o| def o.query(*); { "deployments" => { "edges" => [{ "node" => { "id" => "d", "status" => "SUCCESS", "meta" => { "image" => "ghcr.io/copilotkit/x@sha256:abc" } } }] } }; end }) + c.instance_variable_set(:@ghcr, Object.new.tap do |o| + def o.manifest_exists(_); :exists; end + def o.resolve_digest(ref); ref.include?("@sha256:") ? ref.split("@", 2).last : "sha256:abc"; end + def o.parse_image_ref(ref); Railway::GHCR.allocate.parse_image_ref(ref); end + end) + # Inject the probe result as a stub. + c.define_singleton_method(:run_staging_probe) { |services:| probe_result } + c + end + + def test_refuses_on_red_probe_when_flag_default_on + cmd = make_cmd(probe_result: { ok: false, summary: "x: HTTP 502 from docs.staging.copilotkit.ai" }, flag: nil) + out, _ = capture_io { @rc = cmd.run_with_preflight_only } + assert_equal 1, @rc + assert_match(/REFUSE: P3.*staging.*not green.*HTTP 502/i, out) + end + + def test_skips_probe_when_no_require_staging_green + cmd = make_cmd(probe_result: { ok: false, summary: "would have failed" }, flag: "--no-require-staging-green") + # Fail LOUD if the skip path ever calls the probe — the probe stub + # must be unreachable under --no-require-staging-green. + cmd.define_singleton_method(:run_staging_probe) do |services:| + raise "probe must not run under --no-require-staging-green" + end + out, _ = capture_io { cmd.run_with_preflight_only } + refute_match(/REFUSE: P3/, out) + assert_match(/P3 SKIPPED.*--no-require-staging-green/, out) + end + + def test_passes_p3_on_green_probe + cmd = make_cmd(probe_result: { ok: true, summary: "all green" }, flag: nil) + out, _ = capture_io { cmd.run_with_preflight_only } + refute_match(/REFUSE: P3/, out) + end +end diff --git a/showcase/bin/spec/test_promote_p5.rb b/showcase/bin/spec/test_promote_p5.rb new file mode 100644 index 00000000000..853c4a8734f --- /dev/null +++ b/showcase/bin/spec/test_promote_p5.rb @@ -0,0 +1,99 @@ +# frozen_string_literal: true + +require_relative "spec_helper" + +class PromoteP5Test < Minitest::Test + class FakeGQL + def initialize(plan); @plan = plan; @calls = []; end + attr_reader :calls + + def query(q, vars = {}) + @calls << [q, vars] + step = @plan.shift + raise "fake exhausted at call ##{@calls.size}: #{q[0,40]}" unless step + raise step[:raise] if step[:raise] + step[:data] # inner data hash, matching GraphQL#query's return shape + end + end + + # Helper: a "pre-update snapshot" GQL response with a given updatedAt. + def pre(ts) = { data: { "serviceInstance" => { "id" => "i", "source" => { "image" => "ghcr.io/copilotkit/x@sha256:OLD" }, "updatedAt" => ts } } } + + # Helper: a "post-update re-query" response. + def post(image:, ts:) = { data: { "serviceInstance" => { "id" => "i", "source" => { "image" => image }, "updatedAt" => ts } } } + + def test_refuses_when_update_returns_false + # Order: pre-update snapshot, then update (false) — refuse before redeploy. + gql = FakeGQL.new([ + pre("2026-05-27T00:00:00Z"), + { data: { "serviceInstanceUpdate" => false } }, + ]) + assert_raises(Railway::PromoteCommand::MutationError) do + Railway::PromoteCommand.pin_and_verify(gql, + service_id: "s", env_id: "e", image: "ghcr.io/copilotkit/x@sha256:NEW", + sleeper: ->(_n) {}) + end + end + + def test_verifies_image_AND_updatedAt_advanced_after_update + gql = FakeGQL.new([ + pre("2026-05-27T00:00:00Z"), + { data: { "serviceInstanceUpdate" => true } }, + { data: { "serviceInstanceRedeploy" => true } }, + post(image: "ghcr.io/copilotkit/x@sha256:NEW", ts: "2026-05-28T01:00:00Z"), + ]) + Railway::PromoteCommand.pin_and_verify(gql, + service_id: "s", env_id: "e", image: "ghcr.io/copilotkit/x@sha256:NEW", + sleeper: ->(_n) {}) + assert_equal 4, gql.calls.size + end + + def test_refuses_when_image_advanced_but_updatedAt_did_NOT_advance + # P5 guard for the no-op re-pin / cache-shaped race: image-equality + # alone is insufficient. updatedAt MUST strictly advance past + # pre_update_ts; if not, three retries then refuse. + pre_ts = "2026-05-27T00:00:00Z" + stale_ts_post = post(image: "ghcr.io/copilotkit/x@sha256:NEW", ts: pre_ts) + gql = FakeGQL.new([ + pre(pre_ts), + { data: { "serviceInstanceUpdate" => true } }, + { data: { "serviceInstanceRedeploy" => true } }, + stale_ts_post, stale_ts_post, stale_ts_post, + ]) + assert_raises(Railway::PromoteCommand::MutationError) do + Railway::PromoteCommand.pin_and_verify(gql, + service_id: "s", env_id: "e", image: "ghcr.io/copilotkit/x@sha256:NEW", + sleeper: ->(_n) {}) + end + end + + def test_retries_then_refuses_on_stale_image + # Three re-queries all report stale image. + stale = post(image: "ghcr.io/copilotkit/x@sha256:OLD", ts: "2026-05-27T00:00:00Z") + gql = FakeGQL.new([ + pre("2026-05-27T00:00:00Z"), + { data: { "serviceInstanceUpdate" => true } }, + { data: { "serviceInstanceRedeploy" => true } }, + stale, stale, stale, + ]) + assert_raises(Railway::PromoteCommand::MutationError) do + Railway::PromoteCommand.pin_and_verify(gql, + service_id: "s", env_id: "e", image: "ghcr.io/copilotkit/x@sha256:NEW", + sleeper: ->(_n) {}) + end + end + + def test_accepts_when_third_requery_shows_advance + new_ok = post(image: "ghcr.io/copilotkit/x@sha256:NEW", ts: "2026-05-28T02:00:00Z") + stale = post(image: "ghcr.io/copilotkit/x@sha256:OLD", ts: "2026-05-27T00:00:00Z") + gql = FakeGQL.new([ + pre("2026-05-27T00:00:00Z"), + { data: { "serviceInstanceUpdate" => true } }, + { data: { "serviceInstanceRedeploy" => true } }, + stale, stale, new_ok, + ]) + Railway::PromoteCommand.pin_and_verify(gql, + service_id: "s", env_id: "e", image: "ghcr.io/copilotkit/x@sha256:NEW", + sleeper: ->(_n) {}) + end +end diff --git a/showcase/bin/spec/test_promote_p6.rb b/showcase/bin/spec/test_promote_p6.rb new file mode 100644 index 00000000000..45b6abe59fe --- /dev/null +++ b/showcase/bin/spec/test_promote_p6.rb @@ -0,0 +1,104 @@ +# frozen_string_literal: true + +require_relative "spec_helper" + +class PromoteP6Test < Minitest::Test + def cmd_with(staging, prod, flag: nil) + argv = ["--non-interactive", "--yes"] + argv << flag if flag + c = Railway::PromoteCommand.new(argv) + # run_with_preflight_only skips the parser.parse!() that #run normally + # invokes; parse flags eagerly so --confirm-divergence etc. land in + # options[]. + c.parser.parse!(c.argv) + c.instance_variable_set(:@staging_snapshot, staging) + c.instance_variable_set(:@prod_snapshot, prod) + c.instance_variable_set(:@gql, Object.new.tap { |o| def o.query(*); { "deployments" => { "edges" => [{ "node" => { "id" => "d", "status" => "SUCCESS", "meta" => { "image" => "ghcr.io/copilotkit/x@sha256:abc" } } }] } }; end }) + c.instance_variable_set(:@ghcr, Object.new.tap do |o| + def o.manifest_exists(_); :exists; end + def o.resolve_digest(ref); ref.include?("@sha256:") ? ref.split("@", 2).last : "sha256:abc"; end + def o.parse_image_ref(ref); Railway::GHCR.allocate.parse_image_ref(ref); end + end) + c.define_singleton_method(:run_staging_probe) { |services:| { ok: true, summary: "" } } + c + end + + # Defaults represent the "good" parity baseline: staging tag-floating, + # prod digest-pinned, all other fields identical. Tests override fields + # with the specific divergence under test. + def staging_svc(over = {}) + { + "name" => "x", "service_id" => "s", "image" => "ghcr.io/copilotkit/x:latest", + "digest" => "sha256:abc", "env_keys" => [], + "start_command" => "node server.js", "healthcheck_path" => "/health", + "region" => "us-west", "replicas" => 1, "restart_policy" => "ON_FAILURE", + }.merge(over) + end + + def prod_svc(over = {}) + { + "name" => "x", "service_id" => "s", "image" => "ghcr.io/copilotkit/x@sha256:abc", + "digest" => "sha256:abc", "env_keys" => [], + "start_command" => "node server.js", "healthcheck_path" => "/health", + "region" => "us-west", "replicas" => 1, "restart_policy" => "ON_FAILURE", + }.merge(over) + end + + def test_refuses_on_start_command_divergence + st = { "services" => [staging_svc("start_command" => "node staging.js")] } + pr = { "services" => [prod_svc("start_command" => "node prod.js")] } + out, _ = capture_io { @rc = cmd_with(st, pr).run_with_preflight_only } + assert_equal 1, @rc + assert_match(/REFUSE: P6.*x.*startCommand/i, out) + end + + def test_refuses_on_healthcheck_path_divergence + st = { "services" => [staging_svc("healthcheck_path" => "/health")] } + pr = { "services" => [prod_svc("healthcheck_path" => "/healthz")] } + out, _ = capture_io { @rc = cmd_with(st, pr).run_with_preflight_only } + assert_equal 1, @rc + assert_match(/REFUSE: P6.*x.*healthcheckPath/i, out) + end + + def test_refuses_on_image_shape_divergence + # staging digest-pinned (wrong; expected tag), prod tag-floating + # (wrong; expected digest). Both shapes invert the parity. + st = { "services" => [staging_svc("image" => "ghcr.io/copilotkit/x@sha256:abc")] } + pr = { "services" => [prod_svc("image" => "ghcr.io/copilotkit/x:latest")] } + out, _ = capture_io { @rc = cmd_with(st, pr).run_with_preflight_only } + assert_equal 1, @rc + assert_match(/REFUSE: P6.*x.*image shape/i, out) + end + + def test_warns_on_region_replicas_restartpolicy_envkeys_without_confirm_divergence + st = { "services" => [staging_svc("region" => "us-west", "replicas" => 1, "restart_policy" => "ON_FAILURE", "env_keys" => ["A", "B"])] } + pr = { "services" => [prod_svc( "region" => "us-east", "replicas" => 3, "restart_policy" => "ALWAYS", "env_keys" => ["A", "C"])] } + out, _ = capture_io { @rc = cmd_with(st, pr).run_with_preflight_only } + assert_equal 1, @rc, "must refuse without --confirm-divergence even on WARN-only" + assert_match(/WARN.*x.*region/i, out) + assert_match(/WARN.*x.*replicas/i, out) + assert_match(/WARN.*x.*restartPolicy/i, out) + assert_match(/WARN.*x.*env key set/i, out) + end + + def test_warns_proceed_with_confirm_divergence + st = { "services" => [staging_svc("region" => "us-west")] } + pr = { "services" => [prod_svc( "region" => "us-east")] } + # capture_destructive_confirmation: --non-interactive + --yes bypasses prompt. + # Stub execute_promotion to isolate the WARN-proceed gate from the + # real mutation path (which would chase under-stubbed gql). + c = cmd_with(st, pr, flag: "--confirm-divergence") + c.define_singleton_method(:execute_promotion) { |_st, _pr| 0 } + out, _ = capture_io { @rc = c.run_with_preflight_only } + assert_equal 0, @rc, "WARN-proceed path with --confirm-divergence must exit 0" + assert_match(/WARN.*x.*region/i, out) + assert_match(/proceeding past .* WARN finding/i, out) + end + + def test_env_var_values_never_compared_message_printed_every_run + st = { "services" => [staging_svc] } + pr = { "services" => [prod_svc] } + out, _ = capture_io { cmd_with(st, pr).run_with_preflight_only } + assert_match(/env var VALUES are not compared/i, out) + end +end diff --git a/showcase/bin/spec/test_promote_resolve_once.rb b/showcase/bin/spec/test_promote_resolve_once.rb new file mode 100644 index 00000000000..ad781a82b8a --- /dev/null +++ b/showcase/bin/spec/test_promote_resolve_once.rb @@ -0,0 +1,296 @@ +# frozen_string_literal: true + +require_relative "spec_helper" + +# Covers five CR fixes against PromoteCommand: +# FIX-1: digest is resolved EXACTLY ONCE per staging service across the +# whole preflight+execute, and the SAME ref P1 verified is what +# gets pinned by execute_promotion (no TOCTOU). +# FIX-2: a Railway::GHCR::Error raised mid-loop in P1 produces a per- +# service REFUSE finding and the loop continues for remaining +# services — earlier findings are NOT discarded. +# FIX-3: pin_and_verify asserts the serviceInstanceRedeploy result is +# truthy (symmetric with the serviceInstanceUpdate result check). +# FIX-4: P2 emits a WARN finding when deployment meta is not a Hash, so +# the silent skip of the in-flight race-check is visible. +# FIX-5: when pin_and_verify raises mid-loop in execute_promotion, a +# loud PARTIAL-PROMOTION report names the already-pinned services. +class PromoteResolveOnceTest < Minitest::Test + # FakeGHCR that counts resolve_digest calls per ref. + class CountingGHCR + attr_reader :resolve_calls + + def initialize(resolve_map:, exists_set: nil) + @resolve_map = resolve_map + @exists_set = exists_set + @resolve_calls = Hash.new(0) + end + + def resolve_digest(ref) + @resolve_calls[ref] += 1 + return ref.split("@", 2).last if ref.include?("@sha256:") + @resolve_map[ref] + end + + def manifest_exists(ref) + return :missing if @exists_set && !@exists_set.include?(ref) + :exists + end + + def parse_image_ref(ref) + Railway::GHCR.allocate.parse_image_ref(ref) + end + end + + # GQL fake reusing the pattern from PromoteExecuteTest::RecordingGQL, + # but with optional knobs for FIX-3 (redeploy returns false) and FIX-5 + # (per-service update failure). + class RecordingGQL + def initialize(redeploy_result: true, update_fail_for: nil) + @calls = [] + # Per-service post-update state so multi-service test cases don't + # leak each other's pinned image into pre-update rechecks. Keyed by + # serviceId. Value is { image:, post_ts: }. Pre-update returns the + # "OLD" snapshot (with an early ts) for any service not yet pinned. + @post = {} + @ts_counter = 0 + @redeploy_result = redeploy_result + @update_fail_for = update_fail_for # service_id whose update returns false + end + attr_reader :calls + + def query(q, vars = {}) + @calls << [q, vars] + sid = vars[:serviceId] + if q.include?("serviceInstanceUpdate") + if @update_fail_for && sid == @update_fail_for + return { "serviceInstanceUpdate" => false } + end + @ts_counter += 1 + @post[sid] = { image: vars[:image], ts: "2026-05-29T00:00:%02dZ" % @ts_counter } + { "serviceInstanceUpdate" => true } + elsif q.include?("serviceInstanceRedeploy") + { "serviceInstanceRedeploy" => @redeploy_result } + elsif q.include?("ServiceInstanceRecheck") + if (entry = @post[sid]) + { + "serviceInstance" => { + "id" => "i", + "source" => { "image" => entry[:image] }, + "updatedAt" => entry[:ts], + }, + } + else + { + "serviceInstance" => { + "id" => "i", + "source" => { "image" => "ghcr.io/copilotkit/x@sha256:OLD" }, + "updatedAt" => "2026-05-28T00:00:00Z", + }, + } + end + else + {} + end + end + + def pinned_images + @calls.select { |q, _| q.include?("serviceInstanceUpdate") }.map { |_, v| v[:image] } + end + end + + # Silence pin_and_verify's 10s-per-retry waits. + def with_fast_sleeper + original = Railway::PromoteCommand.const_get(:RETRY_DELAY_SEC) + Railway::PromoteCommand.send(:remove_const, :RETRY_DELAY_SEC) + Railway::PromoteCommand.const_set(:RETRY_DELAY_SEC, 0) + yield + ensure + Railway::PromoteCommand.send(:remove_const, :RETRY_DELAY_SEC) + Railway::PromoteCommand.const_set(:RETRY_DELAY_SEC, original) + end + + def make_svc(name, image:) + { + "name" => name, "service_id" => "svc-stg-#{name}", + "image" => image, + "env_keys" => [], + "start_command" => "node server.js", "healthcheck_path" => "/health", + "region" => "us-west", "replicas" => 1, "restart_policy" => "ON_FAILURE", + } + end + + def make_prod_svc(name) + { + "name" => name, "service_id" => "svc-prod-#{name}", + "image" => "ghcr.io/copilotkit/#{name}@sha256:OLD", + "env_keys" => [], + "start_command" => "node server.js", "healthcheck_path" => "/health", + "region" => "us-west", "replicas" => 1, "restart_policy" => "ON_FAILURE", + } + end + + # =================== FIX-1 =================== + + def test_fix1_resolve_digest_called_exactly_once_per_service + # Single :latest staging service. After full preflight + execute, the + # ghcr.resolve_digest call counter MUST be exactly 1 for that ref — + # not 2 (which was the pre-fix TOCTOU duplicate-resolve behavior). + ghcr = CountingGHCR.new(resolve_map: { "ghcr.io/copilotkit/x:latest" => "sha256:abc123" }) + gql = RecordingGQL.new + + cmd = Railway::PromoteCommand.new(["--non-interactive", "--yes", "--confirm-divergence"]) + cmd.parser.parse!(cmd.argv) + cmd.instance_variable_set(:@staging_snapshot, { + "services" => [make_svc("x", image: "ghcr.io/copilotkit/x:latest")], + }) + cmd.instance_variable_set(:@prod_snapshot, { "services" => [make_prod_svc("x")] }) + cmd.instance_variable_set(:@gql, gql) + cmd.instance_variable_set(:@ghcr, ghcr) + cmd.define_singleton_method(:fetch_latest_staging_deployments) do |_svc_id| + [{ "id" => "d", "status" => "SUCCESS", + "meta" => { "image" => "ghcr.io/copilotkit/x@sha256:abc123" } }] + end + cmd.define_singleton_method(:run_staging_probe) { |services:| { ok: true, summary: "" } } + + out, _ = with_fast_sleeper { capture_io { @rc = cmd.run_with_preflight_only } } + assert_equal 0, @rc, "promote should succeed; got out=#{out}" + + # The exact ref pinned MUST be what P1 verified (resolve-once). + assert_equal ["ghcr.io/copilotkit/x@sha256:abc123"], gql.pinned_images, + "must pin the SAME digest-form ref P1 verified" + + # The :latest tag must have been resolved EXACTLY ONCE — not twice. + assert_equal 1, ghcr.resolve_calls["ghcr.io/copilotkit/x:latest"], + "resolve_digest must be called exactly once for the staging tag; calls=#{ghcr.resolve_calls.inspect}" + end + + # =================== FIX-2 =================== + + # GHCR fake whose manifest_exists raises Railway::GHCR::Error on the + # SECOND service. The first service's findings must survive. + class RaisingOnSecondGHCR + def initialize + @manifest_calls = 0 + end + + def resolve_digest(ref) + return ref.split("@", 2).last if ref.include?("@sha256:") + "sha256:resolved_for_#{ref}" + end + + def manifest_exists(_ref) + @manifest_calls += 1 + raise Railway::GHCR::Error, "boom on call ##{@manifest_calls}" if @manifest_calls == 2 + :exists + end + + def parse_image_ref(ref); Railway::GHCR.allocate.parse_image_ref(ref); end + end + + def test_fix2_p1_rescue_is_per_service_not_method_level + # Two services. The SECOND triggers a GHCR::Error in manifest_exists. + # Before the fix: method-level rescue replaced ALL findings with a + # single "REFUSE: P1: GHCR check raised ..." entry, dropping the + # first service's clean record AND scoping the error to "P1" with no + # service name. After the fix: the first service has no finding + # (it passed), the second service has a per-service REFUSE finding + # that names the service. + cmd = Railway::PromoteCommand.new([]) + cmd.instance_variable_set(:@ghcr, RaisingOnSecondGHCR.new) + staging = { + "services" => [ + make_svc("a", image: "ghcr.io/copilotkit/a:latest"), + make_svc("b", image: "ghcr.io/copilotkit/b:latest"), + ], + } + findings = cmd.send(:check_p1_ghcr_digests, staging) + + refute(findings.any? { |f| f =~ /REFUSE: P1 \(a\)/ }, + "service 'a' passed P1 and must have no REFUSE; findings=#{findings.inspect}") + assert(findings.any? { |f| f =~ /REFUSE: P1 \(b\).*boom on call #2/ }, + "service 'b' must have a per-service P1 REFUSE naming it; findings=#{findings.inspect}") + end + + # =================== FIX-3 =================== + + def test_fix3_pin_and_verify_raises_when_redeploy_returns_false + gql = RecordingGQL.new(redeploy_result: false) + err = assert_raises(Railway::PromoteCommand::MutationError) do + Railway::PromoteCommand.pin_and_verify(gql, + service_id: "svc-x", env_id: "env-prod", + image: "ghcr.io/copilotkit/x@sha256:abc", + sleeper: ->(_) {}) + end + assert_match(/serviceInstanceRedeploy/i, err.message, + "MutationError must reference the redeploy mutation; got: #{err.message}") + end + + # =================== FIX-4 =================== + + def test_fix4_p2_warns_when_meta_is_a_string + # P2 already guards meta.is_a?(Hash) (no crash), but the silent skip + # of the race-check should produce a WARN finding so it's visible. + cmd = Railway::PromoteCommand.new([]) + cmd.define_singleton_method(:fetch_latest_staging_deployments) do |_svc_id| + [{ "id" => "d", "status" => "SUCCESS", + "meta" => "raw-string-not-a-hash" }] + end + staging = { + "services" => [{ + "name" => "x", "service_id" => "svc-1", + "image" => "ghcr.io/copilotkit/x@sha256:abc", "digest" => "sha256:abc", + "env_keys" => [], + }], + } + findings = cmd.send(:check_p2_staging_deployments, staging) + assert(findings.any? { |f| f =~ /WARN: P2 \(x\).*meta.*String.*Hash/ }, + "expected WARN finding for non-Hash meta; got: #{findings.inspect}") + end + + # =================== FIX-5 =================== + + def test_fix5_partial_promotion_report_on_midloop_failure + # Three staging services [a, b, c]; pin succeeds for a and b, fails + # for c (serviceInstanceUpdate returns false → MutationError). + # The output must name a and b as already-pinned and c as failed. + ghcr = CountingGHCR.new(resolve_map: { + "ghcr.io/copilotkit/a:latest" => "sha256:aaa", + "ghcr.io/copilotkit/b:latest" => "sha256:bbb", + "ghcr.io/copilotkit/c:latest" => "sha256:ccc", + }) + gql = RecordingGQL.new(update_fail_for: "svc-prod-c") + + cmd = Railway::PromoteCommand.new(["--non-interactive", "--yes", "--confirm-divergence"]) + cmd.parser.parse!(cmd.argv) + cmd.instance_variable_set(:@staging_snapshot, { + "services" => [ + make_svc("a", image: "ghcr.io/copilotkit/a:latest"), + make_svc("b", image: "ghcr.io/copilotkit/b:latest"), + make_svc("c", image: "ghcr.io/copilotkit/c:latest"), + ], + }) + cmd.instance_variable_set(:@prod_snapshot, { + "services" => [make_prod_svc("a"), make_prod_svc("b"), make_prod_svc("c")], + }) + cmd.instance_variable_set(:@gql, gql) + cmd.instance_variable_set(:@ghcr, ghcr) + cmd.define_singleton_method(:fetch_latest_staging_deployments) do |svc_id| + svc = svc_id.sub("svc-stg-", "") + digest = { "a" => "sha256:aaa", "b" => "sha256:bbb", "c" => "sha256:ccc" }.fetch(svc) + [{ "id" => "d", "status" => "SUCCESS", + "meta" => { "image" => "ghcr.io/copilotkit/#{svc}@#{digest}" } }] + end + cmd.define_singleton_method(:run_staging_probe) { |services:| { ok: true, summary: "" } } + + out, err = with_fast_sleeper { capture_io { @rc = cmd.run_with_preflight_only } } + assert_equal 1, @rc, "execute_promotion must return 1 when a mid-loop pin fails" + combined = out + err + assert_match(/PARTIAL PROMOTION/i, combined, + "must emit a loud PARTIAL PROMOTION report; output=#{combined}") + assert_match(/already pinned.*\ba\b.*\bb\b/m, combined, + "report must name the already-pinned services a and b; output=#{combined}") + assert_match(/FAILED on c/, combined, + "report must name the failing service c; output=#{combined}") + end +end diff --git a/showcase/bin/spec/test_promote_single_service.rb b/showcase/bin/spec/test_promote_single_service.rb new file mode 100644 index 00000000000..b09d98609f6 --- /dev/null +++ b/showcase/bin/spec/test_promote_single_service.rb @@ -0,0 +1,266 @@ +# frozen_string_literal: true + +# bin/railway promote — single-service positional + --digest support. +# +# Context: showcase_promote.yml loops services and invokes +# bin/railway promote "$svc" [--digest "$DIGEST"] +# but the original PromoteCommand parser accepts NEITHER. The positional +# was silently discarded (promoting the ENTIRE fleet per iteration) and +# --digest raised OptionParser::InvalidOption (the workflow aborts under +# set -euo pipefail). These tests pin the fix contract. + +require_relative "spec_helper" +require "stringio" + +class PromoteSingleServiceTest < Minitest::Test + # Minimal benign GQL fake (P2 deployments query, etc.). The promotion + # tests below stub fetch_latest_staging_deployments and run_staging_probe + # so this is only used as a fallback. + class FakeGQLBenign + attr_reader :calls + def initialize + @calls = [] + @pinned_by_service = {} # serviceId => image; tracks per-service pin + @ts_counter = 0 + end + def query(q, vars = {}) + @calls << [q, vars] + sid = vars[:serviceId] + if q.include?("serviceInstanceUpdate") + @pinned_by_service[sid] = vars[:image] + { "serviceInstanceUpdate" => true } + elsif q.include?("serviceInstanceRedeploy") + { "serviceInstanceRedeploy" => true } + elsif q.include?("ServiceInstanceRecheck") + pinned = @pinned_by_service[sid] + if pinned.nil? + { + "serviceInstance" => { + "id" => "i", + "source" => { "image" => "ghcr.io/copilotkit/old@sha256:OLD" }, + "updatedAt" => "2026-05-28T00:00:00Z", + }, + } + else + @ts_counter += 1 + { + "serviceInstance" => { + "id" => "i", + "source" => { "image" => pinned }, + "updatedAt" => "2026-05-29T00:00:#{format('%02d', @ts_counter)}Z", + }, + } + end + else + { "deployments" => { "edges" => [] } } + end + end + + def pinned_services + @calls.select { |q, _| q.include?("serviceInstanceUpdate") } + .map { |_, vars| [vars[:serviceId], vars[:image]] } + end + + def pinned_image_for(service_id) + row = @calls.find { |q, vars| q.include?("serviceInstanceUpdate") && vars[:serviceId] == service_id } + row && row[1][:image] + end + end + + class FakeGHCR + def initialize(resolve_map: {}) + @resolve_map = resolve_map + end + def resolve_digest(ref) + return ref.split("@", 2).last if ref.include?("@sha256:") + @resolve_map[ref] || "sha256:default_digest_for_#{ref.sub(/[^a-z0-9]/i, '_')[0, 16]}" + end + def manifest_exists(_ref); :exists; end + def parse_image_ref(ref); Railway::GHCR.allocate.parse_image_ref(ref); end + end + + def make_service(name, prod_image: "ghcr.io/copilotkit/#{name}@sha256:OLD#{name.gsub('-', '')}") + { + "name" => name, "service_id" => "svc-#{name}", + "image" => "ghcr.io/copilotkit/#{name}:latest", + "env_keys" => [], + "start_command" => "node server.js", "healthcheck_path" => "/health", + "region" => "us-west", "replicas" => 1, "restart_policy" => "ON_FAILURE", + } + end + + def make_prod(name) + { + "name" => name, "service_id" => "prod-#{name}", + "image" => "ghcr.io/copilotkit/#{name}@sha256:OLD#{name.gsub('-', '')}", + "env_keys" => [], + "start_command" => "node server.js", "healthcheck_path" => "/health", + "region" => "us-west", "replicas" => 1, "restart_policy" => "ON_FAILURE", + } + end + + def build_cmd(argv) + cmd = Railway::PromoteCommand.new(argv + ["--non-interactive", "--yes", "--confirm-divergence"]) + # IMPORTANT: do NOT call parser.parse! here — tests must exercise the + # full `run` path so argv parsing (positional + --digest) is covered. + cmd + end + + def install_two_service_fixture(cmd, gql, ghcr) + # Two staging services — "aimock" and "harness" — both also in prod. + staging_svcs = [make_service("aimock"), make_service("harness")] + prod_svcs = [make_prod("aimock"), make_prod("harness")] + cmd.instance_variable_set(:@staging_snapshot, { "services" => staging_svcs }) + cmd.instance_variable_set(:@prod_snapshot, { "services" => prod_svcs }) + cmd.instance_variable_set(:@gql, gql) + cmd.instance_variable_set(:@ghcr, ghcr) + # Skip P2 race-check: stub deployments to SUCCESS with the digest the + # promote will actually pin (which, when --digest is set, is the + # override — NOT the GHCR-resolved one). Read options[:digest] off the + # command at call-time so this works for both default and override + # paths without test-side branching. + cmd.define_singleton_method(:fetch_latest_staging_deployments) do |service_id| + name = service_id.sub(/^svc-/, "") + override = options[:digest] + if override && options[:service] == name && override.include?("@") + ref = override + else + ghcr_obj = instance_variable_get(:@ghcr) + digest = ghcr_obj.resolve_digest("ghcr.io/copilotkit/#{name}:latest") + ref = "ghcr.io/copilotkit/#{name}@#{digest}" + end + [{ "id" => "d", "status" => "SUCCESS", "meta" => { "image" => ref } }] + end + cmd.define_singleton_method(:run_staging_probe) { |services:| { ok: true, summary: "" } } + end + + def with_fast_sleeper + original = Railway::PromoteCommand.const_get(:RETRY_DELAY_SEC) + Railway::PromoteCommand.send(:remove_const, :RETRY_DELAY_SEC) + Railway::PromoteCommand.const_set(:RETRY_DELAY_SEC, 0) + yield + ensure + Railway::PromoteCommand.send(:remove_const, :RETRY_DELAY_SEC) + Railway::PromoteCommand.const_set(:RETRY_DELAY_SEC, original) + end + + # ── (a) Positional service: only that service is promoted. ──────────── + + def test_positional_service_promotes_only_that_service + gql = FakeGQLBenign.new + ghcr = FakeGHCR.new(resolve_map: { + "ghcr.io/copilotkit/aimock:latest" => "sha256:NEW_AIMOCK", + "ghcr.io/copilotkit/harness:latest" => "sha256:NEW_HARNESS", + }) + cmd = build_cmd(["aimock"]) + install_two_service_fixture(cmd, gql, ghcr) + rc = nil + out, _ = with_fast_sleeper { capture_io { rc = cmd.run } } + assert_equal 0, rc, "single-service promote should succeed; got out=#{out}" + + pinned = gql.pinned_services + assert_equal 1, pinned.size, "must pin exactly ONE service when positional is given (got #{pinned.inspect})" + sid, image = pinned.first + assert_equal "prod-aimock", sid, "must target prod-aimock, not the whole fleet" + assert_equal "ghcr.io/copilotkit/aimock@sha256:NEW_AIMOCK", image + # Loud regression guard against silent fleet-wide pin. + refute gql.pinned_image_for("prod-harness"), "harness must NOT be touched when only aimock was requested" + end + + # ── (b) Positional + --digest: digest overrides resolved ref. ───────── + + def test_positional_service_with_digest_pins_that_digest + gql = FakeGQLBenign.new + ghcr = FakeGHCR.new(resolve_map: { + "ghcr.io/copilotkit/aimock:latest" => "sha256:NEW_AIMOCK", + "ghcr.io/copilotkit/harness:latest" => "sha256:NEW_HARNESS", + }) + override = "ghcr.io/copilotkit/aimock@sha256:OVERRIDE_DIGEST" + cmd = build_cmd(["aimock", "--digest", override]) + install_two_service_fixture(cmd, gql, ghcr) + rc = nil + out, _ = with_fast_sleeper { capture_io { rc = cmd.run } } + assert_equal 0, rc, "promote with --digest should succeed; got out=#{out}" + + pinned = gql.pinned_services + assert_equal 1, pinned.size, "--digest must still pin only the named service" + sid, image = pinned.first + assert_equal "prod-aimock", sid + assert_equal override, image, + "must pin the EXPLICIT --digest ref, not the GHCR-resolved one" + end + + # ── (c) --digest without positional → fail fast. ────────────────────── + + def test_digest_without_service_fails_fast + cmd = build_cmd(["--digest", "ghcr.io/copilotkit/x@sha256:abc"]) + ghcr = FakeGHCR.new + gql = FakeGQLBenign.new + install_two_service_fixture(cmd, gql, ghcr) + ex = nil + # die! calls Kernel#exit which raises SystemExit; capture it so the + # test can assert the exit code AND the error message together. + out, err = capture_io { ex = assert_raises(SystemExit) { cmd.run } } + refute_equal 0, ex.status, "--digest without a service must NOT promote the fleet" + combined = out + err + assert_match(/--digest.*requires.*service|--digest.*without.*service|service.*required.*--digest/i, + combined, "must surface a clear error explaining --digest needs a positional service") + # And it must NOT have issued any pin mutations. + assert_empty gql.pinned_services, "no pin mutations should run on the fail-fast path" + end + + # ── (d) Unknown positional service → fail fast with valid names. ────── + + def test_unknown_positional_service_fails_with_valid_names_listed + cmd = build_cmd(["this-service-does-not-exist"]) + ghcr = FakeGHCR.new + gql = FakeGQLBenign.new + install_two_service_fixture(cmd, gql, ghcr) + ex = nil + out, err = capture_io { ex = assert_raises(SystemExit) { cmd.run } } + refute_equal 0, ex.status, "unknown service must fail, not silently no-op or fall through to fleet" + combined = out + err + assert_match(/unknown.*service|not.*known|invalid.*service/i, combined) + # The error must list at least one canonical staging name (e.g. aimock) + # so the operator can self-correct. + assert_match(/aimock/, combined, + "valid-names enumeration must include canonical staging services") + assert_empty gql.pinned_services, "no pin mutations on unknown-service error path" + end + + # ── (e) No positional → preserve full-fleet behavior (regression). ──── + + def test_no_positional_preserves_full_fleet_promote + gql = FakeGQLBenign.new + ghcr = FakeGHCR.new(resolve_map: { + "ghcr.io/copilotkit/aimock:latest" => "sha256:NEW_AIMOCK", + "ghcr.io/copilotkit/harness:latest" => "sha256:NEW_HARNESS", + }) + cmd = build_cmd([]) + install_two_service_fixture(cmd, gql, ghcr) + rc = nil + out, _ = with_fast_sleeper { capture_io { rc = cmd.run } } + assert_equal 0, rc, "no-arg promote must keep working for operators; got out=#{out}" + + pinned_ids = gql.pinned_services.map(&:first).sort + assert_equal ["prod-aimock", "prod-harness"], pinned_ids, + "no-arg promote must continue to pin the whole fleet" + end + + # ── Parser-level coverage for --digest flag and positional acceptance. ─ + + def test_parser_accepts_digest_flag + c = Railway::PromoteCommand.new(["--digest", "ghcr.io/copilotkit/x@sha256:abc"]) + # Must NOT raise InvalidOption. + c.parser.parse!(c.argv) + assert_equal "ghcr.io/copilotkit/x@sha256:abc", c.options[:digest] + end + + def test_parser_leaves_positional_in_argv + c = Railway::PromoteCommand.new(["aimock", "--yes"]) + c.parser.parse!(c.argv) + # OptionParser#parse! consumes known flags; the positional must remain. + assert_includes c.argv, "aimock" + assert c.options[:yes] + end +end diff --git a/showcase/bin/spec/test_promote_single_service_fleet_invariants.rb b/showcase/bin/spec/test_promote_single_service_fleet_invariants.rb new file mode 100644 index 00000000000..06655404b89 --- /dev/null +++ b/showcase/bin/spec/test_promote_single_service_fleet_invariants.rb @@ -0,0 +1,281 @@ +# frozen_string_literal: true + +# bin/railway promote — fleet-scoped invariants must evaluate against the +# FULL un-narrowed snapshots, not the single-service narrowed view. +# +# Background: the per-service `promote ` feature narrows BOTH +# @staging_snapshot and @prod_snapshot to one service before preflight. Two +# fleet-scoped checks were incorrectly co-narrowed: +# +# (1) check_expected_prod_domains compares the FLEET-WIDE public-host set +# (EXPECTED_DOMAINS[PRODUCTION_ENV_ID]) against the union of +# custom_domains across `prod["services"]`. Narrowed prod has ~1 +# service → ~4 fleet hosts look "missing" → spurious WARN. The real +# workflow (.github/workflows/showcase_promote.yml) does NOT pass +# --confirm-divergence, so promote refuses on its first iteration and +# `set -e` aborts the loop. Healthy fleet, no real divergence, +# blocked. +# +# (2) check_service_set_parity diffs staging vs prod service names. After +# narrowing both to the same single name, the diff is always empty — +# the invariant is dead. Worse: if the target is in staging but +# ABSENT from prod, execute_promotion's `next unless find_service` +# silently skips and returns rc=0. Operator sees "success" while +# prod was untouched. +# +# These tests pin the fix contract: fleet-scoped checks read the FULL +# snapshots; per-service mutation reads the narrowed snapshots. + +require_relative "spec_helper" +require "stringio" + +class PromoteSingleServiceFleetInvariantsTest < Minitest::Test + # Reuse the same minimal benign GQL/GHCR fakes as + # test_promote_single_service.rb (parallel fixture style — kept inline + # here so this spec is self-contained). + class FakeGQLBenign + attr_reader :calls + def initialize + @calls = [] + @pinned_by_service = {} + @ts_counter = 0 + end + def query(q, vars = {}) + @calls << [q, vars] + sid = vars[:serviceId] + if q.include?("serviceInstanceUpdate") + @pinned_by_service[sid] = vars[:image] + { "serviceInstanceUpdate" => true } + elsif q.include?("serviceInstanceRedeploy") + { "serviceInstanceRedeploy" => true } + elsif q.include?("ServiceInstanceRecheck") + pinned = @pinned_by_service[sid] + if pinned.nil? + { + "serviceInstance" => { + "id" => "i", + "source" => { "image" => "ghcr.io/copilotkit/old@sha256:OLD" }, + "updatedAt" => "2026-05-28T00:00:00Z", + }, + } + else + @ts_counter += 1 + { + "serviceInstance" => { + "id" => "i", + "source" => { "image" => pinned }, + "updatedAt" => "2026-05-29T00:00:#{format('%02d', @ts_counter)}Z", + }, + } + end + else + { "deployments" => { "edges" => [] } } + end + end + + def pinned_services + @calls.select { |q, _| q.include?("serviceInstanceUpdate") } + .map { |_, vars| [vars[:serviceId], vars[:image]] } + end + + def pinned_image_for(service_id) + row = @calls.find { |q, vars| q.include?("serviceInstanceUpdate") && vars[:serviceId] == service_id } + row && row[1][:image] + end + end + + class FakeGHCR + def initialize(resolve_map: {}) + @resolve_map = resolve_map + end + def resolve_digest(ref) + return ref.split("@", 2).last if ref.include?("@sha256:") + @resolve_map[ref] || "sha256:default_digest_for_#{ref.sub(/[^a-z0-9]/i, '_')[0, 16]}" + end + def manifest_exists(_ref); :exists; end + def parse_image_ref(ref); Railway::GHCR.allocate.parse_image_ref(ref); end + end + + # Public hosts as published by EXPECTED_DOMAINS for the production env. + # These are the SSOT-derived fleet-wide hosts; pulled here verbatim so + # the fixture's full prod snapshot legitimately satisfies the check. + FLEET_PUBLIC_PROD_HOSTS = [ + "dashboard.showcase.copilotkit.ai", + "docs.copilotkit.ai", + "dojo.showcase.copilotkit.ai", + "hooks.showcase.copilotkit.ai", + "showcase.copilotkit.ai", + ].freeze + + def make_staging_service(name) + { + "name" => name, "service_id" => "svc-#{name}", + "image" => "ghcr.io/copilotkit/#{name}:latest", + "env_keys" => [], + "start_command" => "node server.js", "healthcheck_path" => "/health", + "region" => "us-west", "replicas" => 1, "restart_policy" => "ON_FAILURE", + } + end + + def make_prod_service(name, custom_domains: []) + { + "name" => name, "service_id" => "prod-#{name}", + "image" => "ghcr.io/copilotkit/#{name}@sha256:OLD#{name.gsub(/[^a-z0-9]/i, '')}", + "env_keys" => [], + "start_command" => "node server.js", "healthcheck_path" => "/health", + "region" => "us-west", "replicas" => 1, "restart_policy" => "ON_FAILURE", + "custom_domains" => custom_domains, + } + end + + # Build a FLEET-SHAPED snapshot pair. The full prod snapshot's union of + # custom_domains across services EXACTLY satisfies the fleet-wide + # EXPECTED_DOMAINS[PRODUCTION_ENV_ID] set, so a healthy fleet must NOT + # produce a domain WARN. Two of those services ("aimock", "harness") + # also exist in staging — promote will target one of them. + def install_fleet_fixture(cmd, gql, ghcr, prod_includes_target: true, target: "aimock") + # Five "domain-bearing" services so the prod fleet legitimately + # carries all FLEET_PUBLIC_PROD_HOSTS, even when promote is + # narrowed to a service that doesn't itself own any public host. + domain_services_prod = [ + make_prod_service("dashboard", custom_domains: ["dashboard.showcase.copilotkit.ai"]), + make_prod_service("docs", custom_domains: ["docs.copilotkit.ai"]), + make_prod_service("dojo", custom_domains: ["dojo.showcase.copilotkit.ai"]), + make_prod_service("webhooks", custom_domains: ["hooks.showcase.copilotkit.ai"]), + make_prod_service("shell", custom_domains: ["showcase.copilotkit.ai"]), + ] + # Plus the targeted service (no public domain of its own — it's an + # internal aimock). Optionally OMIT it from prod to exercise BUG #2. + domain_services_prod << make_prod_service(target) if prod_includes_target + # And a non-targeted sibling that exists in both staging and prod. + sibling = "harness" + domain_services_prod << make_prod_service(sibling) + + # Staging mirrors prod's service NAMES (set parity) — every prod + # service has a corresponding staging entry. (For BUG #2, target is + # in staging but absent from prod by design.) + staging_services = (domain_services_prod.map { |s| s["name"] } + [target]).uniq.map { |n| make_staging_service(n) } + + cmd.instance_variable_set(:@staging_snapshot, { "services" => staging_services }) + cmd.instance_variable_set(:@prod_snapshot, { "services" => domain_services_prod }) + cmd.instance_variable_set(:@gql, gql) + cmd.instance_variable_set(:@ghcr, ghcr) + + # P2 race-check stub — see test_promote_single_service.rb for the + # same pattern. Mirror the pin the promote will actually issue. + cmd.define_singleton_method(:fetch_latest_staging_deployments) do |service_id| + name = service_id.sub(/^svc-/, "") + override = options[:digest] + if override && options[:service] == name && override.include?("@") + ref = override + else + ghcr_obj = instance_variable_get(:@ghcr) + digest = ghcr_obj.resolve_digest("ghcr.io/copilotkit/#{name}:latest") + ref = "ghcr.io/copilotkit/#{name}@#{digest}" + end + [{ "id" => "d", "status" => "SUCCESS", "meta" => { "image" => ref } }] + end + cmd.define_singleton_method(:run_staging_probe) { |services:| { ok: true, summary: "" } } + end + + def build_cmd(argv) + # CRITICAL: this test MUST mirror the real workflow invocation — + # showcase_promote.yml calls `promote ` WITHOUT + # --confirm-divergence. Do NOT add --confirm-divergence here. + Railway::PromoteCommand.new(argv + ["--non-interactive", "--yes"]) + end + + def with_fast_sleeper + original = Railway::PromoteCommand.const_get(:RETRY_DELAY_SEC) + Railway::PromoteCommand.send(:remove_const, :RETRY_DELAY_SEC) + Railway::PromoteCommand.const_set(:RETRY_DELAY_SEC, 0) + yield + ensure + Railway::PromoteCommand.send(:remove_const, :RETRY_DELAY_SEC) + Railway::PromoteCommand.const_set(:RETRY_DELAY_SEC, original) + end + + # ── BUG #1: healthy fleet, single-service promote, NO --confirm-divergence. + # Today (red): narrowed prod has 1 service whose custom_domains do not + # include the 5 fleet public hosts → WARN → run_with_preflight_only + # refuses (rc=1) because options[:confirm_divergence] is false. + # After fix (green): rc=0, target pinned, siblings untouched. + def test_healthy_fleet_single_service_promote_without_confirm_divergence_succeeds + gql = FakeGQLBenign.new + ghcr = FakeGHCR.new(resolve_map: { + "ghcr.io/copilotkit/aimock:latest" => "sha256:NEW_AIMOCK", + }) + cmd = build_cmd(["aimock"]) + install_fleet_fixture(cmd, gql, ghcr, prod_includes_target: true, target: "aimock") + + rc = nil + out, _ = with_fast_sleeper { capture_io { rc = cmd.run } } + + assert_equal 0, rc, + "single-service promote against a HEALTHY fleet must NOT be " \ + "refused for missing fleet-wide domains (workflow doesn't pass " \ + "--confirm-divergence). Got rc=#{rc.inspect}; out=\n#{out}" + + # Spurious-WARN smoke check: we must NOT have emitted a fleet-domains + # WARN since the un-narrowed fleet satisfies EXPECTED_DOMAINS. + refute_match(/WARN: production missing expected custom domains/, out, + "fleet-domain check should evaluate the FULL prod snapshot; the " \ + "narrowed view must not synthesize a phantom 'missing' WARN") + + pinned = gql.pinned_services + assert_equal 1, pinned.size, + "exactly one service should be promoted (target only); got #{pinned.inspect}" + sid, image = pinned.first + assert_equal "prod-aimock", sid + assert_equal "ghcr.io/copilotkit/aimock@sha256:NEW_AIMOCK", image + + # Siblings must remain untouched. + refute gql.pinned_image_for("prod-harness"), + "harness must not be pinned when only aimock was promoted" + refute gql.pinned_image_for("prod-dashboard"), + "dashboard must not be pinned when only aimock was promoted" + end + + # ── BUG #2: target exists in staging but is ABSENT from prod. + # Today (red): both snapshots get narrowed; service_set_parity (narrowed) + # diffs [target] vs [] but the test fixture wouldn't have a prod entry + # at all after narrowing... wait — narrowing prod yields an EMPTY + # services list (no match). check_service_set_parity then diffs + # ["aimock"] vs [] → finds REFUSE "services in staging not in prod". + # BUT: the per-service feature's narrowing also narrows staging to + # [target], so the diff against an empty prod *should* surface the + # REFUSE. Verify the failure mode is the silent-skip path: + # execute_promotion's `next unless find_service` for absent prod. + # + # Either way, the contract is: if the targeted service is in staging + # but ABSENT from prod, promote must FAIL LOUD (nonzero rc + clear + # message), never silently rc=0 with no pin mutations. + def test_target_absent_from_prod_fails_loud + gql = FakeGQLBenign.new + ghcr = FakeGHCR.new(resolve_map: { + "ghcr.io/copilotkit/aimock:latest" => "sha256:NEW_AIMOCK", + }) + cmd = build_cmd(["aimock"]) + # prod_includes_target=false → "aimock" exists in staging but NOT + # in the full prod snapshot. The fleet otherwise mirrors itself + # (no spurious set-parity issues), so the REFUSE we observe is + # specifically the staging-only-target case. + install_fleet_fixture(cmd, gql, ghcr, prod_includes_target: false, target: "aimock") + + rc = nil + out, _ = with_fast_sleeper { capture_io { rc = cmd.run } } + + refute_equal 0, rc, + "target service present in staging but ABSENT from prod must " \ + "FAIL LOUD (nonzero rc), not silently succeed with no pin. " \ + "Got rc=#{rc.inspect}; out=\n#{out}" + + # The error message must be informative — surface the parity + # violation rather than a generic / opaque failure. + assert_match(/REFUSE: services in staging not in prod/, out, + "must surface a clear REFUSE explaining target is missing from prod") + + assert_empty gql.pinned_services, + "no pin mutations may be issued when target is absent from prod" + end +end diff --git a/showcase/bin/spec/test_snapshot_graphql.rb b/showcase/bin/spec/test_snapshot_graphql.rb index 2a5c90c02fc..492bacaf335 100644 --- a/showcase/bin/spec/test_snapshot_graphql.rb +++ b/showcase/bin/spec/test_snapshot_graphql.rb @@ -59,15 +59,21 @@ def env_vars_response_with_per_service_keys } end - def service_instance_response(image:, start_cmd: nil, domains: []) + def service_instance_response(image:, start_cmd: nil, domains: [], + healthcheck_path: nil, region: nil, + num_replicas: nil, restart_policy: nil) { "serviceInstance" => { - "id" => "inst-#{image[/sha256:[a-f0-9]+/] || 'tag'}", - "serviceId" => "svc-aimock", - "environmentId" => Railway::PRODUCTION_ENV_ID, - "startCommand" => start_cmd, - "source" => { "image" => image, "repo" => nil }, - "latestDeployment" => { "id" => "dep-1", "status" => "SUCCESS" }, + "id" => "inst-#{image[/sha256:[a-f0-9]+/] || 'tag'}", + "serviceId" => "svc-aimock", + "environmentId" => Railway::PRODUCTION_ENV_ID, + "startCommand" => start_cmd, + "healthcheckPath" => healthcheck_path, + "region" => region, + "numReplicas" => num_replicas, + "restartPolicyType" => restart_policy, + "source" => { "image" => image, "repo" => nil }, + "latestDeployment" => { "id" => "dep-1", "status" => "SUCCESS" }, "domains" => { "customDomains" => domains.map { |d| { "id" => "cd-#{d}", "domain" => d } }, "serviceDomains" => [], @@ -101,7 +107,7 @@ def test_build_snapshot_uses_corrected_field_names_and_produces_pinned_entries snap = cmd.build_snapshot(Railway::PRODUCTION_ENV_ID) - assert_equal 1, snap["version"] + assert_equal 2, snap["version"] assert_equal 2, snap["services"].length aimock = snap["services"].find { |s| s["name"] == "aimock" } @@ -170,6 +176,68 @@ def test_redeploy_mutation_uses_serviceInstanceRedeploy_not_serviceInstanceDeplo assert_match(/serviceInstanceRedeploy\s*\(/, Railway::RestoreCommand::REDEPLOY_MUTATION) end + def test_snapshot_v2_captures_healthcheck_region_replicas_restart_policy + # P6 parity-matrix needs these four fields on every snapshot service. + # Snapshot schema is v2; SnapshotCommand#build_snapshot must map them + # from the new SERVICE_INSTANCE_QUERY selection set. + fake = FakeGQL.new( + "ProjectServices" => services_list_response, + "EnvVariables" => env_vars_response_with_per_service_keys, + "ServiceInstance" => lambda do |vars| + if vars[:serviceId] == "svc-aimock" + service_instance_response( + image: "ghcr.io/copilotkit/showcase-aimock@sha256:cafef00d", + start_cmd: "node /app/dist/cli.js", + domains: ["aimock.showcase.copilotkit.ai"], + healthcheck_path: "/healthz", + region: "us-west2", + num_replicas: 2, + restart_policy: "ON_FAILURE", + ) + else + service_instance_response( + image: "ghcr.io/copilotkit/showcase-shell@sha256:beef1234", + healthcheck_path: "/health", + region: "us-east1", + num_replicas: 1, + restart_policy: "ALWAYS", + ) + end + end, + ) + + cmd = Railway::SnapshotCommand.new(["--env", "production", "--dry-run"]) + cmd.instance_variable_set(:@gql, fake) + snap = cmd.build_snapshot(Railway::PRODUCTION_ENV_ID) + + aimock = snap["services"].find { |s| s["name"] == "aimock" } + assert_equal "/healthz", aimock["healthcheck_path"] + assert_equal "us-west2", aimock["region"] + assert_equal 2, aimock["replicas"] + assert_equal "ON_FAILURE", aimock["restart_policy"] + + shell = snap["services"].find { |s| s["name"] == "shell" } + assert_equal "/health", shell["healthcheck_path"] + assert_equal "us-east1", shell["region"] + assert_equal 1, shell["replicas"] + assert_equal "ALWAYS", shell["restart_policy"] + end + + def test_snapshot_io_read_accepts_v1_and_v2_snapshots + # rollback-commit replays historical snapshots from arbitrary git SHAs, + # so SnapshotIO.read MUST stay backwards-compat with v1 even though + # all NEW snapshots are written as v2. + require "tempfile" + [1, 2].each do |ver| + Tempfile.create(["snap-v#{ver}", ".yaml"]) do |f| + f.write(YAML.dump("version" => ver, "services" => [])) + f.flush + snap = Railway::SnapshotIO.read(f.path) + assert_equal ver, snap["version"] + end + end + end + def test_deploymentRollback_mutation_has_no_selection_set_because_it_returns_boolean # deploymentRollback's return type is Boolean (scalar). GraphQL # forbids a selection set on scalar fields. diff --git a/showcase/harness/README.md b/showcase/harness/README.md index 785d2f3b1cf..c5960446e80 100644 --- a/showcase/harness/README.md +++ b/showcase/harness/README.md @@ -432,7 +432,7 @@ docker buildx build --platform linux/amd64 --push \ # serviceInstanceDeployV2 forces a fresh snapshot — serviceInstanceRedeploy # replays the prior manifest and can re-pull a stale digest. RW_TOKEN=$(jq -r .user.token ~/.railway/config.json) -curl -s -X POST https://backboard.railway.com/graphql/v2 \ +curl -s -X POST https://backboard.railway.app/graphql/v2 \ -H "Authorization: Bearer $RW_TOKEN" \ -H "Content-Type: application/json" \ -d '{"query":"mutation { serviceInstanceDeployV2(serviceId:\"3a14bfed-0537-4d71-897b-7c593dca161d\", environmentId:\"b14919f4-6417-429f-848d-c6ae2201e04f\") }"}' diff --git a/showcase/harness/config/probes/d6-all-pills-e2e.yml b/showcase/harness/config/probes/d6-all-pills-e2e.yml index 4064bafe3f9..86f316c3faa 100644 --- a/showcase/harness/config/probes/d6-all-pills-e2e.yml +++ b/showcase/harness/config/probes/d6-all-pills-e2e.yml @@ -36,6 +36,9 @@ discovery: - showcase-shell-dashboard - showcase-shell-docs - showcase-shell-dojo + # Harness service for .NET integration testing — not a demo service, + # deployed: false in manifest, no aimock wiring yet. + - showcase-ms-agent-harness-dotnet # Decommissioned starters - showcase-starter-ag2 - showcase-starter-agno diff --git a/showcase/harness/config/probes/e2e-deep.yml b/showcase/harness/config/probes/e2e-deep.yml index bbe65f1e280..497596872a3 100644 --- a/showcase/harness/config/probes/e2e-deep.yml +++ b/showcase/harness/config/probes/e2e-deep.yml @@ -87,6 +87,9 @@ discovery: - showcase-shell-dashboard - showcase-shell-docs - showcase-shell-dojo + # Harness service for .NET integration testing — not a demo service, + # deployed: false in manifest, no aimock wiring yet. + - showcase-ms-agent-harness-dotnet # Decommissioned starters — Railway services stopped (PR #4390) - showcase-starter-ag2 - showcase-starter-agno diff --git a/showcase/harness/config/probes/e2e-demos.yml b/showcase/harness/config/probes/e2e-demos.yml index 89a61378cf3..22afc4306ae 100644 --- a/showcase/harness/config/probes/e2e-demos.yml +++ b/showcase/harness/config/probes/e2e-demos.yml @@ -118,6 +118,9 @@ discovery: - showcase-shell-dashboard - showcase-shell-docs - showcase-shell-dojo + # Harness service for .NET integration testing — not a demo service, + # deployed: false in manifest, no aimock wiring yet. + - showcase-ms-agent-harness-dotnet # Decommissioned starters — Railway services stopped (PR #4390) - showcase-starter-ag2 - showcase-starter-agno diff --git a/showcase/harness/config/probes/e2e-parity.yml b/showcase/harness/config/probes/e2e-parity.yml index d3854e22467..90e0e4314a8 100644 --- a/showcase/harness/config/probes/e2e-parity.yml +++ b/showcase/harness/config/probes/e2e-parity.yml @@ -102,6 +102,9 @@ discovery: - showcase-shell-dashboard - showcase-shell-docs - showcase-shell-dojo + # Harness service for .NET integration testing — not a demo service, + # deployed: false in manifest, no aimock wiring yet. + - showcase-ms-agent-harness-dotnet # Decommissioned starters — Railway services stopped (PR #4390) - showcase-starter-ag2 - showcase-starter-agno diff --git a/showcase/harness/config/probes/e2e-smoke.yml b/showcase/harness/config/probes/e2e-smoke.yml index 90cd865b3ec..1a57425750d 100644 --- a/showcase/harness/config/probes/e2e-smoke.yml +++ b/showcase/harness/config/probes/e2e-smoke.yml @@ -53,6 +53,9 @@ discovery: - showcase-shell-dashboard - showcase-shell-docs - showcase-shell-dojo + # Harness service for .NET integration testing — not a demo service, + # deployed: false in manifest, no aimock wiring yet. + - showcase-ms-agent-harness-dotnet # Decommissioned starters — Railway services stopped (PR #4390) - showcase-starter-ag2 - showcase-starter-agno diff --git a/showcase/harness/src/orchestrator.ts b/showcase/harness/src/orchestrator.ts index b6eeef8c6ff..f3f33fcbef8 100644 --- a/showcase/harness/src/orchestrator.ts +++ b/showcase/harness/src/orchestrator.ts @@ -1241,7 +1241,7 @@ export function buildCronProbeResolver( /** * Minimal Railway GraphQL adapter used by the aimock-wiring probe. * Lists services in a project and fetches per-service env-var values - * for a given environment. Endpoint: https://backboard.railway.com/graphql/v2. + * for a given environment. Endpoint: https://backboard.railway.app/graphql/v2. * * Routes through the shared `makeGql` helper exported from * `probes/discovery/railway-services.ts` so error taxonomy (Auth / diff --git a/showcase/harness/src/probes/discovery/railway-services.ts b/showcase/harness/src/probes/discovery/railway-services.ts index 74755da049d..cc0ad0ee62b 100644 --- a/showcase/harness/src/probes/discovery/railway-services.ts +++ b/showcase/harness/src/probes/discovery/railway-services.ts @@ -229,7 +229,7 @@ const ConfigSchema = z }) .passthrough(); -const ENDPOINT = "https://backboard.railway.com/graphql/v2"; +const ENDPOINT = "https://backboard.railway.app/graphql/v2"; // Zod shapes for the GraphQL responses. Kept in-file (not exported) so the // schema errors carry consistent paths in their messages. diff --git a/showcase/harness/src/probes/drivers/aimock-wiring.ts b/showcase/harness/src/probes/drivers/aimock-wiring.ts index bd66d25fe63..6ca270d8c94 100644 --- a/showcase/harness/src/probes/drivers/aimock-wiring.ts +++ b/showcase/harness/src/probes/drivers/aimock-wiring.ts @@ -163,7 +163,7 @@ function createRailwayAdapter( listServices: () => Promise<{ name: string; id: string }[]>; getServiceEnv: (name: string) => Promise>; } { - const endpoint = "https://backboard.railway.com/graphql/v2"; + const endpoint = "https://backboard.railway.app/graphql/v2"; async function gql( query: string, diff --git a/showcase/playwright.env-routing.config.ts b/showcase/playwright.env-routing.config.ts new file mode 100644 index 00000000000..fbf1b149608 --- /dev/null +++ b/showcase/playwright.env-routing.config.ts @@ -0,0 +1,38 @@ +import { defineConfig, devices } from "@playwright/test"; + +/** + * Playwright config for the env-routing test (B14). + * + * Scoped narrowly to `tests/env-routing.spec.ts` so it doesn't pull in + * the integrations smoke harness (`tests/playwright.config.ts` → + * `testDir: ./e2e`) or the shell-dashboard visual regression suite + * (`shell-dashboard/playwright.config.ts` → `testDir: ./tests/visual`). + * + * Runs against LIVE deployments (staging + prod). No webServer block: + * the test fetches public URLs; nothing local needs to be brought up. + * + * Usage: + * npx playwright test --config=showcase/playwright.env-routing.config.ts + * + * In CI this is gated to run after the B15 Railway env-var wiring + * deploys settle. + */ +export default defineConfig({ + testDir: "./tests", + testMatch: /env-routing\.spec\.ts$/, + timeout: 60_000, + expect: { timeout: 15_000 }, + fullyParallel: true, + forbidOnly: !!process.env.CI, + // Live-net tests can flake on transient TLS / DNS hiccups; one + // retry mirrors the integrations smoke config and avoids paging + // ops for a single jittered fetch. + retries: process.env.CI ? 1 : 0, + workers: process.env.CI ? 2 : 4, + reporter: process.env.CI ? "github" : "list", + use: { + ...devices["Desktop Chrome"], + trace: "retain-on-failure", + screenshot: "only-on-failure", + }, +}); diff --git a/showcase/scripts/__tests__/aggregate-build-results.test.ts b/showcase/scripts/__tests__/aggregate-build-results.test.ts new file mode 100644 index 00000000000..f2df37bb965 --- /dev/null +++ b/showcase/scripts/__tests__/aggregate-build-results.test.ts @@ -0,0 +1,155 @@ +/** + * aggregate-build-results.test.ts — covers the per-slot aggregator that + * runs in the `aggregate-build-results` job of showcase_build.yml. + * + * The script's `run({inputDir, outputDir, githubOutput})` entrypoint is + * exercised directly with temp dirs (so we never touch tracked files or + * spawn subprocesses). We verify: + * 1. empty INPUT_DIR → results.json = `[]`, any_success=false, no throw + * 2. build-result- dir missing result.json → throws naming the slot + * 3. non-`build-result-*` dirs are ignored + * 4. mixed success/failure → correct merged array + any_success=true + * 5. GITHUB_OUTPUT receives heredoc-form `results` block + any_success + */ +import { mkdirSync, mkdtempSync, readFileSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterEach, beforeEach, describe, expect, it } from "vitest"; +import { run } from "../aggregate-build-results"; + +function makeSlot( + inputDir: string, + service: string, + status: "success" | "failure" | "skipped", +): void { + const dir = join(inputDir, `build-result-${service}`); + mkdirSync(dir, { recursive: true }); + writeFileSync(join(dir, "result.json"), JSON.stringify({ service, status })); +} + +describe("aggregate-build-results.run", () => { + let inputDir: string; + let outputDir: string; + let githubOutput: string; + + beforeEach(() => { + const base = mkdtempSync(join(tmpdir(), "agg-build-")); + inputDir = join(base, "in"); + outputDir = join(base, "out"); + githubOutput = join(base, "gh_output"); + mkdirSync(inputDir, { recursive: true }); + // OUTPUT_DIR intentionally NOT pre-created — run() must mkdir -p. + writeFileSync(githubOutput, ""); + }); + + afterEach(() => { + // Temp dirs are under os.tmpdir(); OS reaps them. No tracked files + // touched, so no cleanup needed. + }); + + it("empty INPUT_DIR → throws (broken artifact download — the aggregator only runs when >=1 service was scheduled)", () => { + // The aggregator is gated on `has_changes == 'true'` upstream, so the + // matrix is guaranteed non-empty by the time we run. A zero-slot input + // dir therefore means the per-slot artifact download produced nothing + // (broken download, expired artifacts, mis-scoped run-id). Silently + // emitting `any_success=false` with `results=[]` is indistinguishable + // from "all builds failed" — that's a false-green path because the + // deploy workflow then has no success set to intersect against and + // falls back to probing the full service set against stale :latest. + // We refuse the ambiguity and fail loud instead. + expect(() => run({ inputDir, outputDir, githubOutput })).toThrow( + /aggregate-build-results: found 0 build-result-\* slot dirs/, + ); + }); + + it("throws naming the slot when build-result-/result.json is missing", () => { + const slotDir = join(inputDir, "build-result-orphan"); + mkdirSync(slotDir, { recursive: true }); + // Note: NO result.json written. + + expect(() => run({ inputDir, outputDir, githubOutput })).toThrow( + /aggregate-build-results: build-result-orphan is missing result\.json/, + ); + }); + + it("ignores directories that do not match build-result-*", () => { + mkdirSync(join(inputDir, "some-other-artifact"), { recursive: true }); + writeFileSync( + join(inputDir, "some-other-artifact", "result.json"), + JSON.stringify({ service: "noise", status: "success" }), + ); + // A file (not a directory) at top level should also be ignored. + writeFileSync(join(inputDir, "build-result-not-a-dir"), "garbage"); + + makeSlot(inputDir, "real", "success"); + + run({ inputDir, outputDir, githubOutput }); + + const results = JSON.parse( + readFileSync(join(outputDir, "results.json"), "utf-8"), + ); + expect(results).toEqual([{ service: "real", status: "success" }]); + }); + + it("merges mixed success/failure correctly and sets any_success=true", () => { + makeSlot(inputDir, "alpha", "success"); + makeSlot(inputDir, "beta", "failure"); + makeSlot(inputDir, "gamma", "skipped"); + + run({ inputDir, outputDir, githubOutput }); + + const results = JSON.parse( + readFileSync(join(outputDir, "results.json"), "utf-8"), + ); + expect(results).toHaveLength(3); + const byName = new Map( + (results as Array<{ service: string; status: string }>).map((r) => [ + r.service, + r.status, + ]), + ); + expect(byName.get("alpha")).toBe("success"); + expect(byName.get("beta")).toBe("failure"); + expect(byName.get("gamma")).toBe("skipped"); + + const gh = readFileSync(githubOutput, "utf-8"); + expect(gh).toContain("any_success=true"); + }); + + it("writes results to GITHUB_OUTPUT in multi-line heredoc form", () => { + makeSlot(inputDir, "alpha", "success"); + makeSlot(inputDir, "beta", "failure"); + + run({ inputDir, outputDir, githubOutput }); + + const gh = readFileSync(githubOutput, "utf-8"); + + // The heredoc form is: + // results< + // EOF + // The delimiter token is implementation-defined but must match on + // both sides (GitHub Actions convention; commonly "EOF" or a unique + // token to avoid collision with embedded payloads). + const heredocRe = /results<<(\S+)\n([\s\S]*?)\n\1\n/; + const match = gh.match(heredocRe); + expect(match).not.toBeNull(); + if (!match) return; + const [, , jsonBody] = match; + const parsed = JSON.parse(jsonBody); + expect(Array.isArray(parsed)).toBe(true); + expect(parsed).toHaveLength(2); + + // any_success line is still a plain key=value. + expect(gh).toMatch(/any_success=true\n/); + }); + + it("results.json has a trailing newline", () => { + makeSlot(inputDir, "alpha", "success"); + + run({ inputDir, outputDir, githubOutput }); + + const raw = readFileSync(join(outputDir, "results.json"), "utf-8"); + expect(raw.endsWith("\n")).toBe(true); + }); +}); diff --git a/showcase/scripts/__tests__/emit-railway-envs-json.test.ts b/showcase/scripts/__tests__/emit-railway-envs-json.test.ts new file mode 100644 index 00000000000..a6efabfbed9 --- /dev/null +++ b/showcase/scripts/__tests__/emit-railway-envs-json.test.ts @@ -0,0 +1,74 @@ +import { execFileSync, spawnSync } from "node:child_process"; +import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { resolve, join } from "node:path"; +import { afterEach, beforeEach, describe, expect, it } from "vitest"; + +const SCRIPT = resolve(__dirname, "..", "emit-railway-envs-json.ts"); + +describe("emit-railway-envs-json", () => { + let workDir: string; + let outPath: string; + + beforeEach(() => { + workDir = mkdtempSync(join(tmpdir(), "emit-railway-envs-")); + outPath = join(workDir, "railway-envs.generated.json"); + }); + + afterEach(() => { + rmSync(workDir, { recursive: true, force: true }); + }); + + it("emits canonical JSON containing every SSOT service", () => { + execFileSync("npx", ["tsx", SCRIPT, `--out=${outPath}`], { stdio: "pipe" }); + const json = JSON.parse(readFileSync(outPath, "utf8")); + expect(json.projectId).toBe("6f8c6bff-a80d-4f8f-b78d-50b32bcf4479"); + expect(json.envIds.staging).toBe("8edfef02-ea09-4a20-8689-261f21cc2849"); + expect(json.envIds.prod).toBe("b14919f4-6417-429f-848d-c6ae2201e04f"); + expect(json.services.length).toBe(27); + const docs = json.services.find((s: { name: string }) => s.name === "docs"); + expect(docs.domains.staging).toBe("docs.staging.copilotkit.ai"); + expect(docs.domains.prod).toBe("docs.copilotkit.ai"); + expect(docs.probe.driver).toBe("docs"); + }); + + it("--check passes when on-disk JSON matches SSOT", () => { + execFileSync("npx", ["tsx", SCRIPT, `--out=${outPath}`], { stdio: "pipe" }); + const out = execFileSync( + "npx", + ["tsx", SCRIPT, "--check", `--out=${outPath}`], + { stdio: "pipe" }, + ).toString(); + expect(out).toMatch(/up to date/); + }); + + it("--check FAILS with a staleness diagnostic when on-disk JSON is stale", () => { + execFileSync("npx", ["tsx", SCRIPT, `--out=${outPath}`], { stdio: "pipe" }); + const original = readFileSync(outPath, "utf8"); + writeFileSync(outPath, original.replace(/docs.copilotkit.ai/g, "x")); + const result = spawnSync( + "npx", + ["tsx", SCRIPT, "--check", `--out=${outPath}`], + { encoding: "utf8" }, + ); + expect(result.status).toBe(1); + expect(result.stderr).toMatch(/stale/); + }); + + it("--check FAILS LOUD on non-ENOENT read errors (e.g. EISDIR)", () => { + // Point --out at a directory so readFileSync raises EISDIR (not ENOENT). + // The script must NOT silently treat this as drift; it must exit non-zero + // with a real error written to stderr, distinct from the staleness exit + // (we use exit code 2 vs 1 to make the distinction observable). + const subdir = mkdtempSync(join(workDir, "isdir-")); + const result = spawnSync( + "npx", + ["tsx", SCRIPT, "--check", `--out=${subdir}`], + { encoding: "utf8" }, + ); + expect(result.status).toBe(2); + expect(result.stderr).toMatch(/EISDIR|illegal operation on a directory/i); + // Must NOT be the staleness message + expect(result.stderr).not.toMatch(/is stale/); + }); +}); diff --git a/showcase/scripts/__tests__/lint-rule-no-public-env.test.ts b/showcase/scripts/__tests__/lint-rule-no-public-env.test.ts new file mode 100644 index 00000000000..7a0ebb4e026 --- /dev/null +++ b/showcase/scripts/__tests__/lint-rule-no-public-env.test.ts @@ -0,0 +1,316 @@ +import { afterEach, describe, expect, it } from "vitest"; +import { execSync } from "node:child_process"; +import { mkdirSync, rmSync, writeFileSync } from "node:fs"; +import { dirname, join, resolve } from "node:path"; +import { pathToFileURL } from "node:url"; + +// Repo root is two directories above this test file +// (showcase/scripts/__tests__/X -> showcase/scripts -> showcase -> ). +const REPO_ROOT = resolve(import.meta.dirname, "..", "..", ".."); +const CONFIG_PATH = join(REPO_ROOT, ".oxlintrc.json"); +const RULE_MODULE_PATH = join( + REPO_ROOT, + "packages", + "react-ui", + "oxlint-rules", + "no-public-env-shell-read.mjs", +); + +// Import BANNED_KEYS from the rule module itself (rather than hand-mirroring +// the list) so the table-driven coverage cannot drift from the rule's true +// banned set. If someone adds a new banned key to the rule and forgets the +// test, the test set still expands automatically; if someone removes a key +// from the rule, the test set contracts. The assertion is "the rule is +// exhaustively table-driven against its own banned set." +const ruleModule = (await import(pathToFileURL(RULE_MODULE_PATH).href)) as { + BANNED_KEYS: Set; +}; +if ( + !ruleModule.BANNED_KEYS || + !(ruleModule.BANNED_KEYS instanceof Set) || + ruleModule.BANNED_KEYS.size === 0 +) { + throw new Error( + `Rule module did not export a non-empty BANNED_KEYS Set: ${RULE_MODULE_PATH}`, + ); +} +const BANNED_KEYS = [...ruleModule.BANNED_KEYS] as readonly string[]; + +const ALLOWED_KEYS = [ + "NEXT_PUBLIC_COMMIT_SHA", + "NEXT_PUBLIC_BRANCH", + "NEXT_PUBLIC_LOCAL_BACKENDS", +] as const; + +// Track every fixture we stage so afterEach reaps them — even on a thrown +// assertion mid-test. Without a centralized tracker, a thrown expect() +// inside a try/finally branch can still leave a file behind if the cleanup +// path itself diverges across tests. +const stagedPaths: string[] = []; + +function stageFile(absPath: string, source: string): void { + mkdirSync(dirname(absPath), { recursive: true }); + writeFileSync(absPath, source, "utf8"); + stagedPaths.push(absPath); +} + +afterEach(() => { + while (stagedPaths.length > 0) { + const p = stagedPaths.pop(); + if (p) rmSync(p, { force: true }); + } +}); + +interface LintOutcome { + exitCode: number; + combined: string; +} + +function runOxlint(target: string): LintOutcome { + try { + const stdout = execSync(`npx oxlint -c ${CONFIG_PATH} ${target} --quiet`, { + encoding: "utf8", + stdio: ["ignore", "pipe", "pipe"], + cwd: REPO_ROOT, + }).toString(); + return { exitCode: 0, combined: stdout }; + } catch (e) { + const err = e as { + status?: number; + stderr?: Buffer | string; + stdout?: Buffer | string; + }; + const stderr = err.stderr?.toString() ?? ""; + const stdout = err.stdout?.toString() ?? ""; + return { exitCode: err.status ?? 1, combined: stderr + stdout }; + } +} + +function shellFixturePath(name: string): string { + return join( + REPO_ROOT, + "showcase", + "shell-dashboard", + "src", + "lib", + `${name}.lintfixture.tsx`, + ); +} + +describe("oxlint copilotkit/no-public-env-shell-read NEXT_PUBLIC_* guard", () => { + describe("banned-key coverage (all variants flagged)", () => { + for (const key of BANNED_KEYS) { + it(`flags process.env.${key} (dotted member)`, () => { + const target = shellFixturePath(`bad-dotted-${key}`); + stageFile(target, `export const x = process.env.${key};\n`); + const r = runOxlint(target); + expect(r.exitCode).not.toBe(0); + expect(r.combined).toMatch(/no-public-env-shell-read/); + expect(r.combined).toMatch(/getRuntimeConfig/); + }); + + it(`flags process.env["${key}"] (bracket-string member)`, () => { + const target = shellFixturePath(`bad-bracket-${key}`); + stageFile(target, `export const x = process.env["${key}"];\n`); + const r = runOxlint(target); + expect(r.exitCode).not.toBe(0); + expect(r.combined).toMatch(/no-public-env-shell-read/); + }); + } + }); + + describe("allowed-key non-firing (build-stamps + computed local-dev value)", () => { + for (const key of ALLOWED_KEYS) { + it(`does NOT flag process.env.${key}`, () => { + const target = shellFixturePath(`ok-${key}`); + stageFile(target, `export const x = process.env.${key};\n`); + const r = runOxlint(target); + expect(r.exitCode).toBe(0); + }); + } + }); + + describe("variant coverage (destructuring / optional chaining / template-key)", () => { + it("flags const { NEXT_PUBLIC_SHELL_URL } = process.env (destructuring)", () => { + const target = shellFixturePath("bad-destructure"); + stageFile( + target, + `const { NEXT_PUBLIC_SHELL_URL } = process.env;\nexport const x = NEXT_PUBLIC_SHELL_URL;\n`, + ); + const r = runOxlint(target); + expect(r.exitCode).not.toBe(0); + expect(r.combined).toMatch(/no-public-env-shell-read/); + }); + + it("flags const { NEXT_PUBLIC_SHELL_URL: aliased } = process.env (destructuring with alias)", () => { + const target = shellFixturePath("bad-destructure-aliased"); + stageFile( + target, + `const { NEXT_PUBLIC_SHELL_URL: aliased } = process.env;\nexport const x = aliased;\n`, + ); + const r = runOxlint(target); + expect(r.exitCode).not.toBe(0); + expect(r.combined).toMatch(/no-public-env-shell-read/); + }); + + it("flags process.env?.NEXT_PUBLIC_SHELL_URL (optional chaining)", () => { + const target = shellFixturePath("bad-optchain"); + stageFile( + target, + `export const x = process.env?.NEXT_PUBLIC_SHELL_URL;\n`, + ); + const r = runOxlint(target); + expect(r.exitCode).not.toBe(0); + expect(r.combined).toMatch(/no-public-env-shell-read/); + }); + + it("flags process.env[`NEXT_PUBLIC_SHELL_URL`] (no-expression template literal key)", () => { + const target = shellFixturePath("bad-tplkey"); + stageFile( + target, + "export const x = process.env[`NEXT_PUBLIC_SHELL_URL`];\n", + ); + const r = runOxlint(target); + expect(r.exitCode).not.toBe(0); + expect(r.combined).toMatch(/no-public-env-shell-read/); + }); + + it('flags const { ["NEXT_PUBLIC_SHELL_URL"]: x } = process.env (destructuring with computed string key)', () => { + // Parity with the bracket-member form: the destructuring branch + // must apply the same staticKeyName() recognition as the member + // branch, otherwise a computed string key sneaks through. + const target = shellFixturePath("bad-destructure-computed-string"); + stageFile( + target, + `const { ["NEXT_PUBLIC_SHELL_URL"]: x } = process.env;\nexport const y = x;\n`, + ); + const r = runOxlint(target); + expect(r.exitCode).not.toBe(0); + expect(r.combined).toMatch(/no-public-env-shell-read/); + }); + + it("flags const { [`NEXT_PUBLIC_SHELL_URL`]: x } = process.env (destructuring with no-expression template key)", () => { + const target = shellFixturePath("bad-destructure-computed-template"); + stageFile( + target, + "const { [`NEXT_PUBLIC_SHELL_URL`]: x } = process.env;\nexport const y = x;\n", + ); + const r = runOxlint(target); + expect(r.exitCode).not.toBe(0); + expect(r.combined).toMatch(/no-public-env-shell-read/); + }); + + it("does NOT flag process.env.NEXT_PUBLIC_SHELL_URL = ... (assignment LHS)", () => { + // Writes (test/runtime overrides) are intentionally not flagged. + const target = shellFixturePath("ok-assign"); + stageFile( + target, + `process.env.NEXT_PUBLIC_SHELL_URL = "x";\nexport {};\n`, + ); + const r = runOxlint(target); + expect(r.exitCode).toBe(0); + }); + + it("does NOT flag delete process.env.NEXT_PUBLIC_SHELL_URL", () => { + const target = shellFixturePath("ok-delete"); + stageFile( + target, + `delete process.env.NEXT_PUBLIC_SHELL_URL;\nexport {};\n`, + ); + const r = runOxlint(target); + expect(r.exitCode).toBe(0); + }); + }); + + describe("override scoping (shell-only enforcement; runtime-config + non-shell exempt)", () => { + it("does NOT flag inside a runtime-config implementation file (off-override)", () => { + // The off-override matches `showcase/**/lib/runtime-config*.{ts,tsx}` + // (and the `.client` variant). Confirm the rule is silenced there. + const target = join( + REPO_ROOT, + "showcase", + "shell-dashboard", + "src", + "lib", + "runtime-config.lintfixture.tsx", + ); + stageFile( + target, + `export const x = process.env.NEXT_PUBLIC_SHELL_URL;\n`, + ); + const r = runOxlint(target); + expect(r.exitCode).toBe(0); + }); + + it("does NOT flag inside packages/ (outside the shell trees)", () => { + const target = join( + REPO_ROOT, + "packages", + "react-ui", + "src", + "__noflag.lintfixture.tsx", + ); + stageFile( + target, + `export const x = process.env.NEXT_PUBLIC_SHELL_URL;\n`, + ); + const r = runOxlint(target); + expect(r.exitCode).toBe(0); + }); + + it("DOES flag inside shell-docs/src (shell-tree, not runtime-config)", () => { + const target = join( + REPO_ROOT, + "showcase", + "shell-docs", + "src", + "__bad.lintfixture.tsx", + ); + stageFile( + target, + `export const x = process.env.NEXT_PUBLIC_SHELL_URL;\n`, + ); + const r = runOxlint(target); + expect(r.exitCode).not.toBe(0); + expect(r.combined).toMatch(/no-public-env-shell-read/); + }); + + it("DOES flag inside shell/src (shell-tree)", () => { + // The override list in .oxlintrc.json includes showcase/shell/src/**; + // exercise that branch explicitly so a future override-list edit that + // accidentally drops `shell` is caught. + const target = join( + REPO_ROOT, + "showcase", + "shell", + "src", + "__bad.lintfixture.tsx", + ); + stageFile( + target, + `export const x = process.env.NEXT_PUBLIC_SHELL_URL;\n`, + ); + const r = runOxlint(target); + expect(r.exitCode).not.toBe(0); + expect(r.combined).toMatch(/no-public-env-shell-read/); + }); + + it("DOES flag inside shell-dojo/src (shell-tree)", () => { + // Same as above for the dojo shell tree. + const target = join( + REPO_ROOT, + "showcase", + "shell-dojo", + "src", + "__bad.lintfixture.tsx", + ); + stageFile( + target, + `export const x = process.env.NEXT_PUBLIC_SHELL_URL;\n`, + ); + const r = runOxlint(target); + expect(r.exitCode).not.toBe(0); + expect(r.combined).toMatch(/no-public-env-shell-read/); + }); + }); +}); diff --git a/showcase/scripts/__tests__/redeploy-env.summary.test.ts b/showcase/scripts/__tests__/redeploy-env.summary.test.ts new file mode 100644 index 00000000000..b782845b17f --- /dev/null +++ b/showcase/scripts/__tests__/redeploy-env.summary.test.ts @@ -0,0 +1,135 @@ +import { mkdtempSync, readFileSync, readdirSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterEach, beforeEach, describe, expect, it } from "vitest"; +import { runRedeploy } from "../redeploy-env"; +import { SERVICES } from "../railway-envs"; + +// A.4: per-service JSON summary contract. +// +// Shape (cross-workstream contract, consumed by showcase_deploy.yml's +// `enforce-redeploy-gate` job in A.7): +// Array<{ service: string; status: "ok" | "error"; error?: string }> +// +// When REDEPLOY_SUMMARY_JSON env var is set, runRedeploy MUST write the +// records array to that path atomically (stage to .tmp, rename). When +// unset, no JSON is written. +// +// PR #5093's exit-code contract MUST be preserved verbatim: +// - staging: exitCode === 0 even when all per-service redeploys fail +// - prod: exitCode === 1 on any per-service failure +// The JSON write happens BEFORE the exit-code computation; it never +// changes exit semantics on disk hiccups (write failures are warn-only). +describe("redeploy-env per-service JSON summary", () => { + let dir: string; + let summaryPath: string; + + beforeEach(() => { + dir = mkdtempSync(join(tmpdir(), "redeploy-summary-")); + summaryPath = join(dir, "summary.json"); + process.env.REDEPLOY_SUMMARY_JSON = summaryPath; + }); + + afterEach(() => { + delete process.env.REDEPLOY_SUMMARY_JSON; + rmSync(dir, { recursive: true, force: true }); + }); + + it("writes one JSON record per attempted service with status", async () => { + const ag2ServiceId = SERVICES["showcase-ag2"].serviceId; + const summary = await runRedeploy({ + env: "staging", + services: ["showcase-mastra", "showcase-ag2"], + appendSummary: () => {}, + redeploy: async (serviceId) => { + if (serviceId === ag2ServiceId) { + return { ok: false, error: "boom" }; + } + return { ok: true }; + }, + }); + expect(summary.exitCode).toBe(0); // staging contract intact + const records = JSON.parse(readFileSync(summaryPath, "utf8")) as Array<{ + service: string; + status: "ok" | "error"; + error?: string; + }>; + expect(records).toEqual( + expect.arrayContaining([ + { service: "showcase-ag2", status: "error", error: "boom" }, + { service: "showcase-mastra", status: "ok" }, + ]), + ); + expect(records).toHaveLength(2); + }); + + it("does NOT write JSON when REDEPLOY_SUMMARY_JSON is unset", async () => { + delete process.env.REDEPLOY_SUMMARY_JSON; + await runRedeploy({ + env: "staging", + services: ["showcase-mastra"], + appendSummary: () => {}, + redeploy: async () => ({ ok: true }), + }); + // The temp dir we created is empty — assert nothing was written. + // (No path was given; the function had nowhere to write.) + expect(readdirSync(dir)).toEqual([]); + }); + + it("preserves PR #5093 contract: staging exits 0 even when all fail", async () => { + const summary = await runRedeploy({ + env: "staging", + services: ["showcase-mastra", "showcase-ag2"], + appendSummary: () => {}, + redeploy: async () => ({ ok: false, error: "boom" }), + }); + expect(summary.exitCode).toBe(0); + expect(summary.failed).toBe(2); + const records = JSON.parse(readFileSync(summaryPath, "utf8")) as Array<{ + service: string; + status: "ok" | "error"; + error?: string; + }>; + expect(records.every((r) => r.status === "error")).toBe(true); + expect(records).toHaveLength(2); + }); + + it("preserves PR #5093 contract: prod exits 1 on any per-service failure", async () => { + const summary = await runRedeploy({ + env: "prod", + services: ["showcase-mastra"], + appendSummary: () => {}, + redeploy: async () => ({ ok: false, error: "boom" }), + }); + expect(summary.exitCode).toBe(1); + const records = JSON.parse(readFileSync(summaryPath, "utf8")) as Array<{ + service: string; + status: "ok" | "error"; + error?: string; + }>; + expect(records).toEqual([ + { service: "showcase-mastra", status: "error", error: "boom" }, + ]); + }); + + it("records caught throws as status:error with the thrown message", async () => { + const summary = await runRedeploy({ + env: "staging", + services: ["showcase-mastra"], + appendSummary: () => {}, + redeploy: async () => { + throw new Error("kaboom"); + }, + }); + expect(summary.exitCode).toBe(0); + expect(summary.failed).toBe(1); + const records = JSON.parse(readFileSync(summaryPath, "utf8")) as Array<{ + service: string; + status: "ok" | "error"; + error?: string; + }>; + expect(records).toEqual([ + { service: "showcase-mastra", status: "error", error: "kaboom" }, + ]); + }); +}); diff --git a/showcase/scripts/__tests__/redeploy-env.token.test.ts b/showcase/scripts/__tests__/redeploy-env.token.test.ts new file mode 100644 index 00000000000..f4600cda2b8 --- /dev/null +++ b/showcase/scripts/__tests__/redeploy-env.token.test.ts @@ -0,0 +1,32 @@ +import { describe, expect, it, vi } from "vitest"; +import { resolveRailwayTokenFromConfig } from "../lib/railway-token"; + +// Characterization test: pins the resolver contract that redeploy-env's +// getToken path will use after E-2d. The resolver itself was proven +// red-green in E-2a/E-2b; this test exists so a future change to the +// resolver that breaks the redeploy-env consumer surfaces here too. +// EXPECTED OUTCOME: PASS on first run (resolver already exists). This is +// intentional — see the step header. Do NOT relabel this as a RED step. +describe("redeploy-env token resolution contract", () => { + it("returns accessToken from a typical post-login config", () => { + const warn = vi.fn(); + expect( + resolveRailwayTokenFromConfig( + { user: { accessToken: "abc-access-aaaaaaaaaaaaa" } }, + { warn }, + ), + ).toBe("abc-access-aaaaaaaaaaaaa"); + expect(warn).not.toHaveBeenCalled(); + }); + + it("warns when only user.token (legacy) is present", () => { + const warn = vi.fn(); + expect( + resolveRailwayTokenFromConfig( + { user: { token: "legacy-xxxxxxxxxx" } }, + { warn }, + ), + ).toBe("legacy-xxxxxxxxxx"); + expect(warn).toHaveBeenCalledOnce(); + }); +}); diff --git a/showcase/scripts/__tests__/resolve-verify-matrix.cli.test.ts b/showcase/scripts/__tests__/resolve-verify-matrix.cli.test.ts new file mode 100644 index 00000000000..8bfdcf04d21 --- /dev/null +++ b/showcase/scripts/__tests__/resolve-verify-matrix.cli.test.ts @@ -0,0 +1,251 @@ +/** + * resolve-verify-matrix.cli.test.ts — pins the byte-for-byte $GITHUB_OUTPUT + * contract that showcase_deploy.yml depends on. + * + * The deploy workflow's `verify:` job has + * if: needs.resolve-matrix.outputs.has_services == 'true' + * which compares against the LITERAL strings 'true'/'false' (everything in + * $GITHUB_OUTPUT is a string). A regression that writes `has_services=1` + * or `has_services=True` would silently skip verify on every redeploy. + * Pin that contract here so the byte-for-byte format is part of the test + * surface, not just the pure-function unit tests. + * + * The pure resolver is covered by resolve-verify-matrix.test.ts. THIS + * file spawns the CLI as a child process against the real + * railway-envs.generated.json so the loader + parseSsotServices + + * writeGithubOutput contract gets end-to-end coverage. + * + * Cases (mirrors decision-table boundaries the workflow YAML depends on): + * 1. workflow_run + SUMMARY_PRESENT=true + OK_FROM_REDEPLOY="" + * → services_csv=\nhas_services=false\n [Issue A pinned end-to-end] + * 2. workflow_run + SUMMARY_PRESENT=true + OK_FROM_REDEPLOY="" + * → services_csv=\nhas_services=true\n + * 3. workflow_dispatch + no summary → full probe-eligible set, has_services=true. + * 4. workflow_dispatch + service= → non-zero exit, stderr ::error::Unknown service. + */ +import { spawnSync } from "node:child_process"; +import { mkdtempSync, readFileSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { dirname, join, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import { describe, expect, it } from "vitest"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +// The repo root is the cwd the workflow YAML uses for the script (paths +// inside the script are written as `showcase/scripts/...`, relative to +// repo root). Tests therefore must spawn from repo root. +const REPO_ROOT = resolve(__dirname, "..", "..", ".."); +const SCRIPT = "showcase/scripts/resolve-verify-matrix.ts"; +const SSOT_PATH = join( + REPO_ROOT, + "showcase/scripts/railway-envs.generated.json", +); + +interface SpawnResult { + status: number; + stderr: string; + output: string; +} + +function runCli(env: Record): SpawnResult { + const dir = mkdtempSync(join(tmpdir(), "rvm-cli-")); + const outputPath = join(dir, "gh_output"); + writeFileSync(outputPath, ""); + const fullEnv = { + ...process.env, + GITHUB_OUTPUT: outputPath, + ...env, + }; + // spawnSync (rather than execFileSync) so we capture stderr on BOTH + // zero and non-zero exit. execFileSync exposes stderr only on throw, + // which means the ::warning:: drift path (exit 0 + stderr text) is + // invisible to the test harness. spawnSync returns a single object + // regardless of status, so we inspect `status` and `stderr` directly. + const res = spawnSync("npx", ["tsx", SCRIPT], { + cwd: REPO_ROOT, + env: fullEnv, + stdio: ["ignore", "pipe", "pipe"], + encoding: "utf-8", + }); + return { + status: typeof res.status === "number" ? res.status : 1, + stderr: typeof res.stderr === "string" ? res.stderr : "", + output: readFileSync(outputPath, "utf-8"), + }; +} + +// Pull two real probe-eligible names off the actual SSOT so the test +// stays in lock-step with the emitter (rather than hard-coding fixtures +// that could drift). +function realProbeEligibleNames(): string[] { + const raw = JSON.parse(readFileSync(SSOT_PATH, "utf-8")) as { + services: { name: string; probe: { staging: boolean } }[]; + }; + return raw.services + .filter((s) => s.probe.staging === true) + .map((s) => s.name) + .sort(); +} + +describe("resolve-verify-matrix CLI (end-to-end against real SSOT)", () => { + // The CLI spawns `npx tsx`. Cold-start can take >1s; bump timeout. + it("workflow_run + summary_present + empty ok_services → has_services=false (Issue A end-to-end)", () => { + const r = runCli({ + EVENT_NAME: "workflow_run", + SUMMARY_PRESENT: "true", + OK_FROM_REDEPLOY: "", + DISPATCH_SERVICE: "", + }); + expect(r.status).toBe(0); + // Byte-for-byte: the workflow YAML compares against the literal + // strings 'true' / 'false'. Match the EXACT bytes (including the + // trailing newlines and key=value form). + expect(r.output).toBe("services_csv=\nhas_services=false\n"); + }, 30_000); + + it("workflow_run + summary_present + two real ok_services → sorted CSV + has_services=true", () => { + // Hard-code two real, stable probe-eligible SSOT names rather than + // picking probe[0]/probe[1] off the live list. The earlier + // probe-index version was tautological: it pulled sorted names from + // the SSOT, reversed them, fed them back, and asserted the resolver + // re-sorted to the same order — which would silently pass even on a + // resolver that did nothing (because probe[0] < probe[1] is the + // ALREADY-sorted SSOT order). Also: if the SSOT ever shrank to <2 + // probe-eligible entries, the test would silently assert + // `services_csv=undefined,undefined`. + // + // `aimock` and `harness` are foundational infra services (not + // integration slots), so they will not churn out of the SSOT. We + // assert they're both still probe-eligible at runtime; if either + // ever leaves, this test fails LOUD with a specific message rather + // than silently degrading. + const probe = new Set(realProbeEligibleNames()); + expect(probe.has("aimock")).toBe(true); + expect(probe.has("harness")).toBe(true); + // Feed unsorted so the sort-on-intersection assertion is real: + // "harness,aimock" must come out as "aimock,harness". + const r = runCli({ + EVENT_NAME: "workflow_run", + SUMMARY_PRESENT: "true", + OK_FROM_REDEPLOY: "harness,aimock", + DISPATCH_SERVICE: "", + }); + expect(r.status).toBe(0); + expect(r.output).toBe("services_csv=aimock,harness\nhas_services=true\n"); + }, 30_000); + + // ----------------------------------------------------------------------- + // FIX 3 — surface SSOT/build drift via ::warning::ok_services tokens + // dropped (no SSOT match). The intersection logic already silently drops + // unmatched tokens from the verify matrix; the WARNING is the entire + // drift-detection contract operators rely on to notice that a redeploy + // reported success for a service the SSOT has forgotten about (or vice + // versa). Without coverage here it could silently regress to "no warning + // emitted" and the gate would keep working while losing its early-warning + // signal. + // ----------------------------------------------------------------------- + it("workflow_run + ok_services with bogus token → ::warning:: lists dropped tokens, real ones still verify", () => { + const r = runCli({ + EVENT_NAME: "workflow_run", + SUMMARY_PRESENT: "true", + // `svc-bogus` is not in the SSOT under any spelling; `aimock` is + // a real probe-eligible service. The verify CSV must drop the + // bogus token AND the wrapper must `::warning::` so the dropped + // token surfaces in the workflow log as an annotation. + OK_FROM_REDEPLOY: "svc-bogus,aimock", + DISPATCH_SERVICE: "", + }); + expect(r.status).toBe(0); + expect(r.output).toContain("services_csv=aimock\n"); + expect(r.output).toContain("has_services=true\n"); + expect(r.stderr).toMatch( + /::warning::ok_services tokens dropped \(no SSOT match\): svc-bogus/, + ); + }, 30_000); + + // ----------------------------------------------------------------------- + // FIX 5 — EVENT_NAME must be exactly 'workflow_run' or 'workflow_dispatch'. + // The CLI used to do an unchecked `as` cast on EVENT_NAME, so a typo or + // accidental new trigger ('push', 'schedule') would compile-pass at the + // type layer and only fail deep inside the resolver. Make the boundary + // total: a narrowing helper rejects unknown EVENT_NAME values up front, + // matching the resolver's own runtime guard with a SINGLE consistent + // story (type system + runtime agree). + // ----------------------------------------------------------------------- + it("EVENT_NAME=push → non-zero exit with ::error::resolve-verify-matrix: unexpected EVENT_NAME", () => { + const r = runCli({ + EVENT_NAME: "push", + SUMMARY_PRESENT: "", + OK_FROM_REDEPLOY: "", + DISPATCH_SERVICE: "", + }); + expect(r.status).not.toBe(0); + expect(r.stderr).toMatch( + /::error::resolve-verify-matrix: unexpected EVENT_NAME 'push'/, + ); + }, 30_000); + + // ----------------------------------------------------------------------- + // FIX 7 — workflow_run requires SUMMARY_PRESENT to be exactly "true" or + // "false". The check-redeploy-summary step always sets one of those two + // values, so any other input (including "" from a step-id wiring break, + // or "True" from a case-typo) means the wiring is broken and the gate + // would silently fall through to the intersection branch. Fail loud + // rather than silently emitting has_services=false on a real redeploy. + // workflow_dispatch ignores SUMMARY_PRESENT and must NOT trigger this. + // ----------------------------------------------------------------------- + it("EVENT_NAME=workflow_run + SUMMARY_PRESENT='' → non-zero exit with workflow_run requires summary_present", () => { + const r = runCli({ + EVENT_NAME: "workflow_run", + SUMMARY_PRESENT: "", + OK_FROM_REDEPLOY: "", + DISPATCH_SERVICE: "", + }); + expect(r.status).not.toBe(0); + expect(r.stderr).toMatch( + /::error::resolve-verify-matrix: workflow_run requires summary_present in \{true,false\}, got ''/, + ); + }, 30_000); + + it("EVENT_NAME=workflow_run + SUMMARY_PRESENT='True' (case typo) → non-zero exit", () => { + const r = runCli({ + EVENT_NAME: "workflow_run", + SUMMARY_PRESENT: "True", + OK_FROM_REDEPLOY: "", + DISPATCH_SERVICE: "", + }); + expect(r.status).not.toBe(0); + expect(r.stderr).toMatch( + /::error::resolve-verify-matrix: workflow_run requires summary_present in \{true,false\}, got 'True'/, + ); + }, 30_000); + + it("workflow_dispatch + no summary → full probe-eligible set + has_services=true", () => { + const probe = realProbeEligibleNames(); + const r = runCli({ + EVENT_NAME: "workflow_dispatch", + SUMMARY_PRESENT: "", + OK_FROM_REDEPLOY: "", + DISPATCH_SERVICE: "", + }); + expect(r.status).toBe(0); + expect(r.output).toBe( + `services_csv=${probe.join(",")}\nhas_services=true\n`, + ); + }, 30_000); + + it("workflow_dispatch + unknown service → non-zero exit with ::error::Unknown service", () => { + const r = runCli({ + EVENT_NAME: "workflow_dispatch", + SUMMARY_PRESENT: "", + OK_FROM_REDEPLOY: "", + DISPATCH_SERVICE: "totally-not-a-real-service", + }); + expect(r.status).not.toBe(0); + expect(r.stderr).toMatch( + /::error::Unknown service 'totally-not-a-real-service'/, + ); + }, 30_000); +}); diff --git a/showcase/scripts/__tests__/resolve-verify-matrix.test.ts b/showcase/scripts/__tests__/resolve-verify-matrix.test.ts new file mode 100644 index 00000000000..a79e1fa1d01 --- /dev/null +++ b/showcase/scripts/__tests__/resolve-verify-matrix.test.ts @@ -0,0 +1,471 @@ +/** + * resolve-verify-matrix.test.ts — covers the pure resolver that decides + * which services the post-redeploy verify probe should target. + * + * The resolver replaces the inline bash+jq block in + * .github/workflows/showcase_deploy.yml's `resolve-matrix` job ("Build + * verify matrix from SSOT" step). The bash had produced two confirmed + * bugs across prior CR rounds, so the logic was extracted to a pure + * function with parity tests against the OLD behavior PLUS a new test + * for the Issue A fix: + * + * workflow_run + summary_present=true + ok_services empty (all errored) + * → has_services=false (skip verify). The previous bash fell through + * to the full probe-eligible fleet, gratuitously probing all services + * against stale `:latest` on a redeploy where everything errored. The + * workflow still reds via `enforce-redeploy-gate` (independent of this + * matrix), so skipping verify here is correct. + * + * Cases (mirrors the decision table in the resolver JSDoc): + * 1. workflow_dispatch + 'all' → full probe-eligible set, has_services=true. + * 2. workflow_dispatch + specific in-SSOT service → just that service. + * 3. workflow_dispatch + unknown service → throws (preserves bash). + * 4. workflow_run + summary_present=false → has_services=false. + * 5. workflow_run + summary_present=true + ok empty → has_services=false. [FIX] + * 6. workflow_run + summary_present=true + ok=[a,c] → intersection. + * 7. ok contains a non-probe-eligible service → excluded. + */ +import { describe, expect, it } from "vitest"; +import { + okCsvToCanonicalNames, + parseSsotServices, + resolveVerifyMatrix, +} from "../resolve-verify-matrix"; +import type { SsotService } from "../resolve-verify-matrix"; + +// Fixture SSOT services: a mix of probe.staging=true and false, with +// both `name` and `dispatchName` populated so the intersection logic +// can be exercised against either spelling. +const fixtureServices: SsotService[] = [ + { + name: "svc-a", + dispatchName: "dispatch-a", + probe: { staging: true }, + }, + { + name: "svc-b", + dispatchName: "dispatch-b", + probe: { staging: true }, + }, + { + name: "svc-c", + dispatchName: "dispatch-c", + probe: { staging: true }, + }, + { + name: "svc-d", + dispatchName: "dispatch-d", + probe: { staging: true }, + }, + // probe.staging=false → never in the probe-eligible set. + { + name: "svc-noprobe-1", + dispatchName: "dispatch-noprobe-1", + probe: { staging: false }, + }, + { + name: "svc-noprobe-2", + dispatchName: null, + probe: { staging: false }, + }, +]; + +describe("resolveVerifyMatrix", () => { + it("workflow_dispatch + 'all' → full probe-eligible set, sorted, has_services=true", () => { + const out = resolveVerifyMatrix({ + eventName: "workflow_dispatch", + summaryPresent: "", + okFromRedeploy: "", + dispatchService: "all", + ssotServices: fixtureServices, + }); + // The bash emits `jq -r '.services[] | select(.probe.staging == true) | .name' | sort -u`. + // Probe-eligible names: svc-a, svc-b, svc-c, svc-d. Sorted+dedup'd, CSV. + expect(out.servicesCsv).toBe("svc-a,svc-b,svc-c,svc-d"); + expect(out.hasServices).toBe(true); + }); + + it("workflow_dispatch + empty dispatch input (defaults to 'all') → full probe-eligible set", () => { + const out = resolveVerifyMatrix({ + eventName: "workflow_dispatch", + summaryPresent: "", + okFromRedeploy: "", + dispatchService: "", + ssotServices: fixtureServices, + }); + expect(out.servicesCsv).toBe("svc-a,svc-b,svc-c,svc-d"); + expect(out.hasServices).toBe(true); + }); + + it("workflow_dispatch + specific in-SSOT service (by name) → just that service", () => { + const out = resolveVerifyMatrix({ + eventName: "workflow_dispatch", + summaryPresent: "", + okFromRedeploy: "", + dispatchService: "svc-b", + ssotServices: fixtureServices, + }); + expect(out.servicesCsv).toBe("svc-b"); + expect(out.hasServices).toBe(true); + }); + + it("workflow_dispatch + specific in-SSOT service (by dispatchName) → resolves to canonical name", () => { + const out = resolveVerifyMatrix({ + eventName: "workflow_dispatch", + summaryPresent: "", + okFromRedeploy: "", + dispatchService: "dispatch-c", + ssotServices: fixtureServices, + }); + expect(out.servicesCsv).toBe("svc-c"); + expect(out.hasServices).toBe(true); + }); + + it("workflow_dispatch + unknown service → throws (preserves bash error/exit behavior)", () => { + // The bash printed `::error::Unknown service '$DISPATCH_SERVICE' (not + // an SSOT key or dispatch_name)` and `exit 1`. The resolver mirrors + // this by throwing; the CLI wrapper converts the throw into a non-zero + // process exit with the same `::error::` annotation. + expect(() => + resolveVerifyMatrix({ + eventName: "workflow_dispatch", + summaryPresent: "", + okFromRedeploy: "", + dispatchService: "totally-not-a-real-service", + ssotServices: fixtureServices, + }), + ).toThrow(/Unknown service 'totally-not-a-real-service'/); + }); + + it("workflow_run + summary_present=false → has_services=false (nothing was redeployed)", () => { + const out = resolveVerifyMatrix({ + eventName: "workflow_run", + summaryPresent: "false", + okFromRedeploy: "", + dispatchService: "", + ssotServices: fixtureServices, + }); + expect(out.servicesCsv).toBe(""); + expect(out.hasServices).toBe(false); + }); + + // --------------------------------------------------------------------- + // Issue A fix — write this FIRST and watch it RED against a naive + // implementation that falls through to the full probe-eligible fleet + // when OK_FROM_REDEPLOY is empty. The old bash collapsed (summary- + // present, all-errored) with (summary-absent / dispatch-fleet) and + // probed every service against stale :latest. The workflow already + // reds via `enforce-redeploy-gate` when redeploy_red=true, so this + // matrix should yield has_services=false in the all-errored case. + // --------------------------------------------------------------------- + it("workflow_run + summary_present=true + ok empty → has_services=false (Issue A fix)", () => { + const out = resolveVerifyMatrix({ + eventName: "workflow_run", + summaryPresent: "true", + okFromRedeploy: "", + dispatchService: "", + ssotServices: fixtureServices, + }); + expect(out.servicesCsv).toBe(""); + expect(out.hasServices).toBe(false); + }); + + it("workflow_run + summary_present=true + ok=[svc-a,svc-c] → csv = sorted intersection with probe-eligible", () => { + const out = resolveVerifyMatrix({ + eventName: "workflow_run", + summaryPresent: "true", + okFromRedeploy: "svc-a,svc-c", + dispatchService: "", + ssotServices: fixtureServices, + }); + expect(out.servicesCsv).toBe("svc-a,svc-c"); + expect(out.hasServices).toBe(true); + }); + + it("workflow_run + ok uses dispatchName aliases → resolved to canonical names in CSV", () => { + // The redeploy summary CSV may carry dispatch_names (the build matrix + // identifier) rather than SSOT keys. The bash mapped via + // `select(.name == $r or .dispatchName == $r) | .name` → canonical. + const out = resolveVerifyMatrix({ + eventName: "workflow_run", + summaryPresent: "true", + okFromRedeploy: "dispatch-a,dispatch-c", + dispatchService: "", + ssotServices: fixtureServices, + }); + expect(out.servicesCsv).toBe("svc-a,svc-c"); + expect(out.hasServices).toBe(true); + }); + + it("workflow_run + ok contains a non-probe-eligible service → excluded from CSV", () => { + // svc-noprobe-1 has probe.staging=false; even if it redeployed OK + // it must not appear in the verify matrix because we have no probe + // driver for it. + const out = resolveVerifyMatrix({ + eventName: "workflow_run", + summaryPresent: "true", + okFromRedeploy: "svc-a,svc-noprobe-1,svc-d", + dispatchService: "", + ssotServices: fixtureServices, + }); + expect(out.servicesCsv).toBe("svc-a,svc-d"); + expect(out.hasServices).toBe(true); + }); + + it("workflow_run + ok intersection collapses to empty → has_services=false", () => { + // Every OK service is non-probe-eligible. Old bash would have emitted + // services_csv='' and has_services=false here too (it set has_services + // off the CSV emptiness), so this is a parity case. + const out = resolveVerifyMatrix({ + eventName: "workflow_run", + summaryPresent: "true", + okFromRedeploy: "svc-noprobe-1,svc-noprobe-2", + dispatchService: "", + ssotServices: fixtureServices, + }); + expect(out.servicesCsv).toBe(""); + expect(out.hasServices).toBe(false); + }); + + it("workflow_run + ok with duplicates → dedup'd in CSV", () => { + const out = resolveVerifyMatrix({ + eventName: "workflow_run", + summaryPresent: "true", + okFromRedeploy: "svc-a,svc-a,dispatch-a,svc-b", + dispatchService: "", + ssotServices: fixtureServices, + }); + expect(out.servicesCsv).toBe("svc-a,svc-b"); + expect(out.hasServices).toBe(true); + }); + + // --------------------------------------------------------------------- + // FIX 3 — unknown eventName must throw rather than silently falling + // through to the workflow_run intersection branch. A typo or unexpected + // trigger today produces a SILENT skip (intersection of "" with probe- + // eligible = empty → has_services=false) which is indistinguishable from + // the legitimate "summary absent, nothing to verify" path. + // --------------------------------------------------------------------- + it("unknown eventName → throws with ::error:: annotation (fail-loud)", () => { + expect(() => + resolveVerifyMatrix({ + eventName: "schedule", + summaryPresent: "", + okFromRedeploy: "", + dispatchService: "", + ssotServices: fixtureServices, + }), + ).toThrow( + /::error::resolve-verify-matrix: unexpected eventName 'schedule'/, + ); + }); + + // --------------------------------------------------------------------- + // FIX 7 — make the workflow_run boundary total. summaryPresent MUST be + // exactly "true" or "false" on workflow_run (check-redeploy-summary + // always sets one of the two). Any other value (including the empty + // string from a future step-id-rename wiring break, or "True" from a + // case-typo) used to fall through to the intersection branch and + // silently emit has_services=false — indistinguishable from a + // legitimate skip. Throw instead. workflow_dispatch ignores + // summaryPresent and must NOT throw. + // --------------------------------------------------------------------- + it("workflow_run + summaryPresent='' → throws (boundary is total)", () => { + expect(() => + resolveVerifyMatrix({ + eventName: "workflow_run", + summaryPresent: "", + okFromRedeploy: "", + dispatchService: "", + ssotServices: fixtureServices, + }), + ).toThrow( + /::error::resolve-verify-matrix: workflow_run requires summary_present in \{true,false\}, got ''/, + ); + }); + + it("workflow_run + summaryPresent='True' (case typo) → throws", () => { + expect(() => + resolveVerifyMatrix({ + eventName: "workflow_run", + summaryPresent: "True", + okFromRedeploy: "", + dispatchService: "", + ssotServices: fixtureServices, + }), + ).toThrow( + /::error::resolve-verify-matrix: workflow_run requires summary_present in \{true,false\}, got 'True'/, + ); + }); + + it("workflow_dispatch ignores summaryPresent (any value) — does NOT throw on empty", () => { + // workflow_dispatch path never reads summaryPresent; it must keep + // working even when the wrapper passes the default "". + const out = resolveVerifyMatrix({ + eventName: "workflow_dispatch", + summaryPresent: "", + okFromRedeploy: "", + dispatchService: "all", + ssotServices: fixtureServices, + }); + expect(out.servicesCsv).toBe("svc-a,svc-b,svc-c,svc-d"); + expect(out.hasServices).toBe(true); + }); +}); + +// ------------------------------------------------------------------------- +// FIX 4 — okCsvToCanonicalNames: trim tokens and report unmatched tokens. +// The redeploy-gate bash emits `join(",")` which produces no spaces today, +// but any future change to the bash (or a human-driven workflow_dispatch +// caller that hand-types a CSV) that adds spaces silently dropped tokens +// because `"a, b".split(",")` yields ["a", " b"] and " b" matches nothing. +// We trim before matching, and surface unknown tokens so the CLI wrapper +// can `::warning::` on SSOT/build drift (the function itself stays pure). +// ------------------------------------------------------------------------- +describe("okCsvToCanonicalNames", () => { + it("trims whitespace around tokens — 'svc-a, svc-c' == 'svc-a,svc-c'", () => { + const a = okCsvToCanonicalNames("svc-a, svc-c", fixtureServices); + const b = okCsvToCanonicalNames("svc-a,svc-c", fixtureServices); + expect(Array.from(a.canonical).sort()).toEqual(["svc-a", "svc-c"]); + expect(Array.from(b.canonical).sort()).toEqual(["svc-a", "svc-c"]); + expect(a.dropped).toEqual([]); + expect(b.dropped).toEqual([]); + }); + + it("reports tokens that match no SSOT service in `dropped`", () => { + const out = okCsvToCanonicalNames( + "svc-a,not-a-real-service,svc-b", + fixtureServices, + ); + expect(Array.from(out.canonical).sort()).toEqual(["svc-a", "svc-b"]); + expect(out.dropped).toEqual(["not-a-real-service"]); + }); + + it("ignores empty tokens (e.g. trailing comma) without reporting them as dropped", () => { + const out = okCsvToCanonicalNames("svc-a,,svc-b,", fixtureServices); + expect(Array.from(out.canonical).sort()).toEqual(["svc-a", "svc-b"]); + expect(out.dropped).toEqual([]); + }); +}); + +// ------------------------------------------------------------------------- +// FIX 1 — parseSsotServices: validate the SSOT shape rather than blindly +// casting JSON.parse() output. A truncated/drifted SSOT (emitter crashed +// mid-write, or schema renamed) parses but silently shrinks/empties the +// probe-eligible set → real redeploys go unverified, or verify is skipped +// on a real redeploy. We refuse the ambiguity and throw with a +// ::error::-prefixed message. +// ------------------------------------------------------------------------- +describe("parseSsotServices", () => { + it("accepts a well-formed SSOT and returns the services array", () => { + const raw = { + services: [ + { name: "svc-a", dispatchName: null, probe: { staging: true } }, + { + name: "svc-b", + dispatchName: "dispatch-b", + probe: { staging: false }, + }, + ], + }; + const out = parseSsotServices(raw, "test-path"); + expect(out).toHaveLength(2); + expect(out[0].name).toBe("svc-a"); + expect(out[1].probe.staging).toBe(false); + }); + + it("throws when `services` is not an array", () => { + expect(() => + parseSsotServices({ services: { not: "an array" } }, "test-path"), + ).toThrow(/::error::SSOT test-path malformed: `services` is not an array/); + }); + + it("throws when `services` is an empty array (emitter crashed mid-write)", () => { + expect(() => parseSsotServices({ services: [] }, "test-path")).toThrow( + /::error::SSOT test-path malformed: `services` is empty/, + ); + }); + + it("throws when a service entry has no `name`", () => { + expect(() => + parseSsotServices( + { + services: [ + { name: "svc-a", dispatchName: null, probe: { staging: true } }, + { dispatchName: null, probe: { staging: true } }, + ], + }, + "test-path", + ), + ).toThrow( + /::error::SSOT test-path malformed: services\[1\] missing `name`/, + ); + }); + + it("throws when `probe` is missing", () => { + expect(() => + parseSsotServices( + { + services: [{ name: "svc-a", dispatchName: null }], + }, + "test-path", + ), + ).toThrow( + /::error::SSOT test-path malformed: services\[0\] \(svc-a\) missing `probe`/, + ); + }); + + it("accepts a missing `dispatchName` (live SSOT has services without one, e.g. pocketbase) and normalizes to null", () => { + const out = parseSsotServices( + { + services: [ + { name: "svc-a", probe: { staging: true } }, + { name: "svc-b", dispatchName: null, probe: { staging: true } }, + { + name: "svc-c", + dispatchName: "dispatch-c", + probe: { staging: true }, + }, + ], + }, + "test-path", + ); + expect(out[0].dispatchName).toBeNull(); + expect(out[1].dispatchName).toBeNull(); + expect(out[2].dispatchName).toBe("dispatch-c"); + }); + + it("throws when `dispatchName` is set to a non-string non-null value", () => { + expect(() => + parseSsotServices( + { + services: [ + { name: "svc-a", dispatchName: 42, probe: { staging: true } }, + ], + }, + "test-path", + ), + ).toThrow( + /::error::SSOT test-path malformed: services\[0\] \(svc-a\) `dispatchName` must be string, null, or absent/, + ); + }); + + it("throws when `probe.staging` is not a boolean", () => { + expect(() => + parseSsotServices( + { + services: [ + { + name: "svc-a", + dispatchName: null, + probe: { staging: "true" }, + }, + ], + }, + "test-path", + ), + ).toThrow( + /::error::SSOT test-path malformed: services\[0\] \(svc-a\) `probe.staging` is not boolean/, + ); + }); +}); diff --git a/showcase/scripts/__tests__/verify-deploy.drivers.test.ts b/showcase/scripts/__tests__/verify-deploy.drivers.test.ts new file mode 100644 index 00000000000..b0abf5499e9 --- /dev/null +++ b/showcase/scripts/__tests__/verify-deploy.drivers.test.ts @@ -0,0 +1,718 @@ +/** + * Red-green tests for the baseline `verify-deploy` driver impls. + * + * Drivers were originally stubs that returned `{ ok: false, error: "... + * not yet implemented ..." }`. These tests pin the agreed completion + * bar: every driver performs the two baseline checks (Railway + * deployment-SUCCESS via GraphQL + healthcheck HTTP 200) before any + * driver-specific extension runs. + * + * Network seams (`fetchImpl`, `getRailwayToken`) are injected so the + * suite runs fully offline. These are NOT LLM calls — plain vi-fn stubs + * are appropriate; aimock is not used here. + */ +import { describe, expect, it, vi } from "vitest"; +import { SERVICES } from "../railway-envs"; +import type { ProbeTarget } from "../verify-deploy"; +import fs from "fs"; +import os from "os"; +import path from "path"; +import { + checkDeploymentSuccess, + checkHealthcheck200, + defaultGetRailwayToken, + envForTarget, + probeBaseline, +} from "../verify-deploy.drivers.baseline"; +import type { FetchLike } from "../verify-deploy.drivers.baseline"; +import { probeShell } from "../verify-deploy.drivers.shell"; +import { probeDocs } from "../verify-deploy.drivers.docs"; +import { probeDashboard } from "../verify-deploy.drivers.dashboard"; +import { probeDojo } from "../verify-deploy.drivers.dojo"; +import { probeHarness } from "../verify-deploy.drivers.harness"; +import { probeEval } from "../verify-deploy.drivers.eval"; +import { probeAimock } from "../verify-deploy.drivers.aimock"; +import { probePocketbase } from "../verify-deploy.drivers.pocketbase"; +import { probeWebhooks } from "../verify-deploy.drivers.webhooks"; +import { probeAgent } from "../verify-deploy.drivers.agent"; + +const TOKEN = "tok_test_abcdef"; + +function mkResponse(body: { + status?: number; + json?: unknown; + text?: string; + ok?: boolean; +}): Awaited> { + const status = body.status ?? 200; + return { + ok: body.ok ?? (status >= 200 && status < 300), + status, + async text() { + return body.text ?? JSON.stringify(body.json ?? {}); + }, + async json() { + return body.json ?? {}; + }, + }; +} + +function makeFetch( + handler: ( + url: string, + init?: Parameters[1], + ) => ReturnType, +): FetchLike { + return vi.fn(handler); +} + +function gqlDeploymentResponse(status: string): ReturnType { + return Promise.resolve( + mkResponse({ + json: { + data: { + deployments: { + edges: [{ node: { id: "d1", status } }], + }, + }, + }, + }), + ); +} + +describe("defaultGetRailwayToken non-ENOENT errors are NOT swallowed", () => { + it("surfaces a clear diagnostic on non-ENOENT read errors (e.g. EISDIR via directory at config path)", () => { + // Point HOME at a tmpdir where ~/.railway/config.json is itself a + // directory — fs.readFileSync raises EISDIR. The previous bare + // `catch {}` swallowed it; the fix must NOT silently return + // undefined without a diagnostic on stderr identifying the path. + const tmpHome = fs.mkdtempSync( + path.join(os.tmpdir(), "verify-deploy-token-"), + ); + const railwayDir = path.join(tmpHome, ".railway"); + fs.mkdirSync(railwayDir, { recursive: true }); + // Make config.json a directory (not a file). + fs.mkdirSync(path.join(railwayDir, "config.json")); + + const origHome = process.env.HOME; + const origToken = process.env.RAILWAY_TOKEN; + delete process.env.RAILWAY_TOKEN; + process.env.HOME = tmpHome; + + const stderr: string[] = []; + const realWrite = process.stderr.write.bind(process.stderr); + ( + process.stderr as unknown as { write: typeof process.stderr.write } + ).write = ((chunk: string | Uint8Array): boolean => { + stderr.push( + typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8"), + ); + return true; + }) as typeof process.stderr.write; + try { + const out = defaultGetRailwayToken(); + expect(out).toBeUndefined(); + const joined = stderr.join(""); + // The fix must log a diagnostic identifying the config path + // and the error — bare `catch {}` printed nothing. + expect(joined).toMatch(/config\.json/); + expect(joined).toMatch(/EISDIR|directory|read/i); + } finally { + ( + process.stderr as unknown as { write: typeof process.stderr.write } + ).write = realWrite; + if (origHome === undefined) delete process.env.HOME; + else process.env.HOME = origHome; + if (origToken === undefined) delete process.env.RAILWAY_TOKEN; + else process.env.RAILWAY_TOKEN = origToken; + try { + fs.rmSync(tmpHome, { recursive: true, force: true }); + } catch { + /* cleanup */ + } + } + }); + + it("treats ENOENT (missing config file) as the legitimate no-token path (silent undefined)", () => { + const tmpHome = fs.mkdtempSync( + path.join(os.tmpdir(), "verify-deploy-token-"), + ); + const origHome = process.env.HOME; + const origToken = process.env.RAILWAY_TOKEN; + delete process.env.RAILWAY_TOKEN; + process.env.HOME = tmpHome; + + const stderr: string[] = []; + const realWrite = process.stderr.write.bind(process.stderr); + ( + process.stderr as unknown as { write: typeof process.stderr.write } + ).write = ((chunk: string | Uint8Array): boolean => { + stderr.push( + typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8"), + ); + return true; + }) as typeof process.stderr.write; + try { + const out = defaultGetRailwayToken(); + expect(out).toBeUndefined(); + // ENOENT is the legitimate no-token path — no diagnostic spam. + expect(stderr.join("")).toBe(""); + } finally { + ( + process.stderr as unknown as { write: typeof process.stderr.write } + ).write = realWrite; + if (origHome === undefined) delete process.env.HOME; + else process.env.HOME = origHome; + if (origToken === undefined) delete process.env.RAILWAY_TOKEN; + else process.env.RAILWAY_TOKEN = origToken; + try { + fs.rmSync(tmpHome, { recursive: true, force: true }); + } catch { + /* cleanup */ + } + } + }); +}); + +describe("envForTarget", () => { + it("returns 'staging' when host matches the SSOT staging domain", () => { + const t: ProbeTarget = { + name: "docs", + host: SERVICES.docs.domains.staging, + driver: "docs", + }; + expect(envForTarget(t)).toBe("staging"); + }); + + it("returns 'prod' when host matches the SSOT prod domain", () => { + const t: ProbeTarget = { + name: "docs", + host: SERVICES.docs.domains.prod, + driver: "docs", + }; + expect(envForTarget(t)).toBe("prod"); + }); + + it("returns undefined for unknown host or unknown service", () => { + expect( + envForTarget({ name: "docs", host: "bogus.example", driver: "docs" }), + ).toBeUndefined(); + expect( + envForTarget({ name: "nonsuch", host: "x", driver: "docs" }), + ).toBeUndefined(); + }); +}); + +describe("checkDeploymentSuccess", () => { + it("returns undefined when Railway reports SUCCESS", async () => { + const fetchImpl = makeFetch(() => gqlDeploymentResponse("SUCCESS")); + const err = await checkDeploymentSuccess( + "svc-id", + "staging", + TOKEN, + fetchImpl, + 5000, + "shell", + ); + expect(err).toBeUndefined(); + }); + + it("returns an error string when status is not SUCCESS", async () => { + const fetchImpl = makeFetch(() => gqlDeploymentResponse("CRASHED")); + const err = await checkDeploymentSuccess( + "svc-id", + "prod", + TOKEN, + fetchImpl, + 5000, + "shell", + ); + expect(err).toMatch(/CRASHED/); + expect(err).toMatch(/prod/); + }); + + it("returns an error when GraphQL returns errors[]", async () => { + const fetchImpl = makeFetch(() => + Promise.resolve( + mkResponse({ json: { errors: [{ message: "bad token" }] } }), + ), + ); + const err = await checkDeploymentSuccess( + "svc-id", + "staging", + TOKEN, + fetchImpl, + 5000, + "shell", + ); + expect(err).toMatch(/bad token/); + }); + + it("returns an error on HTTP non-2xx", async () => { + const fetchImpl = makeFetch(() => + Promise.resolve(mkResponse({ status: 401, text: "unauthorized" })), + ); + const err = await checkDeploymentSuccess( + "svc-id", + "staging", + TOKEN, + fetchImpl, + 5000, + "shell", + ); + expect(err).toMatch(/401/); + }); + + it("returns an error when no deployment edge exists", async () => { + const fetchImpl = makeFetch(() => + Promise.resolve( + mkResponse({ json: { data: { deployments: { edges: [] } } } }), + ), + ); + const err = await checkDeploymentSuccess( + "svc-id", + "staging", + TOKEN, + fetchImpl, + 5000, + "shell", + ); + expect(err).toMatch(/no deployments/); + }); + + it("sends the Authorization bearer + serviceId/environmentId variables", async () => { + const calls: Array<{ url: string; body: unknown; auth?: string }> = []; + const fetchImpl = makeFetch((url, init) => { + calls.push({ + url, + body: JSON.parse(String(init?.body ?? "{}")), + auth: init?.headers?.Authorization, + }); + return gqlDeploymentResponse("SUCCESS"); + }); + await checkDeploymentSuccess( + "svc-123", + "staging", + TOKEN, + fetchImpl, + 5000, + "shell", + ); + expect(calls).toHaveLength(1); + expect(calls[0].url).toMatch(/backboard\.railway\.app\/graphql\/v2/); + expect(calls[0].auth).toBe(`Bearer ${TOKEN}`); + const body = calls[0].body as { + query: string; + variables: { serviceId: string; environmentId: string }; + }; + expect(body.query).toMatch(/deployments\(first: 1/); + expect(body.variables.serviceId).toBe("svc-123"); + expect(body.variables.environmentId).toMatch(/^[0-9a-f-]{36}$/); + }); +}); + +describe("checkHealthcheck200", () => { + it("returns undefined on HTTP 200", async () => { + const fetchImpl = makeFetch(() => + Promise.resolve(mkResponse({ status: 200 })), + ); + const err = await checkHealthcheck200( + "docs.example", + "/", + fetchImpl, + 5000, + "docs", + ); + expect(err).toBeUndefined(); + }); + + it("returns an error string on non-200", async () => { + const fetchImpl = makeFetch(() => + Promise.resolve(mkResponse({ status: 503 })), + ); + const err = await checkHealthcheck200( + "docs.example", + "/", + fetchImpl, + 5000, + "docs", + ); + expect(err).toMatch(/503/); + expect(err).toMatch(/docs/); + }); + + it("composes https + host + path correctly", async () => { + const calls: string[] = []; + const fetchImpl = makeFetch((url) => { + calls.push(url); + return Promise.resolve(mkResponse({ status: 200 })); + }); + await checkHealthcheck200( + "showcase-aimock-production.up.railway.app", + "/health", + fetchImpl, + 5000, + "aimock", + ); + expect(calls[0]).toBe( + "https://showcase-aimock-production.up.railway.app/health", + ); + }); + + it("returns an error on fetch throw", async () => { + const fetchImpl = makeFetch(() => + Promise.reject(new Error("ECONNREFUSED")), + ); + const err = await checkHealthcheck200( + "docs.example", + "/", + fetchImpl, + 5000, + "docs", + ); + expect(err).toMatch(/ECONNREFUSED/); + }); +}); + +describe("probeBaseline", () => { + function okFetch(): FetchLike { + return makeFetch((url) => { + if (url.includes("/graphql/v2")) return gqlDeploymentResponse("SUCCESS"); + return Promise.resolve(mkResponse({ status: 200 })); + }); + } + + it("returns ok when GraphQL = SUCCESS and healthcheck = 200", async () => { + const out = await probeBaseline( + { + name: "docs", + host: SERVICES.docs.domains.staging, + driver: "docs", + }, + { + driverLabel: "docs", + healthcheckPath: "/", + fetchImpl: okFetch(), + getRailwayToken: () => TOKEN, + }, + ); + expect(out).toEqual({ ok: true }); + }); + + it("fails loud when env cannot be resolved from host", async () => { + const out = await probeBaseline( + { name: "docs", host: "wrong.example", driver: "docs" }, + { + driverLabel: "docs", + healthcheckPath: "/", + fetchImpl: okFetch(), + getRailwayToken: () => TOKEN, + }, + ); + expect(out.ok).toBe(false); + if (out.ok === false) { + expect(out.error).toMatch(/cannot resolve env/); + } + }); + + it("fails loud when no Railway token is available", async () => { + const out = await probeBaseline( + { + name: "docs", + host: SERVICES.docs.domains.staging, + driver: "docs", + }, + { + driverLabel: "docs", + healthcheckPath: "/", + fetchImpl: okFetch(), + getRailwayToken: () => undefined, + }, + ); + expect(out.ok).toBe(false); + if (out.ok === false) { + expect(out.error).toMatch(/no Railway token/); + } + }); + + it("fails on deployment status != SUCCESS", async () => { + const fetchImpl = makeFetch((url) => { + if (url.includes("/graphql/v2")) return gqlDeploymentResponse("CRASHED"); + return Promise.resolve(mkResponse({ status: 200 })); + }); + const out = await probeBaseline( + { + name: "docs", + host: SERVICES.docs.domains.staging, + driver: "docs", + }, + { + driverLabel: "docs", + healthcheckPath: "/", + fetchImpl, + getRailwayToken: () => TOKEN, + }, + ); + expect(out.ok).toBe(false); + if (out.ok === false) expect(out.error).toMatch(/CRASHED/); + }); + + it("fails on healthcheck != 200", async () => { + const fetchImpl = makeFetch((url) => { + if (url.includes("/graphql/v2")) return gqlDeploymentResponse("SUCCESS"); + return Promise.resolve(mkResponse({ status: 502 })); + }); + const out = await probeBaseline( + { + name: "docs", + host: SERVICES.docs.domains.staging, + driver: "docs", + }, + { + driverLabel: "docs", + healthcheckPath: "/", + fetchImpl, + getRailwayToken: () => TOKEN, + }, + ); + expect(out.ok).toBe(false); + if (out.ok === false) expect(out.error).toMatch(/502/); + }); +}); + +/** + * Per-driver tests. Each driver exports a probeX(target) that takes ONLY + * the target; the test seam is via `globalThis.fetch` + `RAILWAY_TOKEN` + * env var. We stub both for the duration of each test. + * + * What we pin per driver: + * - It is no longer a "not yet implemented" stub. + * - It calls Railway GraphQL with the SSOT serviceId for the matched + * env, expecting `SUCCESS`. + * - It hits the healthcheck URL appropriate to its service shape. + * - It returns `{ok:true}` on a green baseline, `{ok:false}` on red. + */ +function withGlobalSeam( + fetchImpl: FetchLike, + token: string | undefined, + fn: () => Promise, +): Promise { + const realFetch = globalThis.fetch; + const realToken = process.env.RAILWAY_TOKEN; + (globalThis as unknown as { fetch: FetchLike }).fetch = fetchImpl; + if (token === undefined) delete process.env.RAILWAY_TOKEN; + else process.env.RAILWAY_TOKEN = token; + return fn().finally(() => { + (globalThis as unknown as { fetch: typeof fetch }).fetch = realFetch; + if (realToken === undefined) delete process.env.RAILWAY_TOKEN; + else process.env.RAILWAY_TOKEN = realToken; + }); +} + +type DriverFn = ( + target: ProbeTarget, +) => Promise<{ ok: true } | { ok: false; error: string }>; + +interface DriverCase { + label: string; + driver: DriverFn; + service: keyof typeof SERVICES; + enumLiteral: string; + expectedHealthPath: string; +} + +const DRIVER_CASES: DriverCase[] = [ + { + label: "shell", + driver: probeShell, + service: "shell", + enumLiteral: "shell", + expectedHealthPath: "/", + }, + { + label: "docs", + driver: probeDocs, + service: "docs", + enumLiteral: "docs", + expectedHealthPath: "/", + }, + { + label: "dashboard", + driver: probeDashboard, + service: "dashboard", + enumLiteral: "dashboard", + expectedHealthPath: "/", + }, + { + label: "dojo", + driver: probeDojo, + service: "dojo", + enumLiteral: "dojo", + expectedHealthPath: "/", + }, + { + label: "harness", + driver: probeHarness, + service: "harness", + enumLiteral: "harness", + expectedHealthPath: "/health", + }, + { + label: "aimock", + driver: probeAimock, + service: "aimock", + enumLiteral: "aimock", + expectedHealthPath: "/health", + }, + { + label: "pocketbase", + driver: probePocketbase, + service: "pocketbase", + enumLiteral: "pocketbase", + expectedHealthPath: "/api/health", + }, + { + label: "webhooks", + driver: probeWebhooks, + service: "webhooks", + enumLiteral: "webhooks", + expectedHealthPath: "/api/health", + }, + { + // No service in the SSOT currently uses the `eval` driver literal, + // but the enum literal exists in railway-envs.ts and the dispatch + // switch wires probeEval. We still need probeEval to be a working + // impl. Test it by routing through any real SSOT service — + // probeEval's behavior is structurally identical to the agent + // driver; we use `harness` as the host carrier since it's the + // smallest standalone API surface in the SSOT. + label: "eval", + driver: probeEval, + service: "harness", + enumLiteral: "eval", + expectedHealthPath: "/api/health", + }, + { + label: "agent", + driver: probeAgent, + service: "showcase-mastra", + enumLiteral: "agent", + expectedHealthPath: "/api/health", + }, +]; + +describe.each(DRIVER_CASES)( + "$label driver", + ({ label, driver, service, expectedHealthPath }) => { + it("is no longer a 'not yet implemented' stub", async () => { + // GREEN baseline against the live seam. + const entry = SERVICES[service]; + if (!entry) { + throw new Error( + `test setup: SERVICES["${service}"] is missing — update DRIVER_CASES`, + ); + } + const fetchImpl = makeFetch((url) => { + if (url.includes("/graphql/v2")) + return gqlDeploymentResponse("SUCCESS"); + return Promise.resolve(mkResponse({ status: 200 })); + }); + await withGlobalSeam(fetchImpl, TOKEN, async () => { + const out = await driver({ + name: service, + host: entry.domains.staging, + driver: label as ProbeTarget["driver"], + }); + expect(out.ok).toBe(true); + if (out.ok === false) { + // Specifically reject the legacy stub message — that + // was the bug we're fixing. + expect(out.error).not.toMatch(/not yet implemented/); + } + }); + }); + + it("hits the expected healthcheck path", async () => { + const entry = SERVICES[service]; + if (!entry) throw new Error("test setup"); + const seen: string[] = []; + const fetchImpl = makeFetch((url) => { + seen.push(url); + if (url.includes("/graphql/v2")) + return gqlDeploymentResponse("SUCCESS"); + return Promise.resolve(mkResponse({ status: 200 })); + }); + await withGlobalSeam(fetchImpl, TOKEN, async () => { + await driver({ + name: service, + host: entry.domains.prod, + driver: label as ProbeTarget["driver"], + }); + }); + const healthUrls = seen.filter((u) => !u.includes("/graphql/v2")); + expect(healthUrls).toHaveLength(1); + expect(healthUrls[0]).toBe( + `https://${entry.domains.prod}${expectedHealthPath}`, + ); + }); + + it("queries Railway with the SSOT serviceId for the resolved env", async () => { + const entry = SERVICES[service]; + if (!entry) throw new Error("test setup"); + let gqlBody: { variables?: { serviceId?: string } } | undefined; + const fetchImpl = makeFetch((url, init) => { + if (url.includes("/graphql/v2")) { + gqlBody = JSON.parse(String(init?.body ?? "{}")); + return gqlDeploymentResponse("SUCCESS"); + } + return Promise.resolve(mkResponse({ status: 200 })); + }); + await withGlobalSeam(fetchImpl, TOKEN, async () => { + await driver({ + name: service, + host: entry.domains.staging, + driver: label as ProbeTarget["driver"], + }); + }); + expect(gqlBody?.variables?.serviceId).toBe(entry.serviceId); + }); + + it("fails on CRASHED deployment status", async () => { + const entry = SERVICES[service]; + if (!entry) throw new Error("test setup"); + const fetchImpl = makeFetch((url) => { + if (url.includes("/graphql/v2")) + return gqlDeploymentResponse("CRASHED"); + return Promise.resolve(mkResponse({ status: 200 })); + }); + await withGlobalSeam(fetchImpl, TOKEN, async () => { + const out = await driver({ + name: service, + host: entry.domains.staging, + driver: label as ProbeTarget["driver"], + }); + expect(out.ok).toBe(false); + if (out.ok === false) expect(out.error).toMatch(/CRASHED/); + }); + }); + + it("fails on healthcheck != 200", async () => { + const entry = SERVICES[service]; + if (!entry) throw new Error("test setup"); + const fetchImpl = makeFetch((url) => { + if (url.includes("/graphql/v2")) + return gqlDeploymentResponse("SUCCESS"); + return Promise.resolve(mkResponse({ status: 502 })); + }); + await withGlobalSeam(fetchImpl, TOKEN, async () => { + const out = await driver({ + name: service, + host: entry.domains.staging, + driver: label as ProbeTarget["driver"], + }); + expect(out.ok).toBe(false); + if (out.ok === false) expect(out.error).toMatch(/502/); + }); + }); + }, +); diff --git a/showcase/scripts/__tests__/verify-deploy.test.ts b/showcase/scripts/__tests__/verify-deploy.test.ts new file mode 100644 index 00000000000..ec5a6bec411 --- /dev/null +++ b/showcase/scripts/__tests__/verify-deploy.test.ts @@ -0,0 +1,150 @@ +import { describe, expect, it } from "vitest"; +import { parseArgs, resolveProbeTargets, runVerify } from "../verify-deploy"; +import type { ProbeRunner } from "../verify-deploy"; + +describe("verify-deploy argv parsing", () => { + it("requires --env", () => { + expect(() => parseArgs([])).toThrow(/--env/); + }); + + it("accepts --env staging and --env prod", () => { + expect(parseArgs(["--env", "staging"]).env).toBe("staging"); + expect(parseArgs(["--env=prod"]).env).toBe("prod"); + }); + + it("rejects unknown envs", () => { + expect(() => parseArgs(["--env", "dev"])).toThrow(/Unknown env/); + }); + + it("accepts optional --services CSV", () => { + const parsed = parseArgs(["--env", "staging", "--services", "docs,shell"]); + expect(parsed.services).toEqual(["docs", "shell"]); + }); + + it("rejects empty --services= equals-form (mirrors space-form behavior)", () => { + expect(() => parseArgs(["--env", "staging", "--services="])).toThrow( + /--services/, + ); + }); + + it("rejects --services space-form whose CSV is all empty entries (symmetry with equals-form)", () => { + // Bug: space-form previously only guarded `!v` on the raw next-arg, + // so `--services ,,` produced an empty list that fell through to a + // less-precise zero-targets error. Both forms must throw the same + // precise `--services requires a CSV value` here. + expect(() => parseArgs(["--env=staging", "--services", ",,"])).toThrow( + /--services requires a CSV value/, + ); + }); + + it("rejects bare trailing --env (no following value)", () => { + // Bug: `argv[++i]` was undefined and we deferred to a vague + // `resolveEnv` error. Must throw the precise message here. + expect(() => parseArgs(["--env"])).toThrow( + /--env requires a value \(staging\|prod\)/, + ); + }); + + it("rejects --env= with empty value (symmetry with bare trailing --env)", () => { + expect(() => parseArgs(["--env="])).toThrow( + /--env requires a value \(staging\|prod\)/, + ); + }); +}); + +describe("resolveProbeTargets", () => { + it("filters SSOT to entries where probe[env] is true", () => { + const targets = resolveProbeTargets({ env: "staging" }); + // docs is staging:true → must be present. + expect(targets.find((t) => t.name === "docs")).toBeDefined(); + }); + + it("REFUSES when a probe-required service has no domain for the env", () => { + expect(() => + resolveProbeTargets({ + env: "staging", + overrides: { + docs: { domains: { staging: "", prod: "docs.copilotkit.ai" } }, + }, + }), + ).toThrow(/missing.*staging.*domain/i); + }); + + it("honors --services filter (subset of probe-eligible)", () => { + const targets = resolveProbeTargets({ + env: "staging", + services: ["docs"], + }); + expect(targets.length).toBe(1); + expect(targets[0].name).toBe("docs"); + }); + + it("REFUSES a typo'd service name (unknown service, not silent drop)", () => { + expect(() => + resolveProbeTargets({ env: "staging", services: ["docss"] }), + ).toThrow(/unknown service.*docss/i); + }); + + it("REFUSES a service that exists in SSOT but is not probe-eligible for the env", () => { + // Find a service whose probe[staging] is false. + // Use override seam to flip probe state without mutating SSOT. + // Since resolveProbeTargets doesn't expose a probe-flag override, + // we pick a real service name and an env where the SSOT probe is + // false. Search SERVICES for an entry where probe.staging===false. + // If none exists, this test still validates the error string for + // the more common typo case via the prior test; we focus on the + // distinct error phrasing. + // + // Practical assertion: an unknown name surfaces as "unknown + // service", which is structurally a different (clearer) error + // than "not probe-eligible". The two paths must be distinguished. + expect(() => + resolveProbeTargets({ env: "staging", services: ["totally-fake"] }), + ).toThrow(/unknown service/i); + }); +}); + +describe("runVerify driver dispatch", () => { + it("calls the driver for each target and fails loud on any red", async () => { + const calls: string[] = []; + const runner: ProbeRunner = async (target) => { + calls.push(`${target.driver}:${target.host}`); + if (target.name === "docs") { + return { ok: false, error: "DOM string missing" }; + } + return { ok: true }; + }; + const summary = await runVerify({ + env: "staging", + services: ["docs", "shell"], + runner, + }); + expect(calls).toContain("docs:docs.staging.copilotkit.ai"); + expect(calls).toContain("shell:showcase.staging.copilotkit.ai"); + expect(summary.failed.map((f) => f.name)).toEqual(["docs"]); + expect(summary.exitCode).toBe(1); + }); + + it("exits 0 when all probes green", async () => { + const summary = await runVerify({ + env: "staging", + services: ["docs"], + runner: async () => ({ ok: true }), + }); + expect(summary.exitCode).toBe(0); + }); + + it("FAILS LOUD on zero resolved targets (never silently exit 0)", async () => { + // resolveProbeTargets now throws on unknown service names, so the + // zero-target shape can only happen via the API (empty filter + // set). We exercise the runVerify guard directly: a runner that's + // never called and an exit code of 1 with a clear diagnostic. + const summary = await runVerify({ + env: "staging", + services: [], + runner: async () => ({ ok: true }), + }); + expect(summary.exitCode).not.toBe(0); + expect(summary.failed.length).toBeGreaterThan(0); + }); +}); diff --git a/showcase/scripts/__tests__/verify-railway-image-refs.test.ts b/showcase/scripts/__tests__/verify-railway-image-refs.test.ts new file mode 100644 index 00000000000..28061d665c4 --- /dev/null +++ b/showcase/scripts/__tests__/verify-railway-image-refs.test.ts @@ -0,0 +1,318 @@ +/** + * Tests for the Railway image-ref gate (`verify-railway-image-refs.ts`) + * and the SSOT fields it consumes (`railway-envs.ts`). + * + * Style note: validators are pure and exported; the GraphQL fetch is + * the only impure surface and is exercised manually (per the script's + * docstring). We unit-test the pure validators against synthesized + * inputs — no Railway API calls. + */ + +import { describe, it, expect } from "vitest"; +import { + findMissingServices, + findUntrackedServices, + summarizeFailures, + validateImage, +} from "../verify-railway-image-refs"; +import { SERVICES, repoNameFor } from "../railway-envs"; +import type { ServiceEntry } from "../railway-envs"; + +describe("ServiceEntry gateIgnore field", () => { + it("is optional on the type and defaults to falsy when unset", () => { + // Every real SSOT entry has gateIgnore unset (undefined / falsy). + for (const [name, entry] of Object.entries(SERVICES)) { + const gi = (entry as ServiceEntry).gateIgnore; + expect(gi === undefined || gi === false, `${name} gateIgnore`).toBe(true); + } + }); +}); + +describe("findUntrackedServices (Railway -> SSOT direction)", () => { + it("returns empty when every Railway-reported service is in the SSOT", () => { + // The SSOT keys themselves are by definition all in the SSOT, so + // passing them as "Railway-reported" should yield zero untracked. + const all = new Set(Object.keys(SERVICES)); + expect(findUntrackedServices(all)).toEqual([]); + }); + + it("flags a Railway service that is not in the SSOT", () => { + const railway = new Set([ + "showcase-mastra", // tracked + "phantom-relay", // untracked + ]); + expect(findUntrackedServices(railway)).toEqual(["phantom-relay"]); + }); + + it("returns names sorted for stable output", () => { + const railway = new Set([ + "zeta-svc", + "alpha-svc", + "showcase-mastra", + ]); + expect(findUntrackedServices(railway)).toEqual(["alpha-svc", "zeta-svc"]); + }); + + it("does NOT flag a service that the SSOT marks gateIgnore: true", () => { + // Inject a transient entry into SERVICES for this test, then remove. + const sentinel = "transient-third-party-relay"; + (SERVICES as Record)[sentinel] = { + serviceId: "00000000-0000-0000-0000-000000000000", + prodInstanceId: "11111111-1111-1111-1111-111111111111", + stagingInstanceId: "22222222-2222-2222-2222-222222222222", + ciBuilt: false, + gateValidated: false, + gateIgnore: true, + domains: { + staging: "transient-third-party-relay-staging.up.railway.app", + prod: "transient-third-party-relay-production.up.railway.app", + }, + probe: { staging: false, prod: false, driver: "agent" }, + }; + try { + const railway = new Set([sentinel, "showcase-mastra"]); + expect(findUntrackedServices(railway)).toEqual([]); + } finally { + delete (SERVICES as Record)[sentinel]; + } + }); +}); + +describe("main() unknown-service policy", () => { + // We exercise the pure helper that main() uses, not main() itself + // (main wraps the live GraphQL call and process.exit; out of scope + // for a unit test). + it("reports an untracked Railway service as a hard violation", () => { + const railwayReported = new Set([ + "showcase-mastra", + "rogue-service", + ]); + const untracked = findUntrackedServices(railwayReported); + expect(untracked).toContain("rogue-service"); + // Hard-fail semantics: any non-empty result must cause the gate + // to exit non-zero. We assert the contract by checking the + // boolean the caller will branch on: + expect(untracked.length > 0).toBe(true); + }); +}); + +describe("summarizeFailures", () => { + it("includes untracked Railway services in the failure block and exits non-zero", () => { + const out = summarizeFailures({ + violations: [], + missingByEnv: { prod: [], staging: [] }, + untracked: ["phantom-relay"], + checked: 50, + skipped: 0, + }); + expect(out.shouldFail).toBe(true); + expect(out.lines.join("\n")).toMatch(/phantom-relay/); + expect(out.lines.join("\n")).toMatch(/not in the SSOT/i); + }); + + it("does not fail when nothing is wrong", () => { + const out = summarizeFailures({ + violations: [], + missingByEnv: { prod: [], staging: [] }, + untracked: [], + checked: 54, + skipped: 0, + }); + expect(out.shouldFail).toBe(false); + }); + + it("flags shape violations", () => { + const out = summarizeFailures({ + violations: [ + { + service: "showcase-mastra", + env: "prod", + image: "ghcr.io/copilotkit/showcase-mastra:latest", + reason: "prod must be pinned to `@sha256:` (got `:latest`)", + }, + ], + missingByEnv: { prod: [], staging: [] }, + untracked: [], + checked: 50, + skipped: 0, + }); + expect(out.shouldFail).toBe(true); + expect(out.lines.join("\n")).toMatch(/showcase-mastra/); + }); + + it("flags missing services per env", () => { + const out = summarizeFailures({ + violations: [], + missingByEnv: { prod: ["showcase-foo"], staging: [] }, + untracked: [], + checked: 50, + skipped: 0, + }); + expect(out.shouldFail).toBe(true); + expect(out.lines.join("\n")).toMatch(/showcase-foo/); + }); +}); + +describe("WS-C: all 27 services gateValidated, with correct overrides", () => { + const FIVE_NEW = [ + ["dashboard", "showcase-shell-dashboard"], + ["docs", "showcase-shell-docs"], + ["dojo", "showcase-shell-dojo"], + ["shell", "showcase-shell"], + ["harness", "showcase-harness"], + ] as const; + + it("has 27 services in the SSOT", () => { + expect(Object.keys(SERVICES)).toHaveLength(27); + }); + + it("marks every service gateValidated (no Phase-2 holdouts)", () => { + const unvalidated = Object.entries(SERVICES) + .filter(([, entry]) => !entry.gateValidated) + .map(([name]) => name); + expect(unvalidated).toEqual([]); + }); + + for (const [serviceKey, expectedRepo] of FIVE_NEW) { + it(`resolves ${serviceKey} -> ${expectedRepo} for both envs via repoNameFor`, () => { + expect(repoNameFor(serviceKey, "prod")).toBe(expectedRepo); + expect(repoNameFor(serviceKey, "staging")).toBe(expectedRepo); + }); + + it(`carries the repoNameOverride directly on the SERVICES entry for ${serviceKey}`, () => { + const entry = SERVICES[serviceKey]; + expect(entry.repoNameOverride?.prod).toBe(expectedRepo); + expect(entry.repoNameOverride?.staging).toBe(expectedRepo); + }); + } + + it("findMissingServices treats all 27 as gateValidated targets", () => { + // With nothing "present", every gateValidated service should + // appear in the missing set; after C.3 that means all 27. + const missingProd = findMissingServices("prod", new Set()); + const missingStaging = findMissingServices("staging", new Set()); + expect(missingProd).toHaveLength(27); + expect(missingStaging).toHaveLength(27); + }); +}); + +describe("WS-C: shape validation for the five newly-gated services", () => { + const PROD_DIGEST = "@sha256:" + "a".repeat(64); + + const FIVE_NEW = [ + { key: "dashboard", repo: "showcase-shell-dashboard" }, + { key: "docs", repo: "showcase-shell-docs" }, + { key: "dojo", repo: "showcase-shell-dojo" }, + { key: "shell", repo: "showcase-shell" }, + { key: "harness", repo: "showcase-harness" }, + ] as const; + + for (const { key, repo } of FIVE_NEW) { + it(`${key}: prod requires @sha256, :latest on prod fails`, () => { + const v = validateImage(`ghcr.io/copilotkit/${repo}:latest`, { + env: "prod", + repoName: repo, + }); + expect(v).not.toBeNull(); + expect(v?.reason).toMatch(/prod must be pinned to `@sha256:`/); + }); + + it(`${key}: prod accepts the canonical @sha256 shape`, () => { + const v = validateImage(`ghcr.io/copilotkit/${repo}${PROD_DIGEST}`, { + env: "prod", + repoName: repo, + }); + expect(v).toBeNull(); + }); + + it(`${key}: staging accepts :latest on the correct repo`, () => { + const v = validateImage(`ghcr.io/copilotkit/${repo}:latest`, { + env: "staging", + repoName: repo, + }); + expect(v).toBeNull(); + }); + + it(`${key}: staging rejects @sha256 (must float on :latest)`, () => { + const v = validateImage(`ghcr.io/copilotkit/${repo}${PROD_DIGEST}`, { + env: "staging", + repoName: repo, + }); + expect(v).not.toBeNull(); + expect(v?.reason).toMatch(/staging must float on :latest/); + }); + + it(`${key}: rejects the wrong GHCR repo name on prod`, () => { + // E.g. ghcr.io/copilotkit/dashboard@sha256:... — what the gate + // would see if someone added gateValidated:true without the + // matching repoNameOverride. Repo NAME must match override. + const wrongRepo = `ghcr.io/copilotkit/${key}${PROD_DIGEST}`; + const v = validateImage(wrongRepo, { env: "prod", repoName: repo }); + expect(v).not.toBeNull(); + expect(v?.reason).toMatch(/image repo name mismatches expected/); + }); + + it(`${key}: rejects the wrong GHCR repo name on staging`, () => { + const wrongRepo = `ghcr.io/copilotkit/${key}:latest`; + const v = validateImage(wrongRepo, { + env: "staging", + repoName: repo, + }); + expect(v).not.toBeNull(); + expect(v?.reason).toMatch(/image repo name mismatches expected/); + }); + } +}); + +describe("WS-C: malformed ref negatives", () => { + it("rejects `:sha256-` on prod (missing the @ separator)", () => { + // Shape: ghcr.io/copilotkit/:sha256- + // Looks vaguely like a digest pin but is actually a *tag* whose + // literal name starts with "sha256-". This is the closest shape + // to the 2026-04-21 "atest" corruption and must fail loudly. + const bad = "ghcr.io/copilotkit/showcase-shell:sha256-" + "a".repeat(64); + const v = validateImage(bad, { + env: "prod", + repoName: "showcase-shell", + }); + expect(v).not.toBeNull(); + expect(v?.image).toBe(bad); + // Reason must mention canonical prod shape so the operator knows + // exactly what to fix. + expect(v?.reason).toMatch(/canonical (prod )?shape/); + }); + + it("rejects bare `@sha256:` on prod", () => { + const bad = "ghcr.io/copilotkit/showcase-shell@sha256:" + "a".repeat(10); + const v = validateImage(bad, { + env: "prod", + repoName: "showcase-shell", + }); + expect(v).not.toBeNull(); + }); + + it("rejects a truncated `atest`-style tag on staging", () => { + // The exact 2026-04-21 corruption shape from the script docstring. + const bad = "ghcr.io/copilotkit/showcase-shell-dashboardatest"; + const v = validateImage(bad, { + env: "staging", + repoName: "showcase-shell-dashboard", + }); + expect(v).not.toBeNull(); + }); + + it("rejects non-ghcr.io registries on both envs", () => { + const prodBad = + "docker.io/copilotkit/showcase-shell@sha256:" + "b".repeat(64); + const stagingBad = "docker.io/copilotkit/showcase-shell:latest"; + expect( + validateImage(prodBad, { env: "prod", repoName: "showcase-shell" }), + ).not.toBeNull(); + expect( + validateImage(stagingBad, { + env: "staging", + repoName: "showcase-shell", + }), + ).not.toBeNull(); + }); +}); diff --git a/showcase/scripts/aggregate-build-results.ts b/showcase/scripts/aggregate-build-results.ts new file mode 100644 index 00000000000..f3128892391 --- /dev/null +++ b/showcase/scripts/aggregate-build-results.ts @@ -0,0 +1,170 @@ +/** + * aggregate-build-results.ts — run in the `aggregate-build-results` job + * of showcase_build.yml AFTER actions/download-artifact has extracted + * every per-slot `build-result-` artifact into + * $INPUT_DIR/build-result-/result.json. + * + * Responsibilities: + * 1. Read every per-slot result.json under $INPUT_DIR. + * 2. Merge via mergeBuildResultFiles (single source of contract truth). + * 3. Write the canonical $OUTPUT_DIR/results.json (uploaded as the + * `build-results` artifact for cross-workflow consumption). + * 4. Append `results=...` and `any_success=true|false` to $GITHUB_OUTPUT + * so the redeploy-staging guard and the deploy workflow can read + * them as job-level outputs. + * + * No GitHub API calls. No job-name parsing. Pure filesystem aggregation. + * + * Testability: env reading lives in the CLI entrypoint at the bottom; + * the core is exported as `run({inputDir, outputDir, githubOutput})` so + * tests can drive it with temp dirs. + */ +import { + appendFileSync, + mkdirSync, + readFileSync, + readdirSync, + writeFileSync, +} from "node:fs"; +import { randomBytes } from "node:crypto"; +import { join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { mergeBuildResultFiles, successSet } from "./lib/build-outputs"; + +function requireEnv(name: string): string { + const v = process.env[name]; + if (typeof v !== "string" || v.length === 0) { + throw new Error(`aggregate-build-results: $${name} is required`); + } + return v; +} + +export interface RunOptions { + inputDir: string; + outputDir: string; + githubOutput: string; +} + +/** + * Read a single per-slot result.json. On failure (missing file, permission + * error, etc.), wraps the error with the offending slot directory so the + * job log identifies WHICH slot was the culprit instead of dumping a raw + * ENOENT against a long opaque path. We refuse to silently skip the slot — + * a missing per-slot artifact is a real defect (the build job's artifact + * upload step is broken or the matrix collapsed) and silently dropping it + * would let a failed build masquerade as "not present" downstream. + */ +function readSlotPayload(inputDir: string, slotDirName: string): string { + const path = join(inputDir, slotDirName, "result.json"); + try { + return readFileSync(path, "utf-8"); + } catch (e) { + const code = + e && typeof e === "object" && "code" in e + ? String((e as { code?: unknown }).code) + : e instanceof Error + ? e.message + : String(e); + throw new Error( + `aggregate-build-results: ${slotDirName} is missing result.json (${code})`, + { cause: e }, + ); + } +} + +/** + * Emit the per-job outputs to $GITHUB_OUTPUT. `results` uses the + * multi-line heredoc form, which is the GHA-recommended encoding for + * any value that might contain (or grow to contain) a newline — most + * importantly, it survives pretty-printed JSON or other multi-line + * payloads without truncation. A random delimiter token prevents + * collision with embedded payloads. `any_success` stays a plain + * key=value line since the value is a fixed boolean literal. + * + * Written BEFORE results.json so a $GITHUB_OUTPUT write failure + * (e.g. the file is missing / not writable) aborts before we publish + * an artifact the downstream jobs would consume without seeing the + * matching job output. + */ +function writeGithubOutput( + githubOutput: string, + resultsJson: string, + anySuccess: boolean, +): void { + const delimiter = `EOF_${randomBytes(8).toString("hex")}`; + appendFileSync( + githubOutput, + `results<<${delimiter}\n${resultsJson}\n${delimiter}\n`, + ); + appendFileSync( + githubOutput, + `any_success=${anySuccess ? "true" : "false"}\n`, + ); +} + +export function run(opts: RunOptions): void { + const { inputDir, outputDir, githubOutput } = opts; + + mkdirSync(outputDir, { recursive: true }); + + const slotDirs = readdirSync(inputDir, { withFileTypes: true }) + .filter((d) => d.isDirectory() && d.name.startsWith("build-result-")) + .map((d) => d.name); + + // The aggregator job is gated upstream on `has_changes == 'true'`, so the + // build matrix is guaranteed non-empty by the time we run. A zero-slot + // input dir therefore signals a BROKEN per-slot artifact download (e.g. + // expired artifacts, wrong run-id, transient download error) — NOT a + // legitimate empty build set. Silently emitting `any_success=false` with + // `results=[]` would be indistinguishable from "all builds failed" and + // would push deploy down the false-green path where it probes the full + // service set against stale `:latest`. Fail loud instead. + if (slotDirs.length === 0) { + throw new Error( + `aggregate-build-results: found 0 build-result-* slot dirs in ${inputDir} — ` + + `the per-slot artifact download produced nothing; this indicates a broken ` + + `download, not an empty build set (the job only runs when >=1 service was scheduled).`, + ); + } + + const payloads = slotDirs.map((name) => readSlotPayload(inputDir, name)); + + const merged = mergeBuildResultFiles(payloads); + const resultsJson = JSON.stringify(merged); + const anySuccess = successSet(merged).length > 0; + + // Emit $GITHUB_OUTPUT first so a write failure here doesn't leave a + // published results.json artifact without a matching job output. + writeGithubOutput(githubOutput, resultsJson, anySuccess); + + // Trailing newline for consistency with conventional JSON-on-disk + // tooling (POSIX line, diff-friendly). + writeFileSync(join(outputDir, "results.json"), `${resultsJson}\n`); +} + +function main(): void { + run({ + inputDir: requireEnv("INPUT_DIR"), + outputDir: requireEnv("OUTPUT_DIR"), + githubOutput: requireEnv("GITHUB_OUTPUT"), + }); +} + +// CLI entrypoint: only run main() when invoked directly (e.g. `tsx +// aggregate-build-results.ts`), NOT when imported by a test. Comparing +// `import.meta.url` against process.argv[1] is the standard ESM idiom. +const invokedDirectly = (() => { + try { + return ( + typeof process !== "undefined" && + Array.isArray(process.argv) && + process.argv[1] === fileURLToPath(import.meta.url) + ); + } catch { + return false; + } +})(); + +if (invokedDirectly) { + main(); +} diff --git a/showcase/scripts/create-integration/index.ts b/showcase/scripts/create-integration/index.ts index 9b4e248d20b..7ba8da54087 100644 --- a/showcase/scripts/create-integration/index.ts +++ b/showcase/scripts/create-integration/index.ts @@ -2011,7 +2011,7 @@ export function updateWorkflows(args: CLIArgs) { echo "::error::Railway placeholders not replaced" >&2 exit 1 fi - curl -sf -X POST https://backboard.railway.com/graphql/v2 \\ + curl -sf -X POST https://backboard.railway.app/graphql/v2 \\ -H "Authorization: Bearer \${{ secrets.RAILWAY_TOKEN }}" \\ -H "Content-Type: application/json" \\ -d '{"query":"mutation { serviceInstanceRedeploy(serviceId: \\"RAILWAY_SERVICE_ID\\", environmentId: \\"RAILWAY_ENVIRONMENT_ID\\") }"}' \\ diff --git a/showcase/scripts/deploy-to-railway.ts b/showcase/scripts/deploy-to-railway.ts index 72ac69ac59c..5ca9f0a6b6d 100644 --- a/showcase/scripts/deploy-to-railway.ts +++ b/showcase/scripts/deploy-to-railway.ts @@ -18,12 +18,13 @@ import fs from "fs"; import path from "path"; import yaml from "yaml"; import { fileURLToPath } from "url"; +import { RAILWAY_GRAPHQL_ENDPOINT } from "./lib/railway-graphql"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const ROOT = path.resolve(__dirname, ".."); const PACKAGES_DIR = path.join(ROOT, "integrations"); -const RAILWAY_API = "https://backboard.railway.com/graphql/v2"; +const RAILWAY_API = RAILWAY_GRAPHQL_ENDPOINT; const SHOWCASE = { projectId: "6f8c6bff-a80d-4f8f-b78d-50b32bcf4479", diff --git a/showcase/scripts/emit-railway-envs-json.ts b/showcase/scripts/emit-railway-envs-json.ts new file mode 100644 index 00000000000..3f35aa259ee --- /dev/null +++ b/showcase/scripts/emit-railway-envs-json.ts @@ -0,0 +1,136 @@ +#!/usr/bin/env npx tsx +/** + * emit-railway-envs-json.ts — Serialize the railway-envs.ts SSOT to a + * canonical JSON artifact at `showcase/scripts/railway-envs.generated.json`. + * + * Consumers: + * - showcase/bin/railway (Ruby) reads this to derive EXPECTED_DOMAINS + * instead of maintaining a parallel Ruby hash. + * - .github/workflows/showcase_deploy.yml reads it to build the verify + * matrix without duplicating the inline JSON array. + * + * Idempotent: writes only when the serialized output differs from disk. + * CI runs this with `--check`: non-zero exit if the on-disk file is stale. + * + * Flags: + * --check Exit 1 if on-disk JSON differs from SSOT (don't write). + * Exit 2 if the read fails for any reason other than the + * file being absent (e.g. EACCES, EISDIR) — fail loud + * rather than masquerade a real error as drift. + * --out= Override the output path (used by tests for hermetic + * writes). Defaults to the canonical tracked artifact. + */ + +import { mkdirSync, readFileSync, writeFileSync } from "node:fs"; +import { dirname, resolve } from "node:path"; +import { + PRODUCTION_ENV_ID, + PROJECT_ID, + SERVICES, + STAGING_ENV_ID, +} from "./railway-envs"; + +const DEFAULT_OUTPUT_PATH = resolve( + new URL(".", import.meta.url).pathname, + "railway-envs.generated.json", +); + +interface Emitted { + projectId: string; + envIds: { staging: string; prod: string }; + services: Array<{ + name: string; + serviceId: string; + prodInstanceId: string; + stagingInstanceId: string; + ciBuilt: boolean; + gateValidated: boolean; + dispatchName?: string; + repoNameOverride?: { prod?: string; staging?: string }; + domains: { staging: string; prod: string }; + probe: { staging: boolean; prod: boolean; driver: string }; + }>; +} + +function buildPayload(): Emitted { + const services: Emitted["services"] = Object.entries(SERVICES) + .sort(([a], [b]) => a.localeCompare(b)) + .map(([name, entry]) => ({ + name, + serviceId: entry.serviceId, + prodInstanceId: entry.prodInstanceId, + stagingInstanceId: entry.stagingInstanceId, + ciBuilt: entry.ciBuilt, + gateValidated: entry.gateValidated, + dispatchName: entry.dispatchName, + repoNameOverride: entry.repoNameOverride, + domains: { staging: entry.domains.staging, prod: entry.domains.prod }, + probe: { + staging: entry.probe.staging, + prod: entry.probe.prod, + driver: entry.probe.driver, + }, + })); + return { + projectId: PROJECT_ID, + envIds: { staging: STAGING_ENV_ID, prod: PRODUCTION_ENV_ID }, + services, + }; +} + +function serialize(payload: Emitted): string { + return JSON.stringify(payload, null, 2) + "\n"; +} + +function parseOutPath(args: string[]): string { + const flag = args.find((a) => a.startsWith("--out=")); + if (flag) return resolve(flag.slice("--out=".length)); + return DEFAULT_OUTPUT_PATH; +} + +function main(): void { + const args = process.argv.slice(2); + const check = args.includes("--check"); + const outputPath = parseOutPath(args); + const payload = buildPayload(); + const next = serialize(payload); + + if (check) { + let current = ""; + try { + current = readFileSync(outputPath, "utf8"); + } catch (err) { + const code = (err as NodeJS.ErrnoException).code; + if (code !== "ENOENT") { + // Non-ENOENT errors (EACCES, EISDIR, etc.) are real failures, not + // drift. Fail loud so CI surfaces the real cause instead of + // overwriting on a false signal or printing a misleading "stale" + // message. + process.stderr.write( + `emit-railway-envs-json: failed to read ${outputPath}: ${ + (err as Error).message + }\n`, + ); + process.exit(2); + } + // ENOENT — file missing, treat as drift below. + } + if (current !== next) { + process.stderr.write( + `railway-envs.generated.json is stale. Re-run:\n npx tsx showcase/scripts/emit-railway-envs-json.ts\n`, + ); + process.exit(1); + } + process.stdout.write("railway-envs.generated.json is up to date.\n"); + return; + } + + mkdirSync(dirname(outputPath), { recursive: true }); + writeFileSync(outputPath, next); + process.stdout.write(`wrote ${outputPath}\n`); +} + +const isMain = + import.meta.url === `file://${process.argv[1]}` || + process.argv[1]?.endsWith("emit-railway-envs-json.ts"); +if (isMain) main(); diff --git a/showcase/scripts/lib/__tests__/build-outputs.test.ts b/showcase/scripts/lib/__tests__/build-outputs.test.ts new file mode 100644 index 00000000000..92c855b0b16 --- /dev/null +++ b/showcase/scripts/lib/__tests__/build-outputs.test.ts @@ -0,0 +1,206 @@ +import { describe, expect, it } from "vitest"; +import { + buildResultArtifactName, + mergeBuildResultFiles, + parseBuildOutputs, + shouldRedeployStaging, + successSet, +} from "../build-outputs"; +import type { BuildOutcome, ServiceBuildResult } from "../build-outputs"; + +const sample: ServiceBuildResult[] = [ + { service: "showcase-mastra", status: "success" }, + { service: "showcase-ag2", status: "failure" }, + { service: "shell-docs", status: "skipped" }, + { service: "showcase-aimock", status: "success" }, +]; + +describe("build-outputs", () => { + it("parses a JSON array of {service,status} entries", () => { + const json = JSON.stringify(sample); + expect(parseBuildOutputs(json)).toEqual(sample); + }); + + it("throws on malformed JSON", () => { + expect(() => parseBuildOutputs("not json")).toThrow(/parse/i); + }); + + it("throws on entries missing fields", () => { + expect(() => parseBuildOutputs(JSON.stringify([{ service: "x" }]))).toThrow( + /status/i, + ); + }); + + it("throws on an unknown status value", () => { + const bad = JSON.stringify([{ service: "x", status: "weird" }]); + expect(() => parseBuildOutputs(bad)).toThrow(/status/i); + }); + + it("rejects an empty service string", () => { + const bad = JSON.stringify([{ service: "", status: "success" }]); + expect(() => parseBuildOutputs(bad)).toThrow(/service/i); + }); + + it("rejects a whitespace-only service string", () => { + const bad = JSON.stringify([{ service: " ", status: "success" }]); + expect(() => parseBuildOutputs(bad)).toThrow(/service/i); + }); + + it("error message for parseBuildOutputs includes the entry index", () => { + const bad = JSON.stringify([ + { service: "ok", status: "success" }, + { service: "x", status: "weird" }, + ]); + expect(() => parseBuildOutputs(bad)).toThrow(/\[1\]/); + }); + + it("successSet returns only services with status === 'success'", () => { + expect(successSet(sample).sort()).toEqual( + ["showcase-aimock", "showcase-mastra"].sort(), + ); + }); + + it("successSet returns empty when no services succeeded", () => { + const allFailed: ServiceBuildResult[] = [ + { service: "a", status: "failure" }, + { service: "b", status: "failure" }, + ]; + expect(successSet(allFailed)).toEqual([]); + }); + + it("type BuildOutcome enumerates success|failure|skipped", () => { + const outcomes: BuildOutcome[] = ["success", "failure", "skipped"]; + expect(outcomes).toHaveLength(3); + }); +}); + +describe("buildResultArtifactName", () => { + it("returns the canonical per-slot artifact name", () => { + expect(buildResultArtifactName("showcase-aimock")).toBe( + "build-result-showcase-aimock", + ); + }); + + it("rejects empty service names (would collide with aggregate artifact)", () => { + expect(() => buildResultArtifactName("")).toThrow(/service/i); + }); + + it("rejects whitespace-only service names", () => { + expect(() => buildResultArtifactName(" ")).toThrow(/service/i); + }); +}); + +describe("mergeBuildResultFiles", () => { + it("merges per-slot {service,status} JSON payloads into a single array", () => { + const slotPayloads = [ + '{"service":"showcase-mastra","status":"success"}', + '{"service":"showcase-ag2","status":"failure"}', + '{"service":"showcase-aimock","status":"success"}', + ]; + expect(mergeBuildResultFiles(slotPayloads)).toEqual([ + { service: "showcase-mastra", status: "success" }, + { service: "showcase-ag2", status: "failure" }, + { service: "showcase-aimock", status: "success" }, + ]); + }); + + it("throws when a slot payload is missing service or status", () => { + expect(() => mergeBuildResultFiles(['{"service":"x"}'])).toThrow(/status/i); + }); + + it("throws on an invalid status value", () => { + expect(() => + mergeBuildResultFiles(['{"service":"x","status":"weird"}']), + ).toThrow(/status/i); + }); + + it("returns an empty array when no slot payloads are provided", () => { + expect(mergeBuildResultFiles([])).toEqual([]); + }); + + it("rejects an empty service in a slot payload", () => { + expect(() => + mergeBuildResultFiles(['{"service":"","status":"success"}']), + ).toThrow(/service/i); + }); + + it("rejects a whitespace-only service in a slot payload", () => { + expect(() => + mergeBuildResultFiles(['{"service":" ","status":"success"}']), + ).toThrow(/service/i); + }); + + it("throws when duplicate service names appear across slot payloads", () => { + const slotPayloads = [ + '{"service":"dup","status":"failure"}', + '{"service":"other","status":"success"}', + '{"service":"dup","status":"success"}', + ]; + expect(() => mergeBuildResultFiles(slotPayloads)).toThrow(/duplicate/i); + expect(() => mergeBuildResultFiles(slotPayloads)).toThrow(/dup/); + }); + + it("normalizes trailing whitespace in service to its trimmed canonical form", () => { + const result = mergeBuildResultFiles([ + '{"service":"foo ","status":"success"}', + ]); + expect(result).toEqual([{ service: "foo", status: "success" }]); + }); + + it("normalizes leading whitespace in service to its trimmed canonical form", () => { + const result = mergeBuildResultFiles([ + '{"service":" foo","status":"success"}', + ]); + expect(result).toEqual([{ service: "foo", status: "success" }]); + }); + + it("parseBuildOutputs also returns the trimmed canonical service", () => { + const json = JSON.stringify([{ service: " bar ", status: "success" }]); + expect(parseBuildOutputs(json)).toEqual([ + { service: "bar", status: "success" }, + ]); + }); + + it("detects duplicates that differ only by surrounding whitespace", () => { + const slotPayloads = [ + '{"service":"foo","status":"failure"}', + '{"service":"foo ","status":"success"}', + ]; + expect(() => mergeBuildResultFiles(slotPayloads)).toThrow(/duplicate/i); + expect(() => mergeBuildResultFiles(slotPayloads)).toThrow(/foo/); + }); + + it("rejects an array payload with a clear 'expected object' error (not 'missing service')", () => { + expect(() => mergeBuildResultFiles(["[]"])).toThrow(/expected object/i); + expect(() => mergeBuildResultFiles(["[]"])).not.toThrow(/missing/i); + }); + + it("parseBuildOutputs rejects an array nested as an entry with 'expected object'", () => { + const bad = JSON.stringify([[]]); + expect(() => parseBuildOutputs(bad)).toThrow(/expected object/i); + }); +}); + +describe("shouldRedeployStaging", () => { + it("returns true when at least one service succeeded", () => { + expect( + shouldRedeployStaging([ + { service: "a", status: "success" }, + { service: "b", status: "failure" }, + ]), + ).toBe(true); + }); + + it("returns false when every service failed or was skipped", () => { + expect( + shouldRedeployStaging([ + { service: "a", status: "failure" }, + { service: "b", status: "skipped" }, + ]), + ).toBe(false); + }); + + it("returns false on empty input", () => { + expect(shouldRedeployStaging([])).toBe(false); + }); +}); diff --git a/showcase/scripts/lib/__tests__/railway-graphql.scan.test.ts b/showcase/scripts/lib/__tests__/railway-graphql.scan.test.ts new file mode 100644 index 00000000000..20f66adc72c --- /dev/null +++ b/showcase/scripts/lib/__tests__/railway-graphql.scan.test.ts @@ -0,0 +1,53 @@ +import { describe, expect, it } from "vitest"; +import { execFileSync } from "node:child_process"; +import path from "node:path"; + +const REPO_ROOT = path.resolve(__dirname, "../../../.."); + +describe("Railway GraphQL host unification", () => { + it("no source file references backboard.railway.com (must be .app)", () => { + // grep across showcase/ and .github/workflows/. Use -F (literal) so + // the dot in the pattern is not regex-interpreted. The test file + // itself contains the forbidden literal; exclude it via git pathspec + // `:(exclude)` magic. Also exclude two files that legitimately + // reference the deprecated host in documentation/negative-assertion + // form (no functional use): + // - railway-graphql.ts JSDoc explains the deprecated .com endpoint + // - railway-graphql.test.ts asserts the endpoint is NOT .com + // Use --untracked so a newly-added (not-yet-committed) file that + // reintroduces the forbidden host still trips the guard. Use + // execFileSync with an args array so REPO_ROOT is not shell-parsed. + let out = ""; + try { + out = execFileSync( + "git", + [ + "-C", + REPO_ROOT, + "grep", + "--untracked", + "-nF", + "backboard.railway.com", + "--", + "showcase", + ":(exclude)showcase/scripts/lib/__tests__/railway-graphql.scan.test.ts", + ":(exclude)showcase/scripts/lib/railway-graphql.ts", + ":(exclude)showcase/scripts/lib/__tests__/railway-graphql.test.ts", + ".github/workflows", + ], + { encoding: "utf-8" }, + ); + } catch (err) { + // git grep exits 1 when no matches — that's the clean-miss success + // case. Any other exit status (git missing, bad cwd, pathspec + // syntax error, etc.) must fail loud rather than silently pass. + const status = (err as { status?: number }).status; + if (status === 1) { + out = ""; + } else { + throw err; + } + } + expect(out).toBe(""); + }); +}); diff --git a/showcase/scripts/lib/__tests__/railway-graphql.test.ts b/showcase/scripts/lib/__tests__/railway-graphql.test.ts new file mode 100644 index 00000000000..5087cb52e95 --- /dev/null +++ b/showcase/scripts/lib/__tests__/railway-graphql.test.ts @@ -0,0 +1,14 @@ +import { describe, expect, it } from "vitest"; +import { RAILWAY_GRAPHQL_ENDPOINT } from "../railway-graphql"; + +describe("railway-graphql", () => { + it("uses the canonical backboard.railway.app host", () => { + expect(RAILWAY_GRAPHQL_ENDPOINT).toBe( + "https://backboard.railway.app/graphql/v2", + ); + }); + + it("never resolves to the .com host (historical drift target)", () => { + expect(RAILWAY_GRAPHQL_ENDPOINT).not.toContain("backboard.railway.com"); + }); +}); diff --git a/showcase/scripts/lib/__tests__/railway-token.envelope.test.ts b/showcase/scripts/lib/__tests__/railway-token.envelope.test.ts new file mode 100644 index 00000000000..d380aa8a19b --- /dev/null +++ b/showcase/scripts/lib/__tests__/railway-token.envelope.test.ts @@ -0,0 +1,152 @@ +import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterEach, beforeEach, describe, expect, it } from "vitest"; +import { resolveRailwayToken, RailwayTokenError } from "../railway-token"; + +/** + * Envelope-level tests for resolveRailwayToken — the unified resolver + * used by redeploy-env.ts and verify-railway-image-refs.ts. + * + * Each failure mode must throw a DISTINCT, actionable error so the + * silent-token-fallthrough diagnostic gap can never re-open. + */ +describe("resolveRailwayToken (envelope)", () => { + let dir: string; + const originalEnv = process.env.RAILWAY_TOKEN; + const originalHome = process.env.HOME; + + beforeEach(() => { + dir = mkdtempSync(join(tmpdir(), "rwy-token-env-")); + delete process.env.RAILWAY_TOKEN; + process.env.HOME = dir; + }); + + afterEach(() => { + rmSync(dir, { recursive: true, force: true }); + if (originalEnv === undefined) delete process.env.RAILWAY_TOKEN; + else process.env.RAILWAY_TOKEN = originalEnv; + if (originalHome === undefined) delete process.env.HOME; + else process.env.HOME = originalHome; + }); + + it("returns RAILWAY_TOKEN env var when set, source='env'", () => { + process.env.RAILWAY_TOKEN = "env-token-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; + const result = resolveRailwayToken(); + expect(result.token).toBe("env-token-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); + expect(result.source).toBe("env"); + }); + + it("returns token from ~/.railway/config.json when env unset, source='config'", () => { + mkdirSync(join(dir, ".railway")); + writeFileSync( + join(dir, ".railway", "config.json"), + JSON.stringify({ + user: { accessToken: "from-config-aaaaaaaaaaaaaaaaaaaaaaaa" }, + }), + ); + const result = resolveRailwayToken(); + expect(result.token).toBe("from-config-aaaaaaaaaaaaaaaaaaaaaaaa"); + expect(result.source).toBe("config"); + }); + + it("throws DISTINCT error when $HOME is unset and env-var also unset", () => { + delete process.env.HOME; + expect(() => resolveRailwayToken()).toThrow(RailwayTokenError); + try { + resolveRailwayToken(); + throw new Error("should have thrown"); + } catch (e) { + expect(e).toBeInstanceOf(RailwayTokenError); + const err = e as RailwayTokenError; + expect(err.code).toBe("NO_HOME"); + expect(err.message).toMatch(/\$HOME/); + } + }); + + it("trims RAILWAY_TOKEN env var (padded with whitespace/newline)", () => { + process.env.RAILWAY_TOKEN = " padded-token-xxxxxxxxxxxxxxxxxxxxxxxx\n"; + const result = resolveRailwayToken(); + expect(result.token).toBe("padded-token-xxxxxxxxxxxxxxxxxxxxxxxx"); + expect(result.source).toBe("env"); + }); + + it("treats whitespace-only RAILWAY_TOKEN as UNSET and falls through to config", () => { + process.env.RAILWAY_TOKEN = " "; + mkdirSync(join(dir, ".railway")); + writeFileSync( + join(dir, ".railway", "config.json"), + JSON.stringify({ + user: { accessToken: "from-config-bbbbbbbbbbbbbbbbbbbbbbbb" }, + }), + ); + const result = resolveRailwayToken(); + expect(result.token).toBe("from-config-bbbbbbbbbbbbbbbbbbbbbbbb"); + expect(result.source).toBe("config"); + }); + + it("treats whitespace-only RAILWAY_TOKEN as UNSET and surfaces NO_FILE when no config exists", () => { + process.env.RAILWAY_TOKEN = " \n"; + try { + resolveRailwayToken(); + throw new Error("should have thrown"); + } catch (e) { + expect(e).toBeInstanceOf(RailwayTokenError); + const err = e as RailwayTokenError; + // Must NOT be returned as source="env" with a whitespace token — + // that produces invalid Bearer headers and silent 401s. + expect(err.code).toBe("NO_FILE"); + } + }); + + it("throws DISTINCT error when ~/.railway/config.json does not exist", () => { + try { + resolveRailwayToken(); + throw new Error("should have thrown"); + } catch (e) { + expect(e).toBeInstanceOf(RailwayTokenError); + const err = e as RailwayTokenError; + expect(err.code).toBe("NO_FILE"); + expect(err.message).toMatch(/RAILWAY_TOKEN/); + expect(err.message).toMatch(/railway login/); + } + }); + + it("throws DISTINCT error when ~/.railway/config.json is malformed JSON", () => { + mkdirSync(join(dir, ".railway")); + writeFileSync(join(dir, ".railway", "config.json"), "{ not json"); + try { + resolveRailwayToken(); + throw new Error("should have thrown"); + } catch (e) { + expect(e).toBeInstanceOf(RailwayTokenError); + const err = e as RailwayTokenError; + expect(err.code).toBe("MALFORMED"); + expect(err.message).toMatch(/Malformed ~\/\.railway\/config\.json/); + } + }); + + it("throws DISTINCT error when config parses but yields no usable token (the silent-fallthrough gap)", () => { + mkdirSync(join(dir, ".railway")); + // Parses fine — just no token field at any layer. + writeFileSync( + join(dir, ".railway", "config.json"), + JSON.stringify({ projects: { something: "else" } }), + ); + try { + resolveRailwayToken(); + throw new Error("should have thrown"); + } catch (e) { + expect(e).toBeInstanceOf(RailwayTokenError); + const err = e as RailwayTokenError; + // CRITICAL: must NOT be the generic NO_FILE message — the + // user needs to know the file WAS found but had no token. + expect(err.code).toBe("NO_TOKEN_IN_CONFIG"); + expect(err.message).toMatch( + /~\/\.railway\/config\.json.*no.*token|found.*no.*token/i, + ); + // Must also hint at the remedy. + expect(err.message).toMatch(/railway login|RAILWAY_TOKEN/); + } + }); +}); diff --git a/showcase/scripts/lib/__tests__/railway-token.test.ts b/showcase/scripts/lib/__tests__/railway-token.test.ts new file mode 100644 index 00000000000..6c8ff4ae757 --- /dev/null +++ b/showcase/scripts/lib/__tests__/railway-token.test.ts @@ -0,0 +1,193 @@ +import { describe, expect, it, vi } from "vitest"; +import { resolveRailwayTokenFromConfig } from "../railway-token"; +import type { RailwayConfigShape } from "../railway-token"; + +describe("resolveRailwayTokenFromConfig", () => { + it("prefers user.accessToken when present", () => { + const cfg: RailwayConfigShape = { + user: { + accessToken: "new-access-token-43-chars-or-more-aaaaaaaa", + token: "legacy-short-token", + }, + }; + const warn = vi.fn(); + const result = resolveRailwayTokenFromConfig(cfg, { warn }); + expect(result).toBe("new-access-token-43-chars-or-more-aaaaaaaa"); + expect(warn).not.toHaveBeenCalled(); + }); + + it("falls back to top-level accessToken", () => { + const cfg: RailwayConfigShape = { + accessToken: "top-level-access-token-aaaaaaaaaaaaaaaaaaaa", + }; + const warn = vi.fn(); + const result = resolveRailwayTokenFromConfig(cfg, { warn }); + expect(result).toBe("top-level-access-token-aaaaaaaaaaaaaaaaaaaa"); + expect(warn).not.toHaveBeenCalled(); + }); + + it("falls back to legacy user.token AND emits a deprecation warning", () => { + const cfg: RailwayConfigShape = { + user: { token: "legacy-short-token-aaaaaaaaaa" }, + }; + const warn = vi.fn(); + const result = resolveRailwayTokenFromConfig(cfg, { warn }); + expect(result).toBe("legacy-short-token-aaaaaaaaaa"); + expect(warn).toHaveBeenCalledTimes(1); + expect(warn.mock.calls[0][0]).toMatch( + /deprecated.*user\.token.*accessToken/i, + ); + }); + + it("falls back to top-level token with deprecation warning", () => { + const cfg: RailwayConfigShape = { token: "legacy-top-aaaaaaaaa" }; + const warn = vi.fn(); + const result = resolveRailwayTokenFromConfig(cfg, { warn }); + expect(result).toBe("legacy-top-aaaaaaaaa"); + expect(warn).toHaveBeenCalledTimes(1); + }); + + it("returns undefined and does not warn on an empty config", () => { + const warn = vi.fn(); + const result = resolveRailwayTokenFromConfig({}, { warn }); + expect(result).toBeUndefined(); + expect(warn).not.toHaveBeenCalled(); + }); + + it("ignores empty-string tokens at every layer", () => { + const cfg: RailwayConfigShape = { + user: { accessToken: "", token: "" }, + accessToken: "", + token: "", + }; + const warn = vi.fn(); + const result = resolveRailwayTokenFromConfig(cfg, { warn }); + expect(result).toBeUndefined(); + }); + + it("treats whitespace-only user.accessToken as empty and falls through", () => { + const cfg: RailwayConfigShape = { + user: { accessToken: " " }, + }; + const warn = vi.fn(); + const result = resolveRailwayTokenFromConfig(cfg, { warn }); + expect(result).toBeUndefined(); + expect(warn).not.toHaveBeenCalled(); + }); + + it("falls through whitespace-only user.accessToken to legacy user.token with warn", () => { + const cfg: RailwayConfigShape = { + user: { + accessToken: "\n\t ", + token: "legacy-short-token-aaaaaaaaaa", + }, + }; + const warn = vi.fn(); + const result = resolveRailwayTokenFromConfig(cfg, { warn }); + expect(result).toBe("legacy-short-token-aaaaaaaaaa"); + expect(warn).toHaveBeenCalledTimes(1); + }); + + it("treats whitespace-only tokens at every layer as empty", () => { + const cfg: RailwayConfigShape = { + user: { accessToken: " ", token: "\t" }, + accessToken: "\n", + token: " ", + }; + const warn = vi.fn(); + const result = resolveRailwayTokenFromConfig(cfg, { warn }); + expect(result).toBeUndefined(); + expect(warn).not.toHaveBeenCalled(); + }); + + it("returns undefined when config is null", () => { + const warn = vi.fn(); + const result = resolveRailwayTokenFromConfig(null, { warn }); + expect(result).toBeUndefined(); + expect(warn).not.toHaveBeenCalled(); + }); + + it("returns undefined when config is undefined", () => { + const warn = vi.fn(); + const result = resolveRailwayTokenFromConfig(undefined, { warn }); + expect(result).toBeUndefined(); + expect(warn).not.toHaveBeenCalled(); + }); + + it("returns undefined when config is a string (non-object JSON)", () => { + const warn = vi.fn(); + const result = resolveRailwayTokenFromConfig( + "not-an-object" as unknown as RailwayConfigShape, + { warn }, + ); + expect(result).toBeUndefined(); + expect(warn).not.toHaveBeenCalled(); + }); + + it("returns undefined when config is a number", () => { + const warn = vi.fn(); + const result = resolveRailwayTokenFromConfig( + 42 as unknown as RailwayConfigShape, + { warn }, + ); + expect(result).toBeUndefined(); + expect(warn).not.toHaveBeenCalled(); + }); + + it("returns the trimmed token when user.accessToken has surrounding whitespace", () => { + const cfg: RailwayConfigShape = { + user: { accessToken: " padded-token-aaaaaaaaaaaaaaaaaaaa " }, + }; + const warn = vi.fn(); + const result = resolveRailwayTokenFromConfig(cfg, { warn }); + expect(result).toBe("padded-token-aaaaaaaaaaaaaaaaaaaa"); + expect(warn).not.toHaveBeenCalled(); + }); + + it("returns the trimmed token when user.accessToken has a trailing newline", () => { + const cfg: RailwayConfigShape = { + user: { accessToken: "abc-access-token-aaaaaaaaaaaaaaaaaaaa\n" }, + }; + const warn = vi.fn(); + const result = resolveRailwayTokenFromConfig(cfg, { warn }); + expect(result).toBe("abc-access-token-aaaaaaaaaaaaaaaaaaaa"); + }); + + it("returns the trimmed token when top-level accessToken has surrounding whitespace", () => { + const cfg: RailwayConfigShape = { + accessToken: "\ttop-padded-aaaaaaaaaaaaaaaaaaaaaaaa\n", + }; + const warn = vi.fn(); + const result = resolveRailwayTokenFromConfig(cfg, { warn }); + expect(result).toBe("top-padded-aaaaaaaaaaaaaaaaaaaaaaaa"); + expect(warn).not.toHaveBeenCalled(); + }); + + it("returns the trimmed token for legacy user.token (with warn)", () => { + const cfg: RailwayConfigShape = { + user: { token: " legacy-user-token-aaaaaaaaaa\n" }, + }; + const warn = vi.fn(); + const result = resolveRailwayTokenFromConfig(cfg, { warn }); + expect(result).toBe("legacy-user-token-aaaaaaaaaa"); + expect(warn).toHaveBeenCalledTimes(1); + }); + + it("returns the trimmed token for legacy top-level token (with warn)", () => { + const cfg: RailwayConfigShape = { token: "\nlegacy-top-aaaaaaaaa " }; + const warn = vi.fn(); + const result = resolveRailwayTokenFromConfig(cfg, { warn }); + expect(result).toBe("legacy-top-aaaaaaaaa"); + expect(warn).toHaveBeenCalledTimes(1); + }); + + it("returns undefined when config is an array", () => { + const warn = vi.fn(); + const result = resolveRailwayTokenFromConfig( + [] as unknown as RailwayConfigShape, + { warn }, + ); + expect(result).toBeUndefined(); + expect(warn).not.toHaveBeenCalled(); + }); +}); diff --git a/showcase/scripts/lib/build-outputs.ts b/showcase/scripts/lib/build-outputs.ts new file mode 100644 index 00000000000..89ffd8a55a5 --- /dev/null +++ b/showcase/scripts/lib/build-outputs.ts @@ -0,0 +1,186 @@ +/** + * build-outputs.ts — Parse + merge the structured per-slot build results + * emitted by `showcase_build.yml`. Each matrix slot in the build job + * uploads a per-slot artifact named `build-result-` + * containing a single `result.json` payload of the shape + * `{service: "", status: "success"|"failure"|"skipped"}`. + * The aggregate-build-results job downloads every `build-result-*` + * artifact and merges the payloads via `mergeBuildResultFiles` below; + * the resulting array is uploaded as the canonical `build-results` + * artifact for cross-workflow consumption. The deploy workflow (and the + * redeploy guard) read this list instead of parsing job names. + * + * NOTE: the "single result.json per slot" invariant is enforced + * workflow-side (each matrix slot writes exactly one file before + * uploading its artifact); this module assumes that contract and + * validates only the parsed payload shape, not the filesystem layout. + */ + +// Single source of truth for the set of valid build outcomes. The +// `as const` tuple drives BOTH the runtime `VALID_STATUSES` set AND +// the compile-time `BuildOutcome` union (derived via indexed access +// below), so the tuple is the only place a status needs to be added. +// Since `BuildOutcome` is derived from this tuple there is no separate +// union that could drift out of sync — no redundant exhaustiveness +// assertion is needed. +const BUILD_OUTCOMES = ["success", "failure", "skipped"] as const; + +export type BuildOutcome = (typeof BUILD_OUTCOMES)[number]; + +const VALID_STATUSES: ReadonlySet = new Set(BUILD_OUTCOMES); + +export interface ServiceBuildResult { + service: string; + status: BuildOutcome; +} + +function isNonBlankString(value: unknown): value is string { + return typeof value === "string" && value.trim().length > 0; +} + +/** + * Shared validator for a single `{service, status}` payload. Used by + * both `parseBuildOutputs` (per array entry) and `mergeBuildResultFiles` + * (per slot payload) so validation rules + error wording live in one + * place. `contextLabel` is prefixed to every error message — callers + * pass something like `"parseBuildOutputs entry[3]"` or + * `"mergeBuildResultFiles slot[2]"` so the failure points at the + * offending row. + */ +function validateServiceBuildResult( + raw: unknown, + contextLabel: string, +): ServiceBuildResult { + if (typeof raw !== "object" || raw === null || Array.isArray(raw)) { + throw new Error( + `${contextLabel}: expected object with {service, status}, got ${JSON.stringify(raw)}`, + ); + } + const service = (raw as { service?: unknown }).service; + if (typeof service !== "string") { + throw new Error( + `${contextLabel}: missing required string field "service": ${JSON.stringify(raw)}`, + ); + } + const trimmedService = service.trim(); + if (trimmedService.length === 0) { + throw new Error( + `${contextLabel}: field "service" must be a non-empty, non-whitespace string: ${JSON.stringify(raw)}`, + ); + } + const status = (raw as { status?: unknown }).status; + if ( + typeof status !== "string" || + !VALID_STATUSES.has(status as BuildOutcome) + ) { + throw new Error( + `${contextLabel}: invalid "status" (must be success|failure|skipped): ${JSON.stringify(raw)}`, + ); + } + return { service: trimmedService, status: status as BuildOutcome }; +} + +export function parseBuildOutputs(raw: string): ServiceBuildResult[] { + let parsed: unknown; + try { + parsed = JSON.parse(raw); + } catch (e) { + throw new Error( + `Failed to parse build outputs JSON: ${ + e instanceof Error ? e.message : String(e) + }`, + { cause: e }, + ); + } + if (!Array.isArray(parsed)) { + throw new Error("Build outputs must be a JSON array"); + } + return parsed.map((entry, idx) => + validateServiceBuildResult(entry, `parseBuildOutputs entry[${idx}]`), + ); +} + +export function successSet(results: ServiceBuildResult[]): string[] { + return results.filter((r) => r.status === "success").map((r) => r.service); +} + +/** + * Canonical artifact-name convention for the per-slot build-result + * handoff. Each matrix slot in showcase_build.yml uploads exactly one + * artifact named `build-result-` containing a single + * `result.json` file. The aggregator job downloads every artifact + * matching the `build-result-*` pattern and merges them via + * mergeBuildResultFiles below. We refuse empty/whitespace service + * names so the per-slot artifact cannot collide with the aggregated + * `build-results` artifact published downstream. + */ +export function buildResultArtifactName(service: string): string { + if (!isNonBlankString(service)) { + throw new Error( + "buildResultArtifactName: `service` must be a non-empty, non-whitespace string", + ); + } + return `build-result-${service}`; +} + +/** + * Merge a list of per-slot result.json payloads (raw strings, one per + * matrix slot's uploaded artifact) into a single ServiceBuildResult[]. + * Each payload MUST be a JSON object with `service: string` and + * `status: success|failure|skipped`. The merge is order-preserving so + * downstream consumers can rely on stable iteration. + * + * Fails loud on duplicate `service` names across slots: a duplicate + * means an upstream dispatch-name collision (two slots claiming the + * same service), which would let a `failure` + `success` pair for the + * same service spuriously look like a success in `successSet`. We + * surface the collision instead of silently deduping. + */ +export function mergeBuildResultFiles( + slotPayloads: readonly string[], +): ServiceBuildResult[] { + const merged = slotPayloads.map((raw, idx) => { + let parsed: unknown; + try { + parsed = JSON.parse(raw); + } catch (e) { + throw new Error( + `mergeBuildResultFiles slot[${idx}]: not valid JSON: ${ + e instanceof Error ? e.message : String(e) + }`, + { cause: e }, + ); + } + return validateServiceBuildResult( + parsed, + `mergeBuildResultFiles slot[${idx}]`, + ); + }); + + const seen = new Set(); + const duplicates = new Set(); + for (const { service } of merged) { + if (seen.has(service)) { + duplicates.add(service); + } else { + seen.add(service); + } + } + if (duplicates.size > 0) { + const names = Array.from(duplicates).sort().join(", "); + throw new Error( + `mergeBuildResultFiles: duplicate service name(s) across slots: ${names}`, + ); + } + return merged; +} + +/** + * Returns true iff at least one service in the build set finished as + * `success`. Gates redeploy: when no service succeeded, redeploy MUST + * be skipped so we do not re-pull the stale `:latest` and silently + * look healthy. + */ +export function shouldRedeployStaging(results: ServiceBuildResult[]): boolean { + return results.some((r) => r.status === "success"); +} diff --git a/showcase/scripts/lib/railway-graphql.ts b/showcase/scripts/lib/railway-graphql.ts new file mode 100644 index 00000000000..b52e8c4e35d --- /dev/null +++ b/showcase/scripts/lib/railway-graphql.ts @@ -0,0 +1,51 @@ +/** + * railway-graphql.ts — Single source of truth for the Railway GraphQL + * endpoint host for TypeScript importers in this repo. The historic + * `.com` host (`backboard.railway.com`) is unauthenticated for the + * public GraphQL API and silently returns 401/403; the canonical host + * is `backboard.railway.app`. + * + * Note: the Ruby `bin/railway` script and the inline `curl` commands + * embedded in `.github/workflows/*.yml` hold their OWN copies of this + * URL because they cannot import a TypeScript module. Those copies are + * kept in sync by hand and enforced by the regression guard in + * `./__tests__/railway-graphql.scan.test.ts`, which fails the build if + * any source file under `showcase/` or `.github/workflows/` reintroduces + * the `.com` host. + */ +export const RAILWAY_GRAPHQL_ENDPOINT = + "https://backboard.railway.app/graphql/v2" as const; + +// Fail-fast at module load if a hand-edit ever breaks the URL literal. +new URL(RAILWAY_GRAPHQL_ENDPOINT); + +/** Default cap for sanitizeErrorBody. Multi-KB Cloudflare WAF HTML + * pages would otherwise spam stderr / $GITHUB_STEP_SUMMARY. */ +export const RAILWAY_ERROR_BODY_MAX_DEFAULT = 200; + +/** + * Sanitize a Railway API error body for inclusion in logs / the + * markdown summary. Railway/Cloudflare error responses can be + * multi-KB HTML pages: + * + * - strip `<` and `>` (would break markdown tables) + * - strip control chars `\n`, `\r`, `\t` (would break single-line + * log records AND newline-bearing markdown rows in redeploy-env) + * - cap at `max` chars (default 200) with an ellipsis on overflow + * + * Shared between redeploy-env.ts and verify-railway-image-refs.ts so + * both consumers strip control chars at the source. + */ +export function sanitizeErrorBody( + body: string, + max: number = RAILWAY_ERROR_BODY_MAX_DEFAULT, +): string { + // Strip angle brackets (markdown-breaking) and control chars + // (newline/carriage-return/tab — break single-line log records + // and the markdown row redeploy-env produces). Original behavior + // removed `<>` without substitution; preserve that for `<>` and + // additionally remove `\n\r\t`. + const stripped = body.replace(/[<>\n\r\t]/g, ""); + if (stripped.length <= max) return stripped; + return stripped.slice(0, max) + "…"; +} diff --git a/showcase/scripts/lib/railway-token.ts b/showcase/scripts/lib/railway-token.ts new file mode 100644 index 00000000000..f0370dc1307 --- /dev/null +++ b/showcase/scripts/lib/railway-token.ts @@ -0,0 +1,211 @@ +import fs from "node:fs"; +import path from "node:path"; + +/** + * railway-token.ts — Shared resolver for the Railway GraphQL bearer. + * + * The Railway CLI stores the public-GraphQL bearer in `user.accessToken`. + * The shorter `user.token` is a legacy CLI session token that does NOT + * authenticate to the public GraphQL API. Older configs still on disk + * have `user.token` set and `user.accessToken` empty; those callers get + * a one-cycle deprecation warning and still work. + * + * Resolution order matches the first four candidates of + * `showcase/bin/railway` `Auth.token`; the per-project + * `projects..token` fallback is intentionally not honored (no + * project-scoped tokens here): + * 1. user.accessToken + * 2. accessToken (top-level) + * 3. user.token (legacy → warn) + * 4. token (top-level) (legacy → warn) + * + * Returns undefined when no usable token is present; callers print the + * "set RAILWAY_TOKEN or run `railway login`" error. + * + * This resolver reads ONLY the parsed config object passed in and does + * NOT consult `process.env.RAILWAY_TOKEN` — the caller is responsible + * for the environment-variable lane. Any returned value is trimmed so + * stray whitespace/newlines from `~/.railway/config.json` never reach + * an `Authorization: Bearer ` header. + */ + +export interface RailwayConfigShape { + user?: { + accessToken?: string; + token?: string; + }; + accessToken?: string; + token?: string; +} + +export interface ResolverDeps { + warn?: (message: string) => void; +} + +const DEPRECATION_MESSAGE = + "[railway-token] WARNING: legacy Railway config field is deprecated: " + + "`user.token` / top-level `token` no longer authenticates the public " + + "GraphQL API. The Railway CLI now writes `user.accessToken`; re-run " + + "`railway login` to refresh ~/.railway/config.json. Support for the " + + "legacy field will be removed in a future release."; + +function nonEmpty(v: unknown): v is string { + return typeof v === "string" && v.trim().length > 0; +} + +export function resolveRailwayTokenFromConfig( + config: RailwayConfigShape | null | undefined, + deps: ResolverDeps = {}, +): string | undefined { + // Defensive guard: config originates from JSON.parse of + // ~/.railway/config.json (untrusted). Reject anything that isn't a + // plain object before property access. + if (config === null || config === undefined) return undefined; + if (typeof config !== "object") return undefined; + if (Array.isArray(config)) return undefined; + + const warn = deps.warn ?? ((m: string) => console.warn(m)); + + const userAccess = config.user?.accessToken; + if (nonEmpty(userAccess)) return userAccess.trim(); + + const topAccess = config.accessToken; + if (nonEmpty(topAccess)) return topAccess.trim(); + + const userLegacy = config.user?.token; + if (nonEmpty(userLegacy)) { + warn(DEPRECATION_MESSAGE); + return userLegacy.trim(); + } + + const topLegacy = config.token; + if (nonEmpty(topLegacy)) { + warn(DEPRECATION_MESSAGE); + return topLegacy.trim(); + } + return undefined; +} + +/** + * Failure-mode codes for resolveRailwayToken. Each is a distinct, + * actionable diagnostic so callers (and operators reading CI logs) can + * tell exactly WHY token resolution failed: + * + * NO_HOME : $HOME is unset (so ~/.railway/config.json can't + * be located) AND RAILWAY_TOKEN is also unset. + * NO_FILE : $HOME is set but ~/.railway/config.json does + * not exist (and env-var is unset). + * MALFORMED : ~/.railway/config.json exists but JSON.parse + * threw. + * NO_TOKEN_IN_CONFIG : ~/.railway/config.json exists and parses OK + * but contains no usable token at any of the + * four known layers. (Closes the silent-token- + * fallthrough diagnostic gap where the operator + * previously saw the generic "No Railway token + * found" with no hint the file was inspected.) + */ +export type RailwayTokenErrorCode = + | "NO_HOME" + | "NO_FILE" + | "MALFORMED" + | "NO_TOKEN_IN_CONFIG"; + +export class RailwayTokenError extends Error { + readonly code: RailwayTokenErrorCode; + constructor(code: RailwayTokenErrorCode, message: string) { + super(message); + this.name = "RailwayTokenError"; + this.code = code; + } +} + +export interface ResolveRailwayTokenOptions extends ResolverDeps { + /** Override $HOME lookup (testing only). */ + home?: string; + /** Override env-var lookup (testing only). */ + env?: NodeJS.ProcessEnv; + /** Filesystem injection (testing only). */ + fs?: Pick; +} + +export interface RailwayTokenResolution { + token: string; + source: "env" | "config"; +} + +/** + * Unified entrypoint shared by redeploy-env.ts and + * verify-railway-image-refs.ts. Encapsulates the previously-duplicated + * getToken() envelope so the four failure modes can have distinct, + * actionable diagnostics in one place. + * + * Resolution order: + * 1. process.env.RAILWAY_TOKEN (returned with source="env") + * 2. ~/.railway/config.json via resolveRailwayTokenFromConfig + * (returned with source="config") + * + * Throws RailwayTokenError with a discriminator `.code` for each failure + * mode (NO_HOME / NO_FILE / MALFORMED / NO_TOKEN_IN_CONFIG). NEVER calls + * process.exit — the script entrypoint is responsible for mapping the + * error to a non-zero exit code so this function stays unit-testable. + */ +export function resolveRailwayToken( + opts: ResolveRailwayTokenOptions = {}, +): RailwayTokenResolution { + const env = opts.env ?? process.env; + const fsImpl = opts.fs ?? fs; + + // Trim the env-var lane to honor the module's no-whitespace-in-header + // invariant. A `RAILWAY_TOKEN` secret with a trailing newline (common + // from `op read`/heredoc/shell export) would otherwise be returned + // verbatim and produce an invalid `Authorization: Bearer \n` + // header → silent Railway 401. A whitespace-only value is treated as + // UNSET (falls through to the config-file lane). + const envToken = env.RAILWAY_TOKEN; + if (typeof envToken === "string") { + const trimmed = envToken.trim(); + if (trimmed.length > 0) { + return { token: trimmed, source: "env" }; + } + } + + const home = opts.home ?? env.HOME; + if (!home) { + throw new RailwayTokenError( + "NO_HOME", + "No Railway token found. RAILWAY_TOKEN is unset (or whitespace-only) and $HOME is unset so ~/.railway/config.json cannot be located.", + ); + } + + const configPath = path.join(home, ".railway", "config.json"); + if (!fsImpl.existsSync(configPath)) { + throw new RailwayTokenError( + "NO_FILE", + "No Railway token found. Set RAILWAY_TOKEN or run `railway login`.", + ); + } + + let parsed: unknown; + try { + parsed = JSON.parse(fsImpl.readFileSync(configPath, "utf-8")); + } catch (e) { + const msg = e instanceof Error ? e.message : String(e); + throw new RailwayTokenError( + "MALFORMED", + `Malformed ~/.railway/config.json: ${msg}`, + ); + } + + const token = resolveRailwayTokenFromConfig( + parsed as RailwayConfigShape | null | undefined, + opts, + ); + if (typeof token === "string" && token.length > 0) { + return { token, source: "config" }; + } + + throw new RailwayTokenError( + "NO_TOKEN_IN_CONFIG", + "No Railway token found: ~/.railway/config.json was found and parsed but contains no usable token (user.accessToken / accessToken / user.token / token). Set RAILWAY_TOKEN or re-run `railway login`.", + ); +} diff --git a/showcase/scripts/railway-envs.generated.json b/showcase/scripts/railway-envs.generated.json new file mode 100644 index 00000000000..94bfdf22d71 --- /dev/null +++ b/showcase/scripts/railway-envs.generated.json @@ -0,0 +1,526 @@ +{ + "projectId": "6f8c6bff-a80d-4f8f-b78d-50b32bcf4479", + "envIds": { + "staging": "8edfef02-ea09-4a20-8689-261f21cc2849", + "prod": "b14919f4-6417-429f-848d-c6ae2201e04f" + }, + "services": [ + { + "name": "aimock", + "serviceId": "0fa0435d-8a66-46f0-84fd-e4250b580013", + "prodInstanceId": "5801d8be-5ad9-4eff-9c9c-7be61d9a023e", + "stagingInstanceId": "9f260dfd-d9d4-43e9-98fe-49696f87fe50", + "ciBuilt": true, + "gateValidated": true, + "dispatchName": "showcase-aimock", + "repoNameOverride": { + "prod": "showcase-aimock", + "staging": "showcase-aimock" + }, + "domains": { + "staging": "aimock-staging.up.railway.app", + "prod": "showcase-aimock-production.up.railway.app" + }, + "probe": { + "staging": true, + "prod": true, + "driver": "aimock" + } + }, + { + "name": "dashboard", + "serviceId": "4d5dfd74-be61-40b2-8564-b53b7dd4c15b", + "prodInstanceId": "e68f98fa-b2ef-41cc-82f6-2ed6f9533bf3", + "stagingInstanceId": "aea7332e-17a0-4fab-921c-ed5baad2a6f2", + "ciBuilt": true, + "gateValidated": true, + "dispatchName": "shell-dashboard", + "repoNameOverride": { + "prod": "showcase-shell-dashboard", + "staging": "showcase-shell-dashboard" + }, + "domains": { + "staging": "dashboard.showcase.staging.copilotkit.ai", + "prod": "dashboard.showcase.copilotkit.ai" + }, + "probe": { + "staging": true, + "prod": true, + "driver": "dashboard" + } + }, + { + "name": "docs", + "serviceId": "7badfb8d-4228-414c-9145-b4026803714f", + "prodInstanceId": "b15564fc-f832-49b3-82df-fd36f298fe96", + "stagingInstanceId": "d5caa51d-73ee-4669-bfea-d87bf1488b02", + "ciBuilt": true, + "gateValidated": true, + "dispatchName": "shell-docs", + "repoNameOverride": { + "prod": "showcase-shell-docs", + "staging": "showcase-shell-docs" + }, + "domains": { + "staging": "docs.staging.copilotkit.ai", + "prod": "docs.copilotkit.ai" + }, + "probe": { + "staging": true, + "prod": true, + "driver": "docs" + } + }, + { + "name": "dojo", + "serviceId": "7ad1ece7-2228-49cd-8a78-bddf30322907", + "prodInstanceId": "2ee4f2aa-11ec-4426-9a4a-41a1ad04f16d", + "stagingInstanceId": "1284d717-0ff5-432c-9326-fab12661df61", + "ciBuilt": true, + "gateValidated": true, + "dispatchName": "shell-dojo", + "repoNameOverride": { + "prod": "showcase-shell-dojo", + "staging": "showcase-shell-dojo" + }, + "domains": { + "staging": "dojo.showcase.staging.copilotkit.ai", + "prod": "dojo.showcase.copilotkit.ai" + }, + "probe": { + "staging": true, + "prod": true, + "driver": "dojo" + } + }, + { + "name": "harness", + "serviceId": "3a14bfed-0537-4d71-897b-7c593dca161d", + "prodInstanceId": "05fbcdf2-8a50-4b71-b4f6-c92c4b17e626", + "stagingInstanceId": "0811f68f-fac4-440e-a350-3a7ca5855b80", + "ciBuilt": true, + "gateValidated": true, + "dispatchName": "showcase-harness", + "repoNameOverride": { + "prod": "showcase-harness", + "staging": "showcase-harness" + }, + "domains": { + "staging": "harness-staging-2ee4.up.railway.app", + "prod": "showcase-harness-production.up.railway.app" + }, + "probe": { + "staging": true, + "prod": true, + "driver": "harness" + } + }, + { + "name": "pocketbase", + "serviceId": "ba11e854-d695-4738-9a45-2b0776788824", + "prodInstanceId": "1ee376e2-13f2-4464-801e-d0aa0bf76532", + "stagingInstanceId": "0bc7db7b-5a43-4b33-af46-d07fb53c8610", + "ciBuilt": false, + "gateValidated": true, + "repoNameOverride": { + "prod": "showcase-pocketbase", + "staging": "showcase-pocketbase" + }, + "domains": { + "staging": "pocketbase-staging-eec0.up.railway.app", + "prod": "showcase-pocketbase-production.up.railway.app" + }, + "probe": { + "staging": true, + "prod": true, + "driver": "pocketbase" + } + }, + { + "name": "shell", + "serviceId": "40eea0da-6071-4ea8-bdb9-39afb19225ec", + "prodInstanceId": "01614ccf-e109-4b30-b41b-7c5551c0a34c", + "stagingInstanceId": "25b7de41-188c-4f2e-ac07-538212eaeb91", + "ciBuilt": true, + "gateValidated": true, + "dispatchName": "shell", + "repoNameOverride": { + "prod": "showcase-shell", + "staging": "showcase-shell" + }, + "domains": { + "staging": "showcase.staging.copilotkit.ai", + "prod": "showcase.copilotkit.ai" + }, + "probe": { + "staging": true, + "prod": true, + "driver": "shell" + } + }, + { + "name": "showcase-ag2", + "serviceId": "4a37481b-f264-4eb7-a9cd-0a9ebb9ac05c", + "prodInstanceId": "de571c97-03fd-486b-8a54-9767a4a53f95", + "stagingInstanceId": "ecaf81b3-93a8-4862-92b6-04a016b634ed", + "ciBuilt": true, + "gateValidated": true, + "dispatchName": "ag2", + "domains": { + "staging": "showcase-ag2-staging.up.railway.app", + "prod": "showcase-ag2-production.up.railway.app" + }, + "probe": { + "staging": true, + "prod": true, + "driver": "agent" + } + }, + { + "name": "showcase-agno", + "serviceId": "32cab80b-e329-45bd-9c73-c4e1ddc94305", + "prodInstanceId": "026d12fb-2844-42af-8f92-b47bc8a06bc8", + "stagingInstanceId": "68964ab6-75ca-4095-a64a-52cacfb684f5", + "ciBuilt": true, + "gateValidated": true, + "dispatchName": "agno", + "domains": { + "staging": "showcase-agno-staging.up.railway.app", + "prod": "showcase-agno-production.up.railway.app" + }, + "probe": { + "staging": true, + "prod": true, + "driver": "agent" + } + }, + { + "name": "showcase-built-in-agent", + "serviceId": "f4f8371a-bc46-45b2-b6d4-9c9af608bdbf", + "prodInstanceId": "40018ef7-1ed1-4979-b80c-9c2d957b6d88", + "stagingInstanceId": "b89ae7b3-01cc-4ed4-aca6-23aaa63cd59e", + "ciBuilt": true, + "gateValidated": true, + "dispatchName": "built-in-agent", + "domains": { + "staging": "showcase-built-in-agent-staging.up.railway.app", + "prod": "showcase-built-in-agent-production.up.railway.app" + }, + "probe": { + "staging": true, + "prod": true, + "driver": "agent" + } + }, + { + "name": "showcase-claude-sdk-python", + "serviceId": "b122ab65-9854-4cb2-a68e-b50ff13f7481", + "prodInstanceId": "bb18caaf-9a3e-4fdd-85ec-562fd82a3a89", + "stagingInstanceId": "1ef25aec-5fbd-40b9-8685-57c2681bd45d", + "ciBuilt": true, + "gateValidated": true, + "dispatchName": "claude-sdk-python", + "domains": { + "staging": "showcase-claude-sdk-python-staging.up.railway.app", + "prod": "showcase-claude-sdk-python-production.up.railway.app" + }, + "probe": { + "staging": true, + "prod": true, + "driver": "agent" + } + }, + { + "name": "showcase-claude-sdk-typescript", + "serviceId": "18a98727-5700-44aa-b497-b60795dbbd6a", + "prodInstanceId": "bee425e4-9661-4a88-8888-922b8cd4b61d", + "stagingInstanceId": "92305747-2f55-4122-aad4-882e989558ab", + "ciBuilt": true, + "gateValidated": true, + "dispatchName": "claude-sdk-typescript", + "domains": { + "staging": "showcase-claude-sdk-typescript-staging.up.railway.app", + "prod": "showcase-claude-sdk-typescript-production.up.railway.app" + }, + "probe": { + "staging": true, + "prod": true, + "driver": "agent" + } + }, + { + "name": "showcase-crewai-crews", + "serviceId": "0e9c284d-8d87-4fcf-9f82-6b704d7e4bd4", + "prodInstanceId": "3dab0cc3-cab1-4579-b772-947268088514", + "stagingInstanceId": "88c2a14f-435b-499e-a811-ee4f4be18fd8", + "ciBuilt": true, + "gateValidated": true, + "dispatchName": "crewai-crews", + "domains": { + "staging": "showcase-crewai-crews-staging.up.railway.app", + "prod": "showcase-crewai-crews-production.up.railway.app" + }, + "probe": { + "staging": true, + "prod": true, + "driver": "agent" + } + }, + { + "name": "showcase-google-adk", + "serviceId": "87f60507-5a3d-4b8a-9e23-2b1de85d939c", + "prodInstanceId": "7b2da5db-87d2-40ad-a3d9-b2d7a5485a22", + "stagingInstanceId": "7efe2fa0-fa78-4585-bc4c-6d39c326e6d1", + "ciBuilt": true, + "gateValidated": true, + "dispatchName": "google-adk", + "domains": { + "staging": "showcase-google-adk-staging.up.railway.app", + "prod": "showcase-google-adk-production.up.railway.app" + }, + "probe": { + "staging": true, + "prod": true, + "driver": "agent" + } + }, + { + "name": "showcase-langgraph-fastapi", + "serviceId": "06cccb5c-59f4-46b5-8adc-7113e77011a4", + "prodInstanceId": "105b7e01-acd0-48e2-9a09-541e2103e8d2", + "stagingInstanceId": "7899afe0-141b-4217-8dbb-5907813231dc", + "ciBuilt": true, + "gateValidated": true, + "dispatchName": "langgraph-fastapi", + "domains": { + "staging": "showcase-langgraph-fastapi-staging.up.railway.app", + "prod": "showcase-langgraph-fastapi-production.up.railway.app" + }, + "probe": { + "staging": true, + "prod": true, + "driver": "agent" + } + }, + { + "name": "showcase-langgraph-python", + "serviceId": "90d03214-4569-41b0-b4c1-6438a8a7b203", + "prodInstanceId": "aec504f7-63d7-4ea6-9d50-601b00d2ae80", + "stagingInstanceId": "04d29664-a776-4670-9db3-b1d18bce1669", + "ciBuilt": true, + "gateValidated": true, + "dispatchName": "langgraph-python", + "domains": { + "staging": "showcase-langgraph-python-staging.up.railway.app", + "prod": "showcase-langgraph-python-production.up.railway.app" + }, + "probe": { + "staging": true, + "prod": true, + "driver": "agent" + } + }, + { + "name": "showcase-langgraph-typescript", + "serviceId": "66246d3b-a18e-46f0-be51-5f3ff7a36e5a", + "prodInstanceId": "f53e9fdc-7c3e-4dfd-9fa8-d7241fd55bb8", + "stagingInstanceId": "481ab37f-da8a-4015-bd88-2b28d9eb261a", + "ciBuilt": true, + "gateValidated": true, + "dispatchName": "langgraph-typescript", + "domains": { + "staging": "showcase-langgraph-typescript-staging.up.railway.app", + "prod": "showcase-langgraph-typescript-production.up.railway.app" + }, + "probe": { + "staging": true, + "prod": true, + "driver": "agent" + } + }, + { + "name": "showcase-langroid", + "serviceId": "6dd9cb0a-66cc-46f1-972e-7cd74756157d", + "prodInstanceId": "6b5e20b5-8f8e-4ec3-9288-7a41122e42e5", + "stagingInstanceId": "a213f7d9-2117-4944-988b-05e68d819dd5", + "ciBuilt": true, + "gateValidated": true, + "dispatchName": "langroid", + "domains": { + "staging": "showcase-langroid-staging.up.railway.app", + "prod": "showcase-langroid-production.up.railway.app" + }, + "probe": { + "staging": true, + "prod": true, + "driver": "agent" + } + }, + { + "name": "showcase-llamaindex", + "serviceId": "285386e8-492d-4cb8-b632-0a7d4607378f", + "prodInstanceId": "b778856e-9f90-4136-9415-fb2b41173f8d", + "stagingInstanceId": "17899ea7-355c-43f2-a152-28cb0b7fa864", + "ciBuilt": true, + "gateValidated": true, + "dispatchName": "llamaindex", + "domains": { + "staging": "showcase-llamaindex-staging.up.railway.app", + "prod": "showcase-llamaindex-production.up.railway.app" + }, + "probe": { + "staging": true, + "prod": true, + "driver": "agent" + } + }, + { + "name": "showcase-mastra", + "serviceId": "d7979eb7-2405-4aab-ad21-438f4a1b08af", + "prodInstanceId": "eaeddd9c-8b75-426f-b033-0fd935cbf6ef", + "stagingInstanceId": "eec22411-aab5-47a1-8f5b-d097e233d7f8", + "ciBuilt": true, + "gateValidated": true, + "dispatchName": "mastra", + "domains": { + "staging": "showcase-mastra-staging.up.railway.app", + "prod": "showcase-mastra-production.up.railway.app" + }, + "probe": { + "staging": true, + "prod": true, + "driver": "agent" + } + }, + { + "name": "showcase-ms-agent-dotnet", + "serviceId": "beeb2dd6-87a4-4599-aa07-0578f7bd6519", + "prodInstanceId": "93ca0edf-7b59-4de4-b1fd-3412bb07bc6a", + "stagingInstanceId": "9826bc58-c472-41e6-b050-29249d4b2a52", + "ciBuilt": true, + "gateValidated": true, + "dispatchName": "ms-agent-dotnet", + "domains": { + "staging": "showcase-ms-agent-dotnet-staging.up.railway.app", + "prod": "showcase-ms-agent-dotnet-production.up.railway.app" + }, + "probe": { + "staging": true, + "prod": true, + "driver": "agent" + } + }, + { + "name": "showcase-ms-agent-harness-dotnet", + "serviceId": "6343d7f9-6c3f-4c8d-9a6e-79f03d2f1e37", + "prodInstanceId": "8f91ebc6-95c0-4433-b1f7-657ff49c2d59", + "stagingInstanceId": "6b0fe181-9156-4a40-9e44-90befe09833a", + "ciBuilt": true, + "gateValidated": true, + "dispatchName": "ms-agent-harness-dotnet", + "domains": { + "staging": "showcase-ms-agent-harness-dotnet-staging.up.railway.app", + "prod": "showcase-ms-agent-harness-dotnet-production.up.railway.app" + }, + "probe": { + "staging": true, + "prod": true, + "driver": "agent" + } + }, + { + "name": "showcase-ms-agent-python", + "serviceId": "655db75a-af8d-427d-a4f9-441570ae5003", + "prodInstanceId": "323ed911-4d28-45ab-8fc0-7d151828b938", + "stagingInstanceId": "741725ce-5fa1-4327-aff5-53dcc000c29c", + "ciBuilt": true, + "gateValidated": true, + "dispatchName": "ms-agent-python", + "domains": { + "staging": "showcase-ms-agent-python-staging.up.railway.app", + "prod": "showcase-ms-agent-python-production.up.railway.app" + }, + "probe": { + "staging": true, + "prod": true, + "driver": "agent" + } + }, + { + "name": "showcase-pydantic-ai", + "serviceId": "0a106173-2282-4887-a994-0ca276a99d69", + "prodInstanceId": "192cd647-6824-4f01-937a-1da675d83805", + "stagingInstanceId": "6edf5ca5-6a56-4d28-92c3-2a3360c735db", + "ciBuilt": true, + "gateValidated": true, + "dispatchName": "pydantic-ai", + "domains": { + "staging": "showcase-pydantic-ai-staging.up.railway.app", + "prod": "showcase-pydantic-ai-production.up.railway.app" + }, + "probe": { + "staging": true, + "prod": true, + "driver": "agent" + } + }, + { + "name": "showcase-spring-ai", + "serviceId": "eed5d041-91be-4282-b414-beea00843401", + "prodInstanceId": "2fbf1db2-5e51-44c9-983c-3f2242d95c61", + "stagingInstanceId": "189ac76f-bd77-45c0-9c45-3853dae763cc", + "ciBuilt": true, + "gateValidated": true, + "dispatchName": "spring-ai", + "domains": { + "staging": "showcase-spring-ai-staging.up.railway.app", + "prod": "showcase-spring-ai-production.up.railway.app" + }, + "probe": { + "staging": true, + "prod": true, + "driver": "agent" + } + }, + { + "name": "showcase-strands", + "serviceId": "92e1cfad-ad53-403f-ab2b-5ab380832232", + "prodInstanceId": "2123c71b-9385-443c-a1c3-bcf4b1669eeb", + "stagingInstanceId": "f8a9d2ed-50ec-4f06-85d6-230baced8471", + "ciBuilt": true, + "gateValidated": true, + "dispatchName": "strands", + "domains": { + "staging": "showcase-strands-staging.up.railway.app", + "prod": "showcase-strands-production.up.railway.app" + }, + "probe": { + "staging": true, + "prod": true, + "driver": "agent" + } + }, + { + "name": "webhooks", + "serviceId": "ba6acc13-7585-41fe-a5ee-585b34a58fcd", + "prodInstanceId": "d82ef5b4-3bfd-462e-9436-3d5dbca8681a", + "stagingInstanceId": "450e87e0-aba5-4aba-afaf-15f4deab03f0", + "ciBuilt": false, + "gateValidated": true, + "dispatchName": "webhooks", + "repoNameOverride": { + "prod": "showcase-eval-webhook", + "staging": "showcase-eval-webhook" + }, + "domains": { + "staging": "hooks.showcase.staging.copilotkit.ai", + "prod": "hooks.showcase.copilotkit.ai" + }, + "probe": { + "staging": true, + "prod": true, + "driver": "webhooks" + } + } + ] +} diff --git a/showcase/scripts/railway-envs.test.ts b/showcase/scripts/railway-envs.test.ts new file mode 100644 index 00000000000..44759363b73 --- /dev/null +++ b/showcase/scripts/railway-envs.test.ts @@ -0,0 +1,492 @@ +import { readFileSync } from "node:fs"; +import { resolve } from "node:path"; +import { describe, expect, it } from "vitest"; +import { + CI_BUILT_SERVICES, + ENV_IDS, + PRODUCTION_ENV_ID, + PROJECT_ID, + SERVICES, + STAGING_ENV_ID, + assertDispatchNamesUnique, + domainFor, + instanceIdFor, + listServiceNames, + repoNameFor, + resolveEnv, + serviceForDispatchName, +} from "./railway-envs"; +import type { Domains, ProbeConfig, ProbeDriver } from "./railway-envs"; + +// Compile-time guard: ensure the Domains type alias is referenced so this +// import is not removed by an over-eager organizer. The runtime body of +// this assignment is unused; it exists solely to anchor the type. +const _domainsTypeAnchor: Domains | undefined = undefined; +void _domainsTypeAnchor; + +describe("railway-envs SSOT", () => { + it("exposes the canonical project id", () => { + expect(PROJECT_ID).toBe("6f8c6bff-a80d-4f8f-b78d-50b32bcf4479"); + }); + + it("exposes both env ids and they differ", () => { + expect(PRODUCTION_ENV_ID).toMatch(/^[0-9a-f-]{36}$/); + expect(STAGING_ENV_ID).toMatch(/^[0-9a-f-]{36}$/); + expect(PRODUCTION_ENV_ID).not.toBe(STAGING_ENV_ID); + }); + + it("resolves env synonyms", () => { + expect(resolveEnv("prod").envId).toBe(PRODUCTION_ENV_ID); + expect(resolveEnv("production").envId).toBe(PRODUCTION_ENV_ID); + expect(resolveEnv("PROD").envId).toBe(PRODUCTION_ENV_ID); + expect(resolveEnv("staging").envId).toBe(STAGING_ENV_ID); + expect(resolveEnv(" Staging ").envId).toBe(STAGING_ENV_ID); + }); + + it("throws on unknown env names", () => { + expect(() => resolveEnv("dev")).toThrow(/Unknown env/); + expect(() => resolveEnv("")).toThrow(/Unknown env/); + }); + + it("ENV_IDS contains all synonyms", () => { + expect(ENV_IDS.prod).toBe(PRODUCTION_ENV_ID); + expect(ENV_IDS.production).toBe(PRODUCTION_ENV_ID); + expect(ENV_IDS.staging).toBe(STAGING_ENV_ID); + }); + + it("contains exactly 27 services", () => { + const names = listServiceNames(); + expect(names.length).toBe(27); + }); + + it("contains the expected canonical services", () => { + const names = listServiceNames(); + // Sample sentinels — checking the full list is overkill, but these + // are the ones we cannot tolerate losing without noticing. + for (const expected of [ + "aimock", + "harness", + "pocketbase", + "shell", + "showcase-ag2", + "showcase-langgraph-python", + "showcase-mastra", + "webhooks", + ]) { + expect(names).toContain(expected); + } + }); + + it("every service has non-empty UUIDs for prod and staging", () => { + const uuid = /^[0-9a-f-]{36}$/; + for (const [name, entry] of Object.entries(SERVICES)) { + expect(entry.serviceId, `${name}.serviceId`).toMatch(uuid); + expect(entry.prodInstanceId, `${name}.prodInstanceId`).toMatch(uuid); + expect(entry.stagingInstanceId, `${name}.stagingInstanceId`).toMatch( + uuid, + ); + } + }); + + it("prod and staging instance IDs differ per service", () => { + for (const [name, entry] of Object.entries(SERVICES)) { + expect( + entry.prodInstanceId, + `${name}: prod and staging instanceIds collided`, + ).not.toBe(entry.stagingInstanceId); + } + }); + + it("aimock has the showcase-aimock wrapper override in BOTH envs (permanent)", () => { + // Both prod and staging run the `showcase-aimock` wrapper image — the + // wrapper bakes showcase fixtures into base aimock and is the permanent + // image for the aimock showcase service. Prod is digest-pinned; staging + // floats :latest. + expect(SERVICES.aimock.repoNameOverride?.prod).toBe("showcase-aimock"); + expect(SERVICES.aimock.repoNameOverride?.staging).toBe("showcase-aimock"); + }); + + it("instanceIdFor returns the right ID per env", () => { + expect(instanceIdFor("showcase-mastra", "prod")).toBe( + "eaeddd9c-8b75-426f-b033-0fd935cbf6ef", + ); + expect(instanceIdFor("showcase-mastra", "staging")).toBe( + "eec22411-aab5-47a1-8f5b-d097e233d7f8", + ); + }); + + it("instanceIdFor throws on unknown service", () => { + expect(() => instanceIdFor("nope", "prod")).toThrow( + /Unknown showcase service/, + ); + }); + + it("CI_BUILT_SERVICES contains exactly 25 services and excludes pocketbase/webhooks", () => { + expect(CI_BUILT_SERVICES.size).toBe(25); + expect(CI_BUILT_SERVICES.has("pocketbase")).toBe(false); + expect(CI_BUILT_SERVICES.has("webhooks")).toBe(false); + // Sample positives. + expect(CI_BUILT_SERVICES.has("showcase-mastra")).toBe(true); + expect(CI_BUILT_SERVICES.has("aimock")).toBe(true); + expect(CI_BUILT_SERVICES.has("dashboard")).toBe(true); + }); + + it("pocketbase and webhooks have per-env GHCR repo-name overrides", () => { + expect(SERVICES.pocketbase.repoNameOverride).toEqual({ + prod: "showcase-pocketbase", + staging: "showcase-pocketbase", + }); + expect(SERVICES.webhooks.repoNameOverride).toEqual({ + prod: "showcase-eval-webhook", + staging: "showcase-eval-webhook", + }); + }); + + it("pocketbase and webhooks are marked ciBuilt=false but gateValidated=true", () => { + expect(SERVICES.pocketbase.ciBuilt).toBe(false); + expect(SERVICES.pocketbase.gateValidated).toBe(true); + expect(SERVICES.webhooks.ciBuilt).toBe(false); + expect(SERVICES.webhooks.gateValidated).toBe(true); + }); + + it("repoNameFor resolves the showcase-aimock wrapper override in BOTH envs", () => { + // Both prod and staging run the `showcase-aimock` wrapper image + // (the fixture-baking wrapper is the permanent, canonical aimock + // showcase image — no migration). Prod is digest-pinned; staging + // floats :latest. The SSOT expresses this via overrides on both envs. + expect(repoNameFor("aimock", "prod")).toBe("showcase-aimock"); + expect(repoNameFor("aimock", "staging")).toBe("showcase-aimock"); + }); + + it("repoNameFor resolves pocketbase/webhooks overrides in BOTH envs", () => { + expect(repoNameFor("pocketbase", "prod")).toBe("showcase-pocketbase"); + expect(repoNameFor("pocketbase", "staging")).toBe("showcase-pocketbase"); + expect(repoNameFor("webhooks", "prod")).toBe("showcase-eval-webhook"); + expect(repoNameFor("webhooks", "staging")).toBe("showcase-eval-webhook"); + }); + + it("repoNameFor returns the service name verbatim when no override exists", () => { + expect(repoNameFor("showcase-mastra", "prod")).toBe("showcase-mastra"); + expect(repoNameFor("showcase-mastra", "staging")).toBe("showcase-mastra"); + }); + + it("serviceForDispatchName round-trips through SSOT keys", () => { + expect(serviceForDispatchName("mastra")).toBe("showcase-mastra"); + expect(serviceForDispatchName("shell-dashboard")).toBe("dashboard"); + expect(serviceForDispatchName("showcase-aimock")).toBe("aimock"); + expect(serviceForDispatchName("shell")).toBe("shell"); + expect(serviceForDispatchName("nonsense")).toBeUndefined(); + }); + + it("every dispatchName resolves back to its own SSOT entry", () => { + for (const [name, entry] of Object.entries(SERVICES)) { + if (entry.dispatchName === undefined) continue; + expect( + serviceForDispatchName(entry.dispatchName), + `${name}.dispatchName="${entry.dispatchName}" did not round-trip`, + ).toBe(name); + } + }); + + it("dispatchName values are unique across SERVICES", () => { + const values = Object.values(SERVICES) + .map((entry) => entry.dispatchName) + .filter((v): v is string => v !== undefined); + expect(new Set(values).size).toBe(values.length); + }); + + it("every showcase_build.yml ALL_SERVICES dispatch_name maps to an SSOT entry (bidirectional for CI-built)", () => { + // Forward-guard: parse the build workflow and assert every matrix + // `dispatch_name` resolves through the SSOT. Without this, a contributor + // can add a matrix entry whose dispatch_name has no SERVICES entry and + // only discover it at redeploy time when `redeploy-env.ts` throws + // `Unknown service` mid-deploy. + // + // Implementation note: ALL_SERVICES is a heredoc-style shell variable + // holding a JSON array INSIDE the YAML — so a YAML parser (we have + // `yaml` as a dep) wouldn't expose dispatch_names at any structural + // position. The robust extraction is a regex over the file text scoped + // to `"dispatch_name":"…"` JSON key/value pairs, which only appear + // inside the ALL_SERVICES JSON literal (the workflow_dispatch options + // list uses unkeyed YAML scalars and is not matched). + const workflowPath = resolve( + __dirname, + "../../.github/workflows/showcase_build.yml", + ); + const yaml = readFileSync(workflowPath, "utf8"); + const regex = /"dispatch_name"\s*:\s*"([^"]+)"/g; + const dispatchNames: string[] = []; + for (const match of yaml.matchAll(regex)) { + dispatchNames.push(match[1]); + } + // Sanity: we expect a populated list (25 CI-built services today). + expect( + dispatchNames.length, + "no dispatch_name entries found in showcase_build.yml — regex broken or file moved", + ).toBeGreaterThan(0); + + // Forward direction: every YAML dispatch_name resolves to a defined + // SSOT key. Catches "added to matrix, forgot SSOT entry." + for (const dispatchName of dispatchNames) { + const resolved = serviceForDispatchName(dispatchName); + expect( + resolved, + `workflow dispatch_name "${dispatchName}" has no SSOT entry in railway-envs.ts`, + ).toBeDefined(); + } + + // Reverse direction (scoped to CI-built): every CI-built SSOT entry's + // dispatchName appears in the YAML matrix. Catches "added SSOT entry, + // forgot matrix slot." Non-CI-built services (pocketbase, webhooks) + // legitimately have no matrix entry — they're built by separate + // release workflows — so they are excluded from the reverse check. + const yamlSet = new Set(dispatchNames); + for (const name of CI_BUILT_SERVICES) { + const entry = SERVICES[name]; + expect( + entry.dispatchName, + `CI-built service "${name}" has no dispatchName in SSOT`, + ).toBeDefined(); + expect( + yamlSet.has(entry.dispatchName as string), + `CI-built service "${name}" (dispatchName="${entry.dispatchName}") has no matching entry in showcase_build.yml ALL_SERVICES matrix`, + ).toBe(true); + } + }); +}); + +describe("webhooks SSOT entry", () => { + it("has a dispatchName", () => { + expect(SERVICES.webhooks).toBeDefined(); + expect(SERVICES.webhooks.dispatchName).toBe("webhooks"); + }); + + it("is mirrored in showcase_build.yml dispatch choices", () => { + const yml = readFileSync( + resolve(__dirname, "../../.github/workflows/showcase_build.yml"), + "utf-8", + ); + // The workflow_dispatch.inputs.service options list MUST include + // the SSOT dispatchName for webhooks so humans can target it. + expect(yml).toMatch(/^\s*- webhooks\s*$/m); + // The ALL_SERVICES matrix MUST include a webhooks entry. + expect(yml).toMatch(/"dispatch_name":"webhooks"/); + }); + + it("is wired into showcase_deploy.yml's verify matrix via the SSOT", () => { + // showcase_deploy.yml is SSOT-driven (A.7): the verify matrix is + // derived from railway-envs.generated.json by selecting services + // with `probe.staging === true`, and the workflow_dispatch input is + // a free-form string resolved against the SSOT (key or dispatchName). + // For webhooks to be probe-eligible AND human-dispatchable, the SSOT + // entry MUST carry probe.staging:true and a dispatchName of "webhooks". + expect(SERVICES.webhooks.probe.staging).toBe(true); + expect(SERVICES.webhooks.dispatchName).toBe("webhooks"); + // Pin the workflow's SSOT-driven contract so a future regression + // back to a hardcoded matrix is caught. The matrix-building logic + // was extracted out of inline bash into + // showcase/scripts/resolve-verify-matrix.ts (the prior inline + // bash+jq produced two confirmed bugs across CR rounds, so the + // contract now lives in a pure, unit-tested TS module). The deploy + // workflow MUST invoke that script — that is the SSOT-consumption + // hand-off. The script itself MUST read + // `railway-envs.generated.json` and filter on `probe.staging===true` + // — that is the SSOT-consumption mechanism. Pinning both halves + // catches the two regression shapes that matter: (a) deploy.yml + // silently dropping the script call (back to a hardcoded matrix + // or inline jq) and (b) the script being kept but the SSOT/probe + // contract being weakened inside it. + const yml = readFileSync( + resolve(__dirname, "../../.github/workflows/showcase_deploy.yml"), + "utf-8", + ); + expect( + yml, + "showcase_deploy.yml must invoke resolve-verify-matrix.ts to build the verify matrix from the SSOT", + ).toMatch(/resolve-verify-matrix\.ts/); + + const resolver = readFileSync( + resolve(__dirname, "./resolve-verify-matrix.ts"), + "utf-8", + ); + expect( + resolver, + "resolve-verify-matrix.ts must consume railway-envs.generated.json (the SSOT JSON)", + ).toMatch(/railway-envs\.generated\.json/); + expect( + resolver, + "resolve-verify-matrix.ts must select probe-eligible services on probe.staging===true", + ).toMatch(/probe\.staging\s*===\s*true/); + }); +}); + +describe("dispatchName uniqueness invariant", () => { + it("the production SSOT has no duplicate dispatchNames", () => { + // This calls the invariant against the real exported SERVICES map; + // it MUST pass on a healthy tree. + expect(() => assertDispatchNamesUnique()).not.toThrow(); + }); + + it("throws when two services share a dispatchName", () => { + // Synthetic input: same as production SERVICES but with two + // entries pointing at the same dispatchName "showcase-aimock". + const synthetic: Record = { + aimock: { dispatchName: "showcase-aimock" }, + "showcase-aimock-dupe": { dispatchName: "showcase-aimock" }, + mastra: { dispatchName: "mastra" }, + }; + expect(() => assertDispatchNamesUnique(synthetic)).toThrow( + /duplicate dispatchName.*showcase-aimock.*aimock.*showcase-aimock-dupe/i, + ); + }); + + it("ignores entries without a dispatchName", () => { + const synthetic: Record = { + pocketbase: {}, // no dispatchName — out-of-band + webhooks: { dispatchName: "webhooks" }, + }; + expect(() => assertDispatchNamesUnique(synthetic)).not.toThrow(); + }); +}); + +describe("railway-envs SSOT — domains + probe", () => { + it("every service exposes a domains.staging and domains.prod (no scheme)", () => { + for (const [name, entry] of Object.entries(SERVICES)) { + expect(entry.domains, `${name}.domains missing`).toBeDefined(); + expect(entry.domains.staging, `${name}.domains.staging`).toMatch( + /^[A-Za-z0-9.-]+$/, + ); + expect(entry.domains.prod, `${name}.domains.prod`).toMatch( + /^[A-Za-z0-9.-]+$/, + ); + expect( + entry.domains.staging.startsWith("http"), + `${name}: staging domain must not include scheme`, + ).toBe(false); + expect( + entry.domains.prod.startsWith("http"), + `${name}: prod domain must not include scheme`, + ).toBe(false); + } + }); + + it("confirmed staging domains match the documented values", () => { + expect(SERVICES.pocketbase.domains.staging).toBe( + "pocketbase-staging-eec0.up.railway.app", + ); + expect(SERVICES.harness.domains.staging).toBe( + "harness-staging-2ee4.up.railway.app", + ); + expect(SERVICES.shell.domains.staging).toBe( + "showcase.staging.copilotkit.ai", + ); + expect(SERVICES.docs.domains.staging).toBe("docs.staging.copilotkit.ai"); + expect(SERVICES.dashboard.domains.staging).toBe( + "dashboard.showcase.staging.copilotkit.ai", + ); + }); + + it("confirmed prod domains match the bin/railway:73-88 EXPECTED_DOMAINS", () => { + expect(SERVICES.shell.domains.prod).toBe("showcase.copilotkit.ai"); + expect(SERVICES.dashboard.domains.prod).toBe( + "dashboard.showcase.copilotkit.ai", + ); + expect(SERVICES.dojo.domains.prod).toBe("dojo.showcase.copilotkit.ai"); + expect(SERVICES.docs.domains.prod).toBe("docs.copilotkit.ai"); + expect(SERVICES.webhooks.domains.prod).toBe("hooks.showcase.copilotkit.ai"); + }); + + it("every service exposes a probe.staging, probe.prod, probe.driver", () => { + const validDrivers: ProbeDriver[] = [ + "shell", + "harness", + "eval", + "aimock", + "pocketbase", + "webhooks", + "dojo", + "docs", + "dashboard", + "agent", + ]; + for (const [name, entry] of Object.entries(SERVICES)) { + expect(entry.probe, `${name}.probe missing`).toBeDefined(); + expect(typeof entry.probe.staging).toBe("boolean"); + expect(typeof entry.probe.prod).toBe("boolean"); + expect(validDrivers).toContain(entry.probe.driver); + } + }); + + it("aimock probes BOTH envs by default (the carve-out is digest-only, not probe-skip)", () => { + // The aimock-prod carve-out only freezes the digest; the prod probe + // still runs against whatever is pinned. Spec §3 / §11. + expect(SERVICES.aimock.probe.prod).toBe(true); + expect(SERVICES.aimock.probe.staging).toBe(true); + }); + + it("domainFor returns the no-scheme host for known service+env", () => { + expect(domainFor("docs", "staging")).toBe("docs.staging.copilotkit.ai"); + expect(domainFor("docs", "prod")).toBe("docs.copilotkit.ai"); + expect(domainFor("pocketbase", "staging")).toBe( + "pocketbase-staging-eec0.up.railway.app", + ); + }); + + it("domainFor throws on unknown service (no silent empty string)", () => { + expect(() => domainFor("nope", "staging")).toThrow( + /Unknown showcase service/, + ); + expect(() => domainFor("nope", "prod")).toThrow(/Unknown showcase service/); + }); + + it("domainFor throws on unknown env (defends against ad-hoc EnvName values)", () => { + // @ts-expect-error — intentionally passing a bad env at runtime. + expect(() => domainFor("docs", "dev")).toThrow(/Unknown env/); + }); + + it("domainFor scheme guard rejects scheme-included literals but accepts http*-prefixed hosts", () => { + // The guard is intended to reject `http://...` / `https://...` literals + // accidentally pasted into the SSOT — NOT to reject any host whose name + // happens to begin with the letters "http" (e.g. `httpd-...`, `httpbin...`). + // Discriminator is the scheme separator `://`, not a `startsWith("http")` + // prefix check. + const saved = SERVICES.docs.domains.staging; + try { + // Regression: malformed `http://`-style literal MUST still throw. + (SERVICES.docs.domains as { staging: string }).staging = + "http://docs.staging.copilotkit.ai"; + expect(() => domainFor("docs", "staging")).toThrow( + /malformed\/missing staging domain/, + ); + (SERVICES.docs.domains as { staging: string }).staging = + "https://docs.staging.copilotkit.ai"; + expect(() => domainFor("docs", "staging")).toThrow( + /malformed\/missing staging domain/, + ); + // Positive: a hypothetical `httpd-`-prefixed host MUST be accepted. + (SERVICES.docs.domains as { staging: string }).staging = + "httpd-staging.up.railway.app"; + expect(domainFor("docs", "staging")).toBe("httpd-staging.up.railway.app"); + (SERVICES.docs.domains as { staging: string }).staging = + "httpbin.example.com"; + expect(domainFor("docs", "staging")).toBe("httpbin.example.com"); + } finally { + (SERVICES.docs.domains as { staging: string }).staging = saved; + } + }); + + it("STAGING_ENV_ID and PRODUCTION_ENV_ID are exported and stable", () => { + expect(STAGING_ENV_ID).toBe("8edfef02-ea09-4a20-8689-261f21cc2849"); + expect(PRODUCTION_ENV_ID).toBe("b14919f4-6417-429f-848d-c6ae2201e04f"); + }); + + it("ProbeConfig type compiles with the documented shape", () => { + // Compile-time guard: this object must satisfy ProbeConfig. + const sample: ProbeConfig = { + staging: true, + prod: false, + driver: "shell", + }; + expect(sample.driver).toBe("shell"); + }); +}); diff --git a/showcase/scripts/railway-envs.ts b/showcase/scripts/railway-envs.ts new file mode 100644 index 00000000000..19225d09b32 --- /dev/null +++ b/showcase/scripts/railway-envs.ts @@ -0,0 +1,723 @@ +/** + * railway-envs.ts — Single source of truth for Railway IDs used by all + * TypeScript showcase tooling. Mirrors (but does not import) the IDs in + * `showcase/bin/railway` (Ruby). When these drift, prefer this file as + * the TS-side canonical source and reconcile bin/railway by hand. + * + * - PROJECT_ID is the CopilotKit Showcase Railway project. + * - ENV_IDS maps human env names (and common synonyms) to Railway env IDs. + * - SERVICES is the per-service map of serviceId + per-env serviceInstanceId. + * + * Service-instance IDs are env-scoped, so prod and staging IDs differ for + * the same service. Use instanceIdFor(serviceName, env) to get the right one. + */ + +export const PROJECT_ID = "6f8c6bff-a80d-4f8f-b78d-50b32bcf4479"; + +export const PRODUCTION_ENV_ID = "b14919f4-6417-429f-848d-c6ae2201e04f"; +export const STAGING_ENV_ID = "8edfef02-ea09-4a20-8689-261f21cc2849"; + +export type EnvName = "prod" | "staging"; + +// Accept common synonyms ("production", "prod", "staging") and normalize. +export const ENV_IDS: Record = { + prod: PRODUCTION_ENV_ID, + production: PRODUCTION_ENV_ID, + staging: STAGING_ENV_ID, +}; + +export function resolveEnv(name: string): { env: EnvName; envId: string } { + const lower = name.trim().toLowerCase(); + if (lower === "prod" || lower === "production") { + return { env: "prod", envId: PRODUCTION_ENV_ID }; + } + if (lower === "staging") { + return { env: "staging", envId: STAGING_ENV_ID }; + } + throw new Error( + `Unknown env "${name}". Use one of: prod, production, staging.`, + ); +} + +/** + * Per-env domain host (no scheme). `staging` and `prod` MUST both be set — + * a service that does not exist in one env should not be in the SSOT at all. + * `domainFor(name, env)` is the only public API; it throws on missing data. + */ +export interface Domains { + staging: string; + prod: string; +} + +/** + * The probe driver selects which feature-level verifier `verify-deploy.ts` + * runs against a service's URL. "200 ≠ healthy": every driver does more + * than a naked GET. See showcase/scripts/verify-deploy.ts for the per-driver + * implementation; the SSOT only names the driver here. + * + * - "shell" / "docs" / "dashboard" / "dojo" — Next.js shells: load the page + * and assert a known DOM string plus a known network call. + * - "harness" / "eval" — synthetic e2e fixture against the service's API. + * - "aimock" — fixture replay with deterministic-response drift check. + * - "pocketbase" — admin login + known collection list. + * - "webhooks" — synthetic event POST and downstream confirmation. + * - "agent" — generic agent backend (the showcase-* integration services); + * feature-level fixture call into the integration's /api endpoint. + */ +export type ProbeDriver = + | "shell" + | "harness" + | "eval" + | "aimock" + | "pocketbase" + | "webhooks" + | "dojo" + | "docs" + | "dashboard" + | "agent"; + +/** + * Per-service probe configuration. `staging:false` or `prod:false` excludes + * the service from verify-deploy's pass for that env (e.g. a service we + * deliberately don't manage in one env). The default for every entry is + * `staging:true, prod:true` — only the aimock-prod CARVE-OUT (spec §11) + * lives as a comment; the probe still runs against the pinned aimock-prod. + */ +export interface ProbeConfig { + staging: boolean; + prod: boolean; + driver: ProbeDriver; +} + +export interface ServiceEntry { + /** Railway service ID (env-independent). */ + serviceId: string; + /** serviceInstance ID for the production env. */ + prodInstanceId: string; + /** serviceInstance ID for the staging env. */ + stagingInstanceId: string; + /** + * True iff this service is built and pushed by `showcase_build.yml`. + * pocketbase and webhooks are first-party GHCR images but are built + * by their own repos' release workflows — they MUST NOT be touched + * by the showcase build's staging redeploy step. + */ + ciBuilt: boolean; + /** + * True iff `verify-railway-image-refs.ts` validates this service's + * image refs. As of WS-C completion this is `true` for every service + * in `SERVICES` — the historic Phase-2 deferral on dashboard, docs, + * dojo, shell, and harness has been retired. New services added to + * the SSOT MUST land with `gateValidated: true` (and a + * `repoNameOverride` if the Railway service name does not match the + * GHCR repo name); use the optional `gateIgnore: true` field only + * for deliberately-untracked third-party services. + */ + gateValidated: boolean; + /** + * Optional GHCR repo name override per env. When unset for an env, + * the verify gate expects `ghcr.io/copilotkit/:` + * (or `@sha256:` in prod). When set, the gate uses + * `ghcr.io/copilotkit/:` for that env only. + * + * Real cases (2026-05-28); headings use the actual SSOT keys: + * aimock — prod: "showcase-aimock" (digest-pinned wrapper) + * staging: "showcase-aimock" (wrapper, :latest) + * The `showcase-aimock` wrapper bakes showcase fixtures + * into base aimock and is the permanent, canonical + * image for the aimock showcase service in BOTH envs. + * It is the only aimock image CI builds. + * pocketbase — prod: "showcase-pocketbase" + * staging: "showcase-pocketbase" + * webhooks — prod: "showcase-eval-webhook" + * staging: "showcase-eval-webhook" + * dashboard — prod/staging: "showcase-shell-dashboard" + * docs — prod/staging: "showcase-shell-docs" + * dojo — prod/staging: "showcase-shell-dojo" + * shell — prod/staging: "showcase-shell" + * harness — prod/staging: "showcase-harness" + */ + repoNameOverride?: { prod?: string; staging?: string }; + /** + * Opt-out flag for the image-ref gate. When `true`, the gate ignores + * this service entirely in BOTH the SSOT→Railway direction (no + * "missing from Railway" failure if absent) AND the Railway→SSOT + * direction (no "untracked Railway service" failure if Railway has + * a service with this name that is not WS4-managed). Default: false. + * + * Intentionally narrow: this exists for deliberately-untracked + * third-party relays or one-off experimental services. The default + * for every WS4-managed service is `false` (omitted). + */ + gateIgnore?: boolean; + /** + * Public hosts per env, no scheme. Both must be set. `domainFor` is + * the only API; it throws on unknown service/env so the verify probe + * fails loud rather than silently probing "". + */ + domains: Domains; + /** + * Verify-deploy probe configuration. `verify-deploy.ts --env ` + * iterates SERVICES and runs `driver` for every service where + * `probe[env] === true`. + */ + probe: ProbeConfig; +} + +/** + * Canonical per-service ID map. Keys are the EXACT Railway service names + * (`showcase-*` for integrations; bare names for infra services). + * + * Resolved 2026-05-28 via Railway GraphQL + * `project($id).services.edges[].serviceInstances.edges[]`. + * + * `dispatchName` (when set) is the EXACT `dispatch_name` value used by + * `.github/workflows/showcase_build.yml`'s `ALL_SERVICES` matrix entry + * for this service. The redeploy script uses it to convert the matrix + * output (which carries dispatch_names) back into SSOT keys. + */ +export const SERVICES: Record< + string, + ServiceEntry & { dispatchName?: string } +> = { + aimock: { + serviceId: "0fa0435d-8a66-46f0-84fd-e4250b580013", + prodInstanceId: "5801d8be-5ad9-4eff-9c9c-7be61d9a023e", + stagingInstanceId: "9f260dfd-d9d4-43e9-98fe-49696f87fe50", + ciBuilt: true, + gateValidated: true, + dispatchName: "showcase-aimock", + // Aimock runs the `showcase-aimock` wrapper image in BOTH envs. + // The wrapper (built from `showcase/aimock/Dockerfile`) bakes the + // showcase fixture tree into base aimock and is the permanent, + // canonical aimock image — it is the only aimock image CI builds. + // Prod is digest-pinned (`@sha256:`, promote-only); staging + // floats `:latest`. Both envs override to the same `showcase-aimock` + // GHCR repo; there is no migration to the unwrapped `aimock` repo. + repoNameOverride: { prod: "showcase-aimock", staging: "showcase-aimock" }, + domains: { + staging: "aimock-staging.up.railway.app", + prod: "showcase-aimock-production.up.railway.app", + }, + probe: { staging: true, prod: true, driver: "aimock" }, + }, + dashboard: { + serviceId: "4d5dfd74-be61-40b2-8564-b53b7dd4c15b", + prodInstanceId: "e68f98fa-b2ef-41cc-82f6-2ed6f9533bf3", + stagingInstanceId: "aea7332e-17a0-4fab-921c-ed5baad2a6f2", + ciBuilt: true, + gateValidated: true, + dispatchName: "shell-dashboard", + // Railway service name is `dashboard`; GHCR repo is + // `showcase-shell-dashboard`. Same override for both envs: + // staging :latest, prod @sha256 — uniform across all services. + repoNameOverride: { + prod: "showcase-shell-dashboard", + staging: "showcase-shell-dashboard", + }, + domains: { + staging: "dashboard.showcase.staging.copilotkit.ai", + prod: "dashboard.showcase.copilotkit.ai", + }, + probe: { staging: true, prod: true, driver: "dashboard" }, + }, + docs: { + serviceId: "7badfb8d-4228-414c-9145-b4026803714f", + prodInstanceId: "b15564fc-f832-49b3-82df-fd36f298fe96", + stagingInstanceId: "d5caa51d-73ee-4669-bfea-d87bf1488b02", + ciBuilt: true, + gateValidated: true, + dispatchName: "shell-docs", + // Railway service name is `docs`; GHCR repo is `showcase-shell-docs`. + repoNameOverride: { + prod: "showcase-shell-docs", + staging: "showcase-shell-docs", + }, + domains: { + staging: "docs.staging.copilotkit.ai", + prod: "docs.copilotkit.ai", + }, + probe: { staging: true, prod: true, driver: "docs" }, + }, + dojo: { + serviceId: "7ad1ece7-2228-49cd-8a78-bddf30322907", + prodInstanceId: "2ee4f2aa-11ec-4426-9a4a-41a1ad04f16d", + stagingInstanceId: "1284d717-0ff5-432c-9326-fab12661df61", + ciBuilt: true, + gateValidated: true, + dispatchName: "shell-dojo", + // Railway service name is `dojo`; GHCR repo is `showcase-shell-dojo`. + repoNameOverride: { + prod: "showcase-shell-dojo", + staging: "showcase-shell-dojo", + }, + domains: { + staging: "dojo.showcase.staging.copilotkit.ai", + prod: "dojo.showcase.copilotkit.ai", + }, + probe: { staging: true, prod: true, driver: "dojo" }, + }, + harness: { + serviceId: "3a14bfed-0537-4d71-897b-7c593dca161d", + prodInstanceId: "05fbcdf2-8a50-4b71-b4f6-c92c4b17e626", + stagingInstanceId: "0811f68f-fac4-440e-a350-3a7ca5855b80", + ciBuilt: true, + gateValidated: true, + dispatchName: "showcase-harness", + // Railway service name is `harness`; GHCR repo is `showcase-harness`. + // ciBuilt: true, gateValidated: true — uniform with the rest of + // the CI-built integrations now that WS-C has flipped the five. + repoNameOverride: { + prod: "showcase-harness", + staging: "showcase-harness", + }, + domains: { + staging: "harness-staging-2ee4.up.railway.app", + prod: "showcase-harness-production.up.railway.app", + }, + probe: { staging: true, prod: true, driver: "harness" }, + }, + pocketbase: { + serviceId: "ba11e854-d695-4738-9a45-2b0776788824", + prodInstanceId: "1ee376e2-13f2-4464-801e-d0aa0bf76532", + stagingInstanceId: "0bc7db7b-5a43-4b33-af46-d07fb53c8610", + // pocketbase is a first-party ghcr.io/copilotkit/ image, but its + // GHCR repo name is `showcase-pocketbase` (NOT `pocketbase`), and + // it is built by a separate release workflow — not showcase_build.yml. + ciBuilt: false, + gateValidated: true, + repoNameOverride: { + prod: "showcase-pocketbase", + staging: "showcase-pocketbase", + }, + domains: { + staging: "pocketbase-staging-eec0.up.railway.app", + prod: "showcase-pocketbase-production.up.railway.app", + }, + probe: { staging: true, prod: true, driver: "pocketbase" }, + }, + shell: { + serviceId: "40eea0da-6071-4ea8-bdb9-39afb19225ec", + prodInstanceId: "01614ccf-e109-4b30-b41b-7c5551c0a34c", + stagingInstanceId: "25b7de41-188c-4f2e-ac07-538212eaeb91", + ciBuilt: true, + gateValidated: true, + dispatchName: "shell", + // Railway service name is `shell`; GHCR repo is `showcase-shell`. + repoNameOverride: { + prod: "showcase-shell", + staging: "showcase-shell", + }, + domains: { + staging: "showcase.staging.copilotkit.ai", + prod: "showcase.copilotkit.ai", + }, + probe: { staging: true, prod: true, driver: "shell" }, + }, + "showcase-ag2": { + serviceId: "4a37481b-f264-4eb7-a9cd-0a9ebb9ac05c", + prodInstanceId: "de571c97-03fd-486b-8a54-9767a4a53f95", + stagingInstanceId: "ecaf81b3-93a8-4862-92b6-04a016b634ed", + ciBuilt: true, + gateValidated: true, + dispatchName: "ag2", + domains: { + staging: "showcase-ag2-staging.up.railway.app", + prod: "showcase-ag2-production.up.railway.app", + }, + probe: { staging: true, prod: true, driver: "agent" }, + }, + "showcase-agno": { + serviceId: "32cab80b-e329-45bd-9c73-c4e1ddc94305", + prodInstanceId: "026d12fb-2844-42af-8f92-b47bc8a06bc8", + stagingInstanceId: "68964ab6-75ca-4095-a64a-52cacfb684f5", + ciBuilt: true, + gateValidated: true, + dispatchName: "agno", + domains: { + staging: "showcase-agno-staging.up.railway.app", + prod: "showcase-agno-production.up.railway.app", + }, + probe: { staging: true, prod: true, driver: "agent" }, + }, + "showcase-built-in-agent": { + serviceId: "f4f8371a-bc46-45b2-b6d4-9c9af608bdbf", + prodInstanceId: "40018ef7-1ed1-4979-b80c-9c2d957b6d88", + stagingInstanceId: "b89ae7b3-01cc-4ed4-aca6-23aaa63cd59e", + ciBuilt: true, + gateValidated: true, + dispatchName: "built-in-agent", + domains: { + staging: "showcase-built-in-agent-staging.up.railway.app", + prod: "showcase-built-in-agent-production.up.railway.app", + }, + probe: { staging: true, prod: true, driver: "agent" }, + }, + "showcase-claude-sdk-python": { + serviceId: "b122ab65-9854-4cb2-a68e-b50ff13f7481", + prodInstanceId: "bb18caaf-9a3e-4fdd-85ec-562fd82a3a89", + stagingInstanceId: "1ef25aec-5fbd-40b9-8685-57c2681bd45d", + ciBuilt: true, + gateValidated: true, + dispatchName: "claude-sdk-python", + domains: { + staging: "showcase-claude-sdk-python-staging.up.railway.app", + prod: "showcase-claude-sdk-python-production.up.railway.app", + }, + probe: { staging: true, prod: true, driver: "agent" }, + }, + "showcase-claude-sdk-typescript": { + serviceId: "18a98727-5700-44aa-b497-b60795dbbd6a", + prodInstanceId: "bee425e4-9661-4a88-8888-922b8cd4b61d", + stagingInstanceId: "92305747-2f55-4122-aad4-882e989558ab", + ciBuilt: true, + gateValidated: true, + dispatchName: "claude-sdk-typescript", + domains: { + staging: "showcase-claude-sdk-typescript-staging.up.railway.app", + prod: "showcase-claude-sdk-typescript-production.up.railway.app", + }, + probe: { staging: true, prod: true, driver: "agent" }, + }, + "showcase-crewai-crews": { + serviceId: "0e9c284d-8d87-4fcf-9f82-6b704d7e4bd4", + prodInstanceId: "3dab0cc3-cab1-4579-b772-947268088514", + stagingInstanceId: "88c2a14f-435b-499e-a811-ee4f4be18fd8", + ciBuilt: true, + gateValidated: true, + dispatchName: "crewai-crews", + domains: { + staging: "showcase-crewai-crews-staging.up.railway.app", + prod: "showcase-crewai-crews-production.up.railway.app", + }, + probe: { staging: true, prod: true, driver: "agent" }, + }, + "showcase-google-adk": { + serviceId: "87f60507-5a3d-4b8a-9e23-2b1de85d939c", + prodInstanceId: "7b2da5db-87d2-40ad-a3d9-b2d7a5485a22", + stagingInstanceId: "7efe2fa0-fa78-4585-bc4c-6d39c326e6d1", + ciBuilt: true, + gateValidated: true, + dispatchName: "google-adk", + domains: { + staging: "showcase-google-adk-staging.up.railway.app", + prod: "showcase-google-adk-production.up.railway.app", + }, + probe: { staging: true, prod: true, driver: "agent" }, + }, + "showcase-langgraph-fastapi": { + serviceId: "06cccb5c-59f4-46b5-8adc-7113e77011a4", + prodInstanceId: "105b7e01-acd0-48e2-9a09-541e2103e8d2", + stagingInstanceId: "7899afe0-141b-4217-8dbb-5907813231dc", + ciBuilt: true, + gateValidated: true, + dispatchName: "langgraph-fastapi", + domains: { + staging: "showcase-langgraph-fastapi-staging.up.railway.app", + prod: "showcase-langgraph-fastapi-production.up.railway.app", + }, + probe: { staging: true, prod: true, driver: "agent" }, + }, + "showcase-langgraph-python": { + serviceId: "90d03214-4569-41b0-b4c1-6438a8a7b203", + prodInstanceId: "aec504f7-63d7-4ea6-9d50-601b00d2ae80", + stagingInstanceId: "04d29664-a776-4670-9db3-b1d18bce1669", + ciBuilt: true, + gateValidated: true, + dispatchName: "langgraph-python", + domains: { + staging: "showcase-langgraph-python-staging.up.railway.app", + prod: "showcase-langgraph-python-production.up.railway.app", + }, + probe: { staging: true, prod: true, driver: "agent" }, + }, + "showcase-langgraph-typescript": { + serviceId: "66246d3b-a18e-46f0-be51-5f3ff7a36e5a", + prodInstanceId: "f53e9fdc-7c3e-4dfd-9fa8-d7241fd55bb8", + stagingInstanceId: "481ab37f-da8a-4015-bd88-2b28d9eb261a", + ciBuilt: true, + gateValidated: true, + dispatchName: "langgraph-typescript", + domains: { + staging: "showcase-langgraph-typescript-staging.up.railway.app", + prod: "showcase-langgraph-typescript-production.up.railway.app", + }, + probe: { staging: true, prod: true, driver: "agent" }, + }, + "showcase-langroid": { + serviceId: "6dd9cb0a-66cc-46f1-972e-7cd74756157d", + prodInstanceId: "6b5e20b5-8f8e-4ec3-9288-7a41122e42e5", + stagingInstanceId: "a213f7d9-2117-4944-988b-05e68d819dd5", + ciBuilt: true, + gateValidated: true, + dispatchName: "langroid", + domains: { + staging: "showcase-langroid-staging.up.railway.app", + prod: "showcase-langroid-production.up.railway.app", + }, + probe: { staging: true, prod: true, driver: "agent" }, + }, + "showcase-llamaindex": { + serviceId: "285386e8-492d-4cb8-b632-0a7d4607378f", + prodInstanceId: "b778856e-9f90-4136-9415-fb2b41173f8d", + stagingInstanceId: "17899ea7-355c-43f2-a152-28cb0b7fa864", + ciBuilt: true, + gateValidated: true, + dispatchName: "llamaindex", + domains: { + staging: "showcase-llamaindex-staging.up.railway.app", + prod: "showcase-llamaindex-production.up.railway.app", + }, + probe: { staging: true, prod: true, driver: "agent" }, + }, + "showcase-mastra": { + serviceId: "d7979eb7-2405-4aab-ad21-438f4a1b08af", + prodInstanceId: "eaeddd9c-8b75-426f-b033-0fd935cbf6ef", + stagingInstanceId: "eec22411-aab5-47a1-8f5b-d097e233d7f8", + ciBuilt: true, + gateValidated: true, + dispatchName: "mastra", + domains: { + staging: "showcase-mastra-staging.up.railway.app", + prod: "showcase-mastra-production.up.railway.app", + }, + probe: { staging: true, prod: true, driver: "agent" }, + }, + "showcase-ms-agent-dotnet": { + serviceId: "beeb2dd6-87a4-4599-aa07-0578f7bd6519", + prodInstanceId: "93ca0edf-7b59-4de4-b1fd-3412bb07bc6a", + stagingInstanceId: "9826bc58-c472-41e6-b050-29249d4b2a52", + ciBuilt: true, + gateValidated: true, + dispatchName: "ms-agent-dotnet", + domains: { + staging: "showcase-ms-agent-dotnet-staging.up.railway.app", + prod: "showcase-ms-agent-dotnet-production.up.railway.app", + }, + probe: { staging: true, prod: true, driver: "agent" }, + }, + "showcase-ms-agent-harness-dotnet": { + serviceId: "6343d7f9-6c3f-4c8d-9a6e-79f03d2f1e37", + prodInstanceId: "8f91ebc6-95c0-4433-b1f7-657ff49c2d59", + stagingInstanceId: "6b0fe181-9156-4a40-9e44-90befe09833a", + ciBuilt: true, + gateValidated: true, + dispatchName: "ms-agent-harness-dotnet", + domains: { + staging: "showcase-ms-agent-harness-dotnet-staging.up.railway.app", + prod: "showcase-ms-agent-harness-dotnet-production.up.railway.app", + }, + probe: { staging: true, prod: true, driver: "agent" }, + }, + "showcase-ms-agent-python": { + serviceId: "655db75a-af8d-427d-a4f9-441570ae5003", + prodInstanceId: "323ed911-4d28-45ab-8fc0-7d151828b938", + stagingInstanceId: "741725ce-5fa1-4327-aff5-53dcc000c29c", + ciBuilt: true, + gateValidated: true, + dispatchName: "ms-agent-python", + domains: { + staging: "showcase-ms-agent-python-staging.up.railway.app", + prod: "showcase-ms-agent-python-production.up.railway.app", + }, + probe: { staging: true, prod: true, driver: "agent" }, + }, + "showcase-pydantic-ai": { + serviceId: "0a106173-2282-4887-a994-0ca276a99d69", + prodInstanceId: "192cd647-6824-4f01-937a-1da675d83805", + stagingInstanceId: "6edf5ca5-6a56-4d28-92c3-2a3360c735db", + ciBuilt: true, + gateValidated: true, + dispatchName: "pydantic-ai", + domains: { + staging: "showcase-pydantic-ai-staging.up.railway.app", + prod: "showcase-pydantic-ai-production.up.railway.app", + }, + probe: { staging: true, prod: true, driver: "agent" }, + }, + "showcase-spring-ai": { + serviceId: "eed5d041-91be-4282-b414-beea00843401", + prodInstanceId: "2fbf1db2-5e51-44c9-983c-3f2242d95c61", + stagingInstanceId: "189ac76f-bd77-45c0-9c45-3853dae763cc", + ciBuilt: true, + gateValidated: true, + dispatchName: "spring-ai", + domains: { + staging: "showcase-spring-ai-staging.up.railway.app", + prod: "showcase-spring-ai-production.up.railway.app", + }, + probe: { staging: true, prod: true, driver: "agent" }, + }, + "showcase-strands": { + serviceId: "92e1cfad-ad53-403f-ab2b-5ab380832232", + prodInstanceId: "2123c71b-9385-443c-a1c3-bcf4b1669eeb", + stagingInstanceId: "f8a9d2ed-50ec-4f06-85d6-230baced8471", + ciBuilt: true, + gateValidated: true, + dispatchName: "strands", + domains: { + staging: "showcase-strands-staging.up.railway.app", + prod: "showcase-strands-production.up.railway.app", + }, + probe: { staging: true, prod: true, driver: "agent" }, + }, + webhooks: { + serviceId: "ba6acc13-7585-41fe-a5ee-585b34a58fcd", + prodInstanceId: "d82ef5b4-3bfd-462e-9436-3d5dbca8681a", + stagingInstanceId: "450e87e0-aba5-4aba-afaf-15f4deab03f0", + ciBuilt: false, + gateValidated: true, + dispatchName: "webhooks", + // webhooks is a first-party ghcr.io/copilotkit/ image, but its + // GHCR repo name is `showcase-eval-webhook` (NOT `webhooks`), and + // it is built by a separate release workflow — not showcase_build.yml. + // The dispatch_name entry below exists so humans can redeploy/verify + // webhooks from CI on demand; the build slot is no-op. + repoNameOverride: { + prod: "showcase-eval-webhook", + staging: "showcase-eval-webhook", + }, + domains: { + staging: "hooks.showcase.staging.copilotkit.ai", + prod: "hooks.showcase.copilotkit.ai", + }, + probe: { staging: true, prod: true, driver: "webhooks" }, + }, +}; + +export function instanceIdFor(serviceName: string, env: EnvName): string { + const entry = SERVICES[serviceName]; + if (!entry) { + throw new Error( + `Unknown showcase service "${serviceName}". Add it to SERVICES in showcase/scripts/railway-envs.ts.`, + ); + } + return env === "prod" ? entry.prodInstanceId : entry.stagingInstanceId; +} + +export function listServiceNames(): string[] { + return Object.keys(SERVICES).sort(); +} + +/** + * The subset of SERVICES that `showcase_build.yml` actually builds and + * pushes. Excludes `pocketbase` and `webhooks` (released by their own + * repos). Default target set for `redeploy-env.ts ` when no + * explicit `--services` list is provided. + */ +export const CI_BUILT_SERVICES: ReadonlySet = new Set( + Object.entries(SERVICES) + .filter(([, entry]) => entry.ciBuilt) + .map(([name]) => name), +); + +/** + * Resolve the expected GHCR repo name for a (serviceName, env) pair. + * Exported so callers (verify-railway-image-refs.ts) and unit tests can + * exercise override resolution directly. + */ +export function repoNameFor(serviceName: string, env: EnvName): string { + const entry = SERVICES[serviceName]; + if (!entry) return serviceName; + const override = entry.repoNameOverride?.[env]; + return override ?? serviceName; +} + +/** + * Resolve the public host (no scheme) for a (serviceName, env) pair. + * + * THROWS on unknown service. THROWS on env values outside "staging"|"prod". + * Never returns "" — the verify probe and any consumer reading from the + * SSOT must fail loud, not silently probe an empty domain. + * + * Use this from verify-deploy.ts, from any TS caller that needs the URL, + * and from the JSON artifact emitter that the Ruby side consumes. + */ +export function domainFor(serviceName: string, env: EnvName): string { + const entry = SERVICES[serviceName]; + if (!entry) { + throw new Error( + `Unknown showcase service "${serviceName}". Add it to SERVICES in showcase/scripts/railway-envs.ts.`, + ); + } + if (env !== "prod" && env !== "staging") { + throw new Error( + `Unknown env "${String(env)}" passed to domainFor. Expected "prod" or "staging".`, + ); + } + const host = entry.domains[env]; + if (!host || host.includes("://")) { + // Defense-in-depth: SERVICES population is asserted by the + // railway-envs.test.ts schema check, but if a future contributor + // ships a scheme-included literal or an empty host this catches it + // at first use instead of returning a malformed value. We test for + // the scheme separator `://` rather than `startsWith("http")` so we + // don't false-reject legitimate hosts whose name happens to begin + // with the letters "http" (e.g. `httpd-…`, `httpbin…`). + throw new Error( + `Service "${serviceName}" has malformed/missing ${env} domain ("${host}").`, + ); + } + return host; +} + +/** + * Resolve a `showcase_build.yml` `dispatch_name` (e.g. `mastra`, + * `shell-dashboard`, `showcase-aimock`) to the canonical SSOT key + * (e.g. `showcase-mastra`, `dashboard`, `aimock`). Returns undefined + * when the dispatch_name does not correspond to a CI-built service. + */ +export function serviceForDispatchName( + dispatchName: string, +): string | undefined { + for (const [name, entry] of Object.entries(SERVICES)) { + if (entry.dispatchName === dispatchName) return name; + } + return undefined; +} + +/** + * Throw on SSOT load if any two services share the same `dispatchName`. + * `serviceForDispatchName` iterates `Object.entries(SERVICES)` and returns + * the first match — a silent collision would route redeploys to the wrong + * service. We fail loud at module load instead. + * + * Accepts an injected map for testing; defaults to the real SERVICES map. + */ +export function assertDispatchNamesUnique( + services: Record = SERVICES, +): void { + const seen = new Map(); // dispatchName -> first ssotKey + const collisions: Array<{ + dispatchName: string; + keys: [string, string]; + }> = []; + for (const [key, entry] of Object.entries(services)) { + const dn = entry.dispatchName; + if (typeof dn !== "string" || dn.length === 0) continue; + const prior = seen.get(dn); + if (prior !== undefined) { + collisions.push({ dispatchName: dn, keys: [prior, key] }); + } else { + seen.set(dn, key); + } + } + if (collisions.length > 0) { + const lines = collisions + .map( + (c) => + ` - duplicate dispatchName "${c.dispatchName}" on SSOT keys: ${c.keys[0]}, ${c.keys[1]}`, + ) + .join("\n"); + throw new Error( + `railway-envs SSOT invariant violated:\n${lines}\n` + + `Fix: each Railway service must have a unique dispatchName ` + + `(or no dispatchName at all for out-of-band services).`, + ); + } +} + +// Module-load assertion: fail any importer if the SSOT drifts into a +// collision. Tests that exercise the invariant with synthetic input +// call assertDispatchNamesUnique(synthetic) directly. +assertDispatchNamesUnique(); diff --git a/showcase/scripts/redeploy-env.test.ts b/showcase/scripts/redeploy-env.test.ts new file mode 100644 index 00000000000..3cf734f92fc --- /dev/null +++ b/showcase/scripts/redeploy-env.test.ts @@ -0,0 +1,308 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { parseArgs, runRedeploy, resolveTargetServices } from "./redeploy-env"; +import { SERVICES } from "./railway-envs"; + +describe("runRedeploy", () => { + let consoleErrSpy: ReturnType; + let consoleLogSpy: ReturnType; + let stdoutWriteSpy: ReturnType; + let summary: string; + + beforeEach(() => { + consoleErrSpy = vi.spyOn(console, "error").mockImplementation(() => {}); + consoleLogSpy = vi.spyOn(console, "log").mockImplementation(() => {}); + // runRedeploy's per-service progress lines go to process.stdout.write + // (NOT console.log); spy on that too or tests spam the terminal. + stdoutWriteSpy = vi + .spyOn(process.stdout, "write") + .mockImplementation(() => true); + summary = ""; + }); + + afterEach(() => { + consoleErrSpy.mockRestore(); + consoleLogSpy.mockRestore(); + stdoutWriteSpy.mockRestore(); + }); + + const appendSummary = (s: string) => { + summary += s + "\n"; + }; + + it("default scope (no --services) targets the 25 CI-built services, NOT all 27", async () => { + const seenNames: string[] = []; + const redeploy = vi.fn(async (serviceId: string) => { + // Reverse-lookup the SSOT name from serviceId so the test can + // assert exact membership rather than counting opaquely. + const name = Object.entries(SERVICES).find( + ([, e]) => e.serviceId === serviceId, + )?.[0]; + if (name) seenNames.push(name); + return { ok: true as const }; + }); + + const result = await runRedeploy({ + env: "staging", + redeploy, + appendSummary, + // services omitted → default = CI_BUILT_SERVICES + }); + + expect(result.exitCode).toBe(0); + expect(result.attempted).toBe(25); + expect(result.succeeded).toBe(25); + expect(redeploy).toHaveBeenCalledTimes(25); + expect(seenNames).not.toContain("pocketbase"); + expect(seenNames).not.toContain("webhooks"); + }); + + it("default whole-env staging redeploy NEVER bounces pocketbase or webhooks", async () => { + // Explicit anti-regression test: this exact assertion exists + // because the v1 of this script iterated all 27 SERVICES. + const seenIds = new Set(); + const redeploy = vi.fn(async (serviceId: string) => { + seenIds.add(serviceId); + return { ok: true as const }; + }); + await runRedeploy({ env: "staging", redeploy, appendSummary }); + expect(seenIds.has(SERVICES.pocketbase.serviceId)).toBe(false); + expect(seenIds.has(SERVICES.webhooks.serviceId)).toBe(false); + }); + + it("explicit --services list targets exactly that subset", async () => { + const seenIds: string[] = []; + const redeploy = vi.fn(async (serviceId: string) => { + seenIds.push(serviceId); + return { ok: true as const }; + }); + const result = await runRedeploy({ + env: "staging", + redeploy, + appendSummary, + services: ["showcase-mastra", "showcase-ag2"], + }); + expect(result.attempted).toBe(2); + expect(seenIds).toEqual([ + SERVICES["showcase-ag2"].serviceId, // alphabetical iteration + SERVICES["showcase-mastra"].serviceId, + ]); + }); + + it("targets the staging env id", async () => { + const calls: Array<{ environmentId: string }> = []; + const redeploy = vi.fn(async (_svc: string, environmentId: string) => { + calls.push({ environmentId }); + return { ok: true as const }; + }); + await runRedeploy({ + env: "staging", + redeploy, + appendSummary, + services: ["showcase-mastra"], + }); + for (const c of calls) { + expect(c.environmentId).toBe("8edfef02-ea09-4a20-8689-261f21cc2849"); + } + }); + + it("targets the prod env id when env=prod", async () => { + const calls: Array<{ environmentId: string }> = []; + const redeploy = vi.fn(async (_svc: string, environmentId: string) => { + calls.push({ environmentId }); + return { ok: true as const }; + }); + await runRedeploy({ + env: "prod", + redeploy, + appendSummary, + services: ["showcase-mastra"], + }); + for (const c of calls) { + expect(c.environmentId).toBe("b14919f4-6417-429f-848d-c6ae2201e04f"); + } + }); + + it("treats per-service failures as non-fatal and exits 0", async () => { + // Deterministic: fail on a specific named service so the test does + // not depend on the size of the default scope. + const failTarget = SERVICES["showcase-mastra"].serviceId; + const redeploy = vi.fn(async (serviceId: string) => { + if (serviceId === failTarget) { + return { ok: false as const, error: "boom" }; + } + return { ok: true as const }; + }); + + const result = await runRedeploy({ + env: "staging", + redeploy, + appendSummary, + }); + + expect(result.exitCode).toBe(0); + expect(result.failed).toBe(1); + expect(result.succeeded + result.failed).toBe(result.attempted); + // Failure must be visible in the summary. + expect(summary).toMatch(/FAIL/); + expect(summary).toMatch(/boom/); + }); + + it("per-service catch records non-Error throws (null, string) as failures without crashing", async () => { + // Catch block previously did `(e as Error).message ?? String(e)`, + // which throws TypeError when e is null. + const failTarget = SERVICES["showcase-mastra"].serviceId; + const redeploy = vi.fn(async (serviceId: string) => { + if (serviceId === failTarget) { + // eslint-disable-next-line @typescript-eslint/no-throw-literal + throw null; + } + return { ok: true as const }; + }); + + const result = await runRedeploy({ + env: "staging", + redeploy, + appendSummary, + }); + + expect(result.exitCode).toBe(0); + expect(result.failed).toBe(1); + expect(summary).toMatch(/FAIL/); + }); + + it("env=prod returns non-zero exitCode on per-service failure", async () => { + // Prod isn't wired yet, but the design must NOT silently swallow + // prod per-service failures the way staging intentionally does. + const redeploy = vi.fn(async () => ({ + ok: false as const, + error: "kaboom", + })); + const result = await runRedeploy({ + env: "prod", + redeploy, + appendSummary, + services: ["showcase-mastra"], + }); + expect(result.failed).toBe(1); + expect(result.exitCode).not.toBe(0); + }); + + it("env=prod returns exitCode 0 when all services succeed", async () => { + const redeploy = vi.fn(async () => ({ ok: true as const })); + const result = await runRedeploy({ + env: "prod", + redeploy, + appendSummary, + services: ["showcase-mastra"], + }); + expect(result.exitCode).toBe(0); + }); + + it("reports zero failures with empty failure list in summary", async () => { + const redeploy = vi.fn(async () => ({ ok: true as const })); + await runRedeploy({ env: "staging", redeploy, appendSummary }); + expect(summary).toMatch(/0 failed/); + }); + + it("throws on a --services entry that doesn't resolve to a known SSOT key", async () => { + const redeploy = vi.fn(); + await expect( + runRedeploy({ + env: "staging", + redeploy, + appendSummary, + services: ["nonsense"], + }), + ).rejects.toThrow(/Unknown service/); + expect(redeploy).not.toHaveBeenCalled(); + }); + + it("rejects unknown env names", async () => { + const redeploy = vi.fn(); + await expect( + runRedeploy({ env: "dev" as never, redeploy, appendSummary }), + ).rejects.toThrow(/Unknown env/); + expect(redeploy).not.toHaveBeenCalled(); + }); +}); + +describe("resolveTargetServices", () => { + it("accepts SSOT keys verbatim and resolves CI dispatch_names too", () => { + // The workflow passes detect-changes.outputs.matrix .dispatch_name + // values, but a human operator might pass SSOT keys directly. + // Both forms must resolve. + const resolved = resolveTargetServices([ + "showcase-mastra", // already an SSOT key + "mastra", // dispatch_name → showcase-mastra + "shell-dashboard", // dispatch_name → dashboard + "showcase-aimock", // dispatch_name → aimock + ]); + expect(resolved).toEqual(["showcase-mastra", "dashboard", "aimock"]); + // Dedupes the duplicate showcase-mastra ↔ mastra. + }); + + it("throws on inputs that match neither SSOT keys nor dispatch_names", () => { + expect(() => resolveTargetServices(["mastra", "garbage"])).toThrow( + /Unknown service "garbage"/, + ); + }); + + it("returns the CI_BUILT_SERVICES set sorted when given undefined", () => { + const resolved = resolveTargetServices(undefined); + expect(resolved.length).toBe(25); + expect(resolved).not.toContain("pocketbase"); + expect(resolved).not.toContain("webhooks"); + }); +}); + +describe("parseArgs", () => { + it("parses bare env name", () => { + expect(parseArgs(["staging"])).toEqual({ env: "staging" }); + }); + + it("parses --services with CSV value", () => { + expect(parseArgs(["staging", "--services", "mastra,ag2"])).toEqual({ + env: "staging", + services: ["mastra", "ag2"], + }); + }); + + it("parses --services=csv form", () => { + expect(parseArgs(["staging", "--services=mastra,ag2"])).toEqual({ + env: "staging", + services: ["mastra", "ag2"], + }); + }); + + it("throws when --services has no following value (bare flag at end)", () => { + expect(() => parseArgs(["staging", "--services"])).toThrow( + /--services requires a non-empty comma-separated value/, + ); + }); + + it("throws when --services value is the empty string", () => { + expect(() => parseArgs(["staging", "--services", ""])).toThrow( + /--services requires a non-empty comma-separated value/, + ); + }); + + it("throws when --services value is only commas/whitespace", () => { + expect(() => parseArgs(["staging", "--services", ","])).toThrow( + /--services requires a non-empty comma-separated value/, + ); + }); + + it("throws when --services= value empties after split/filter", () => { + expect(() => parseArgs(["staging", "--services="])).toThrow( + /--services requires a non-empty comma-separated value/, + ); + }); + + it("throws on unknown argument", () => { + expect(() => parseArgs(["staging", "--bogus"])).toThrow(/Unknown argument/); + }); + + it("throws on empty argv", () => { + expect(() => parseArgs([])).toThrow(); + }); +}); diff --git a/showcase/scripts/redeploy-env.ts b/showcase/scripts/redeploy-env.ts new file mode 100644 index 00000000000..571e12c8a01 --- /dev/null +++ b/showcase/scripts/redeploy-env.ts @@ -0,0 +1,405 @@ +#!/usr/bin/env npx tsx +/** + * redeploy-env.ts — Trigger Railway `serviceInstanceRedeploy` for the + * CI-built showcase services in the named environment. + * + * Usage: + * npx tsx showcase/scripts/redeploy-env.ts [--services ] + * + * npx tsx showcase/scripts/redeploy-env.ts staging + * → redeploys all 25 CI_BUILT_SERVICES (default scope excludes + * pocketbase/webhooks; explicit --services can still target them) + * + * npx tsx showcase/scripts/redeploy-env.ts staging --services mastra,ag2 + * → redeploys only the listed services (CSV of SSOT keys OR + * showcase_build.yml dispatch_names; mixed is fine). + * + * Behavior: + * - Default target set: CI_BUILT_SERVICES (25 of 27 SSOT entries). + * pocketbase and webhooks are first-party but released by their own + * repos — they are excluded from the default scope. An explicit + * `--services pocketbase` (or webhooks) WILL still redeploy them; + * resolveTargetServices honors any SSOT key the caller asks for. + * - When `--services` is provided, each entry is resolved via + * resolveTargetServices() against SSOT keys + dispatch_names. + * - Per-service Railway failures (including the all-services-fail case) + * are logged to stderr and $GITHUB_STEP_SUMMARY but DO NOT fail the + * process for staging. Staging is not a release gate; the + * verify-deploy workflow is what fails on bad images. For env=prod, + * per-service failures DO yield a non-zero exitCode (fail loud). + * - Operator/config errors (bad env name, unknown service, missing or + * malformed token) ALWAYS fail loud with a non-zero exit. + * + * Auth: RAILWAY_TOKEN env var or ~/.railway/config.json. + * Exit code: 0 on staging even when per-service redeploys fail; non-zero + * for prod per-service failures and for any operator/config error. + */ + +import fs from "fs"; +import { fileURLToPath } from "url"; +import { + CI_BUILT_SERVICES, + PRODUCTION_ENV_ID, + SERVICES, + STAGING_ENV_ID, + resolveEnv, + serviceForDispatchName, +} from "./railway-envs"; +import type { EnvName } from "./railway-envs"; +import { + RAILWAY_GRAPHQL_ENDPOINT, + sanitizeErrorBody, +} from "./lib/railway-graphql"; +import { RailwayTokenError, resolveRailwayToken } from "./lib/railway-token"; + +const RAILWAY_API = RAILWAY_GRAPHQL_ENDPOINT; + +/** + * Resolve the Railway bearer token for this run. Wraps the shared + * `resolveRailwayToken` envelope and maps any RailwayTokenError onto + * the script's exit-1 contract for operator/config errors. The shared + * helper never calls process.exit — exit-code mapping lives HERE so the + * helper stays unit-testable. + */ +function getToken(): string { + try { + return resolveRailwayToken().token; + } catch (e) { + if (e instanceof RailwayTokenError) { + console.error(e.message); + process.exit(1); + } + throw e; + } +} + +export interface RedeployResult { + ok: true; +} +export interface RedeployFailure { + ok: false; + error: string; +} +export type RedeployOutcome = RedeployResult | RedeployFailure; + +export type RedeployFn = ( + serviceId: string, + environmentId: string, +) => Promise; + +/** + * Build a `liveRedeploy` RedeployFn bound to a single resolved token, so + * token resolution happens once per process rather than once per service. + */ +function makeLiveRedeploy(token: string): RedeployFn { + return async function liveRedeploy( + serviceId: string, + environmentId: string, + ): Promise { + const res = await fetch(RAILWAY_API, { + method: "POST", + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + query: `mutation serviceInstanceRedeploy($serviceId: String!, $environmentId: String!) { + serviceInstanceRedeploy(serviceId: $serviceId, environmentId: $environmentId) + }`, + variables: { serviceId, environmentId }, + }), + }); + if (!res.ok) { + const body = sanitizeErrorBody(await res.text()); + return { ok: false, error: `HTTP ${res.status}: ${body}` }; + } + const json = (await res.json()) as { + data?: { serviceInstanceRedeploy?: boolean }; + errors?: Array<{ message: string }>; + }; + if (json.errors?.length) { + return { ok: false, error: json.errors.map((e) => e.message).join("; ") }; + } + if (json.data?.serviceInstanceRedeploy !== true) { + return { + ok: false, + error: `serviceInstanceRedeploy returned ${JSON.stringify(json.data?.serviceInstanceRedeploy)}`, + }; + } + return { ok: true }; + }; +} + +export interface RunRedeployOpts { + env: EnvName; + redeploy: RedeployFn; + appendSummary: (line: string) => void; + /** + * Explicit service list. Each entry may be either an SSOT key + * (e.g. `showcase-mastra`) or a `showcase_build.yml` dispatch_name + * (e.g. `mastra`, `shell-dashboard`, `showcase-aimock`). + * + * When undefined, the default scope is `CI_BUILT_SERVICES` (the 25 + * services that `showcase_build.yml` actually builds). pocketbase + * and webhooks are NEVER in the default scope. + */ + services?: string[]; +} + +export interface RunRedeploySummary { + exitCode: number; + attempted: number; + succeeded: number; + failed: number; +} + +/** + * Normalize a caller-supplied service list (CSV of SSOT keys and/or + * dispatch_names, in any mix) into a deduped list of SSOT keys. + * + * Ordering is intentionally split by branch: + * - When `input` is undefined, returns the default `CI_BUILT_SERVICES` + * set sorted alphabetically (never includes pocketbase/webhooks). + * - When `input` is provided, returns the resolved SSOT keys in the + * caller's INSERTION order (deduped). `runRedeploy` then sorts before + * iterating, so the user-visible iteration order is alphabetical in + * both cases, but this function preserves insertion order so callers + * that want it can opt in. + * + * Exported for direct unit testing. + */ +export function resolveTargetServices(input: string[] | undefined): string[] { + if (input === undefined) { + return [...CI_BUILT_SERVICES].sort(); + } + const resolved = new Set(); + for (const raw of input) { + const name = raw.trim(); + if (!name) continue; + if (SERVICES[name]) { + resolved.add(name); + continue; + } + const viaDispatch = serviceForDispatchName(name); + if (viaDispatch) { + resolved.add(viaDispatch); + continue; + } + throw new Error( + `Unknown service "${name}" — not an SSOT key or dispatch_name in railway-envs.ts. Add it to SERVICES (with a dispatchName) or fix the caller.`, + ); + } + return [...resolved]; +} + +/** + * Pure argv parser. Accepts either `--services x,y,z` or `--services=x,y,z`. + * Throws if `--services` is provided with a missing/empty value (silent + * no-op in CI is worse than a loud failure). Throws on unknown args or + * empty argv. Exported for direct unit testing. + */ +export function parseArgs(argv: string[]): { + env: string; + services?: string[]; +} { + if (argv.length === 0) { + throw new Error( + "Usage: redeploy-env.ts [--services ] (env: prod | production | staging)", + ); + } + const env = argv[0]; + let services: string[] | undefined; + + const ensureNonEmpty = (raw: string | undefined): string[] => { + if (raw === undefined || raw === "") { + throw new Error("--services requires a non-empty comma-separated value"); + } + const parts = raw + .split(",") + .map((s) => s.trim()) + .filter(Boolean); + if (parts.length === 0) { + throw new Error("--services requires a non-empty comma-separated value"); + } + return parts; + }; + + for (let i = 1; i < argv.length; i++) { + const a = argv[i]; + if (a === "--services") { + services = ensureNonEmpty(argv[++i]); + } else if (a.startsWith("--services=")) { + services = ensureNonEmpty(a.slice("--services=".length)); + } else { + throw new Error(`Unknown argument: ${a}`); + } + } + return services === undefined ? { env } : { env, services }; +} + +export async function runRedeploy( + opts: RunRedeployOpts, +): Promise { + const { env, redeploy, appendSummary, services } = opts; + if (env !== "prod" && env !== "staging") { + throw new Error( + `Unknown env: ${String(env)} (expected "prod" or "staging")`, + ); + } + const envId = env === "prod" ? PRODUCTION_ENV_ID : STAGING_ENV_ID; + const names = resolveTargetServices(services).sort(); + + const failures: Array<{ service: string; error: string }> = []; + // Per-service structured records — cross-workstream contract consumed + // by showcase_deploy.yml's `enforce-redeploy-gate` (A.7) via the + // REDEPLOY_SUMMARY_JSON artifact. Shape: + // Array<{ service: string; status: "ok" | "error"; error?: string }> + // Built in parallel with the existing `failures`/`succeeded` tallies so + // PR #5093's exit-code computation below is untouched. + const records: Array<{ + service: string; + status: "ok" | "error"; + error?: string; + }> = []; + let succeeded = 0; + + appendSummary(`## Railway redeploy — env=${env}`); + appendSummary(""); + + for (const name of names) { + const entry = SERVICES[name]; + process.stdout.write(` ${name.padEnd(36)} `); + try { + const outcome = await redeploy(entry.serviceId, envId); + if (outcome.ok) { + succeeded++; + records.push({ service: name, status: "ok" }); + process.stdout.write("OK\n"); + } else { + failures.push({ service: name, error: outcome.error }); + records.push({ service: name, status: "error", error: outcome.error }); + process.stdout.write(`FAIL: ${outcome.error}\n`); + } + } catch (e: unknown) { + const error = e instanceof Error ? e.message : String(e); + failures.push({ service: name, error }); + records.push({ service: name, status: "error", error }); + process.stdout.write(`FAIL (threw): ${error}\n`); + } + } + + const attempted = names.length; + const failed = failures.length; + + appendSummary(`- attempted: **${attempted}**`); + appendSummary(`- succeeded: **${succeeded}**`); + appendSummary(`- ${failed} failed`); + appendSummary(""); + + if (failures.length > 0) { + appendSummary("### Failures"); + appendSummary(""); + appendSummary("| service | status | error |"); + appendSummary("| --- | --- | --- |"); + for (const f of failures) { + const safeErr = f.error.replace(/\|/g, "\\|").replace(/\n/g, " "); + appendSummary(`| \`${f.service}\` | FAIL | ${safeErr} |`); + } + appendSummary(""); + if (env === "staging") { + appendSummary( + "Staging redeploys are non-fatal — the verify-deploy workflow is the gate.", + ); + } + } + + // A.4: optional per-service JSON summary for showcase_deploy.yml's + // `enforce-redeploy-gate` job. Atomic write (stage to .tmp, rename) so + // a CI consumer racing the writer never sees a partial file. A failure + // here is warn-only — PR #5093's exit-code semantics MUST NOT regress + // on a disk hiccup. + const jsonPath = process.env.REDEPLOY_SUMMARY_JSON; + if (jsonPath) { + try { + const tmp = `${jsonPath}.tmp`; + fs.writeFileSync(tmp, JSON.stringify(records, null, 2) + "\n"); + fs.renameSync(tmp, jsonPath); + } catch (e) { + const msg = e instanceof Error ? e.message : String(e); + process.stderr.write( + `warning: failed to write REDEPLOY_SUMMARY_JSON=${jsonPath} (${msg})\n`, + ); + // Non-fatal: best-effort CI summary write; do NOT regress + // PR #5093's exit semantics on a disk hiccup. + } + } + + // Staging: per-service failures are non-fatal (the verify-deploy + // workflow is the real release gate). Prod: per-service failures must + // surface as a non-zero exit so an operator notices. + const exitCode = env === "prod" && failed > 0 ? 1 : 0; + return { exitCode, attempted, succeeded, failed }; +} + +async function main(): Promise { + const argv = process.argv.slice(2); + if (argv.length === 0) { + console.error( + "Usage: npx tsx showcase/scripts/redeploy-env.ts [--services ]", + ); + console.error(" env: prod | production | staging"); + console.error(" --services: optional CSV of SSOT keys or dispatch_names"); + process.exit(2); + } + const parsed = parseArgs(argv); + const { env } = resolveEnv(parsed.env); + const services = parsed.services; + + const summaryFile = process.env.GITHUB_STEP_SUMMARY; + const appendSummary = (line: string) => { + if (summaryFile) { + try { + fs.appendFileSync(summaryFile, line + "\n"); + } catch (e) { + // Best-effort: a non-writable $GITHUB_STEP_SUMMARY must not abort + // the run. Mirror to stderr so the failure is at least visible. + const msg = e instanceof Error ? e.message : String(e); + process.stderr.write( + `warning: failed to append to GITHUB_STEP_SUMMARY (${msg})\n`, + ); + } + } + // Also mirror to stderr so local runs see it. + process.stderr.write(line + "\n"); + }; + + // Resolve the Railway token ONCE for the whole run, then thread it + // through to liveRedeploy. Missing/malformed creds exit non-zero from + // getToken() before we ever enter the per-service loop. + const token = getToken(); + const redeploy = makeLiveRedeploy(token); + + const result = await runRedeploy({ + env, + redeploy, + appendSummary, + services, + }); + console.log( + `\n${result.succeeded}/${result.attempted} redeploys triggered (${result.failed} failed)`, + ); + process.exit(result.exitCode); +} + +if (process.argv[1] === fileURLToPath(import.meta.url)) { + main().catch((e) => { + console.error(e); + // Fail loud on operator/config errors (bad env name, unknown service, + // missing/malformed token, parseArgs rejection, etc.). Per-service + // Railway failures are caught INSIDE runRedeploy and reflected in + // result.exitCode — they never reach this catch — so reaching here + // means something is wrong with how the script was invoked or + // configured, and CI should see a red run instead of a silent no-op. + process.exit(1); + }); +} diff --git a/showcase/scripts/resolve-verify-matrix.ts b/showcase/scripts/resolve-verify-matrix.ts new file mode 100644 index 00000000000..1a4f61b2680 --- /dev/null +++ b/showcase/scripts/resolve-verify-matrix.ts @@ -0,0 +1,449 @@ +/** + * resolve-verify-matrix.ts — decide which staging services the + * post-redeploy verify probe should target. + * + * Replaces the inline bash+jq block in showcase_deploy.yml's + * `resolve-matrix` job ("Build verify matrix from SSOT" step). The bash + * had produced two confirmed bugs across prior CR rounds, so the logic + * was extracted into a pure, unit-testable TypeScript function. + * + * Decision table — `resolveVerifyMatrix` returns `{servicesCsv, hasServices}`: + * + * workflow_dispatch + service='all'/empty + * → full probe-eligible set (every SSOT service with probe.staging===true), + * sorted+dedup'd. has_services=true. + * + * workflow_dispatch + specific service (SSOT key OR dispatchName) + * → just that one canonical name. Unknown service → throws (CLI wrapper + * exits non-zero with `::error::` annotation, matching prior bash). + * + * workflow_run + summary_present='false' + * → has_services=false, services_csv=''. Nothing was redeployed (build + * legitimately had no buildable changes); skip verify. + * + * workflow_run + summary_present='true' + ok_services empty + * → has_services=false, services_csv=''. ALL services errored on + * redeploy → the success-set is empty → nothing left to verify. + * `enforce-redeploy-gate` independently reds the workflow on + * redeploy_red=true, so this case is already loud. The PRIOR bash + * fell through to the full probe-eligible fleet here, gratuitously + * probing every service against stale `:latest`; that was the + * "Issue A" bug this module fixes. + * + * workflow_run + summary_present='true' + ok_services non-empty + * → intersect ok_services with probe-eligible SSOT services. The + * ok_services CSV may carry SSOT keys OR dispatchName aliases; + * both spellings resolve to the canonical name. Result is sorted, + * dedup'd, CSV-joined. has_services=(csv non-empty). + * + * Testability: the pure function takes `ssotServices` as a parameter so + * tests run without filesystem IO. The CLI wrapper at the bottom reads + * env vars, loads `railway-envs.generated.json` (regenerating via + * emit-railway-envs-json.ts if missing — preserves the fallback the + * old bash had), and writes `services_csv=` / `has_services=` lines to + * $GITHUB_OUTPUT. + */ +import { execFileSync } from "node:child_process"; +import { appendFileSync, existsSync, readFileSync } from "node:fs"; +import { fileURLToPath } from "node:url"; + +export interface SsotService { + name: string; + dispatchName: string | null; + probe: { staging: boolean }; +} + +/** Accepted github.event_name values for this resolver. */ +export type SupportedEventName = "workflow_run" | "workflow_dispatch"; + +export interface ResolveVerifyMatrixInput { + /** github.event_name — 'workflow_run' or 'workflow_dispatch'. */ + eventName: SupportedEventName; + /** 'true' / 'false' / '' — the upstream `check-redeploy-summary` step output. */ + summaryPresent: string; + /** CSV of services that redeployed OK (from `redeploy-gate.outputs.ok_services`). */ + okFromRedeploy: string; + /** github.event.inputs.service — 'all', '', or an SSOT key / dispatchName. */ + dispatchService: string; + /** The `services` array from railway-envs.generated.json. */ + ssotServices: SsotService[]; +} + +export interface ResolveVerifyMatrixOutput { + servicesCsv: string; + hasServices: boolean; +} + +/** Sorted, dedup'd list of every SSOT service with probe.staging===true. */ +function probeEligibleNames(ssotServices: SsotService[]): string[] { + // Shape is guaranteed by parseSsotServices (every service has a `probe` + // object with a boolean `staging`), so no defensive optional chain. + const names = ssotServices + .filter((s) => s.probe.staging === true) + .map((s) => s.name); + return Array.from(new Set(names)).sort(); +} + +/** + * Map an `ok_services` CSV (which may carry SSOT keys OR dispatchName + * aliases — the build matrix sometimes emits dispatch_names) into the + * set of canonical SSOT names. + * + * Tokens are .trim()'d before lookup so a future change to the + * redeploy-gate bash (or a human-typed workflow_dispatch caller) that + * emits "a, b" with spaces does not silently drop tokens. Unmatched + * tokens are returned in `dropped` so the CLI wrapper can surface them + * via `::warning::` — a silent drop here is the exact SSOT/build drift + * smell that the boundary hardening is supposed to detect. Empty tokens + * (trailing comma, double comma) are filtered before matching and are + * NOT reported as dropped (they carry no signal). + */ +export interface OkCsvResult { + canonical: Set; + dropped: string[]; +} + +export function okCsvToCanonicalNames( + okFromRedeploy: string, + ssotServices: SsotService[], +): OkCsvResult { + const canonical = new Set(); + const dropped: string[] = []; + const tokens = okFromRedeploy + .split(",") + .map((t) => t.trim()) + .filter((t) => t.length > 0); + for (const t of tokens) { + const match = ssotServices.find( + (s) => s.name === t || s.dispatchName === t, + ); + if (match) { + canonical.add(match.name); + } else { + dropped.push(t); + } + } + return { canonical, dropped }; +} + +export function resolveVerifyMatrix( + input: ResolveVerifyMatrixInput, +): ResolveVerifyMatrixOutput { + const { + eventName, + summaryPresent, + okFromRedeploy, + dispatchService, + ssotServices, + } = input; + + // Fail loud on unrecognized eventName. Today only `workflow_run` and + // `workflow_dispatch` reach this resolver (showcase_deploy.yml's `on:` + // block is closed to those two). Any other value here means either a + // typo in the wrapper's env wiring or a new trigger that hasn't been + // explicitly handled — the prior fall-through to the workflow_run + // intersection branch silently emitted has_services=false for both, + // indistinguishable from a legit "no summary, nothing to verify" skip. + // + // The CLI wrapper narrows EVENT_NAME via `asSupportedEventName` before + // calling here, so on the CLI path this guard is defense-in-depth. + // Direct test callers (which build their own input object) still pay + // the runtime check, which is the point. + if (eventName !== "workflow_run" && eventName !== "workflow_dispatch") { + throw new Error( + `::error::resolve-verify-matrix: unexpected eventName '${eventName}' (expected 'workflow_run' or 'workflow_dispatch')`, + ); + } + + // Make the workflow_run boundary total. `check-redeploy-summary` + // always sets `summary_present` to exactly "true" or "false"; any other + // value here (including "" from a future step-id-rename wiring break, + // or "True" from a case-typo) means the wiring is broken upstream, NOT + // a legitimate skip. Without this guard, an empty/garbage summaryPresent + // falls through to the workflow_run intersection branch — which silently + // emits has_services=false on what may be a real redeploy. Throw instead + // so enforce-redeploy-gate (which fans in on resolve-matrix.result == + // 'failure') reds the workflow. + // workflow_dispatch ignores summaryPresent and must NOT trip this guard. + if ( + eventName === "workflow_run" && + summaryPresent !== "true" && + summaryPresent !== "false" + ) { + throw new Error( + `::error::resolve-verify-matrix: workflow_run requires summary_present in {true,false}, got '${summaryPresent}'`, + ); + } + + // Case: workflow_run + nothing redeployed — the build legitimately + // didn't redeploy anything (no buildable service changed). Skip verify. + if (eventName === "workflow_run" && summaryPresent === "false") { + return { servicesCsv: "", hasServices: false }; + } + + // Case: workflow_run + redeploy summary PRESENT + empty ok_services + // (every service errored on redeploy). The success-set is empty, so + // there is nothing to verify. `enforce-redeploy-gate` already reds + // the workflow on redeploy_red=true; this matrix simply skips. This + // is the "Issue A" fix: the old bash fell through to the full + // probe-eligible fleet here. + if ( + eventName === "workflow_run" && + summaryPresent === "true" && + okFromRedeploy.length === 0 + ) { + return { servicesCsv: "", hasServices: false }; + } + + const probeEligible = probeEligibleNames(ssotServices); + + // workflow_dispatch: resolve the dispatch input first. If 'all' / empty, + // fall through to the full probe-eligible set. Otherwise resolve to + // the chosen service (by SSOT name OR dispatchName). + if (eventName === "workflow_dispatch") { + const dispatch = dispatchService || "all"; + if (dispatch !== "all") { + const resolved = ssotServices.find( + (s) => s.name === dispatch || s.dispatchName === dispatch, + ); + if (!resolved) { + // Preserves prior bash behavior: `::error::Unknown service...` + + // exit 1. The CLI wrapper converts this throw into a non-zero + // exit with the same annotation. + throw new Error( + `::error::Unknown service '${dispatch}' (not an SSOT key or dispatch_name)`, + ); + } + const csv = resolved.name; + return { servicesCsv: csv, hasServices: csv.length > 0 }; + } + const csv = probeEligible.join(","); + return { servicesCsv: csv, hasServices: csv.length > 0 }; + } + + // workflow_run + summary present + ok non-empty: intersect ok with + // probe-eligible. Map ok-tokens to canonical names first (handles the + // dispatchName-alias case). Drop anything not in probe-eligible (e.g. + // a service that redeployed OK but has no probe driver). + const { canonical: okCanonical } = okCsvToCanonicalNames( + okFromRedeploy, + ssotServices, + ); + const intersection = probeEligible.filter((n) => okCanonical.has(n)); + const csv = intersection.join(","); + return { servicesCsv: csv, hasServices: csv.length > 0 }; +} + +// --------------------------------------------------------------------------- +// CLI wrapper — guarded by import.meta.url check so tests can import the +// pure function without triggering env reads or filesystem IO. +// --------------------------------------------------------------------------- + +const SSOT_JSON = "showcase/scripts/railway-envs.generated.json"; +const EMIT_SCRIPT = "showcase/scripts/emit-railway-envs-json.ts"; + +/** + * Validate the parsed SSOT JSON shape. Two distinct failure modes to + * guard against: + * - SCHEMA DRIFT (e.g. someone renamed `probe.staging`): the JSON + * parses fine but silently empties the probe-eligible set, which + * the resolver would otherwise propagate as a false-green skip. + * - TRUNCATION (emitter crashed mid-write): either fails `JSON.parse` + * outright (loud) or — if the truncation happened to land on a + * valid-JSON boundary — leaves an empty `services` array, caught + * here by the non-empty check. + * We require the exact shape we depend on, and `::error::`-prefix the + * throw so the workflow log surfaces it as an annotation. + */ +export function parseSsotServices(raw: unknown, path: string): SsotService[] { + if (raw === null || typeof raw !== "object") { + throw new Error( + `::error::SSOT ${path} malformed: top-level value is not an object`, + ); + } + const services = (raw as { services?: unknown }).services; + if (!Array.isArray(services)) { + throw new Error( + `::error::SSOT ${path} malformed: \`services\` is not an array`, + ); + } + if (services.length === 0) { + throw new Error( + `::error::SSOT ${path} malformed: \`services\` is empty (emitter crashed mid-write or schema drift?)`, + ); + } + const validated: SsotService[] = []; + for (let i = 0; i < services.length; i++) { + const s = services[i]; + if (s === null || typeof s !== "object") { + throw new Error( + `::error::SSOT ${path} malformed: services[${i}] is not an object`, + ); + } + const obj = s as { + name?: unknown; + dispatchName?: unknown; + probe?: unknown; + }; + if (typeof obj.name !== "string" || obj.name.length === 0) { + throw new Error( + `::error::SSOT ${path} malformed: services[${i}] missing \`name\` (or not a non-empty string)`, + ); + } + // dispatchName is OPTIONAL in the live SSOT (e.g. `pocketbase` has + // no dispatchName field — it is not a CI-built service). Accept + // missing OR null OR string; normalize to null internally. + if ( + obj.dispatchName !== undefined && + obj.dispatchName !== null && + typeof obj.dispatchName !== "string" + ) { + throw new Error( + `::error::SSOT ${path} malformed: services[${i}] (${obj.name}) \`dispatchName\` must be string, null, or absent`, + ); + } + if (obj.probe === null || typeof obj.probe !== "object") { + throw new Error( + `::error::SSOT ${path} malformed: services[${i}] (${obj.name}) missing \`probe\` object`, + ); + } + const probe = obj.probe as { staging?: unknown }; + if (typeof probe.staging !== "boolean") { + throw new Error( + `::error::SSOT ${path} malformed: services[${i}] (${obj.name}) \`probe.staging\` is not boolean`, + ); + } + validated.push({ + name: obj.name, + dispatchName: + typeof obj.dispatchName === "string" ? obj.dispatchName : null, + probe: { staging: probe.staging }, + }); + } + return validated; +} + +function loadSsotServices(): SsotService[] { + // Preserve the prior bash behavior: regenerate the JSON if it's missing + // (e.g. a freshly-checked-out workspace that hasn't run the emitter). + if (!existsSync(SSOT_JSON)) { + execFileSync("npx", ["tsx", EMIT_SCRIPT], { stdio: "inherit" }); + } + // Re-check existence after the regen attempt: if the emitter exits 0 + // without writing (transient race, broken IO, mis-configured cwd), the + // pre-fix code would JSON.parse a ReadFile-against-missing-path error + // and fail with a useless stack. Fail loud instead. + if (!existsSync(SSOT_JSON)) { + throw new Error( + `::error::SSOT ${SSOT_JSON} still missing after regenerate attempt (${EMIT_SCRIPT} exited 0 without writing?)`, + ); + } + const raw: unknown = JSON.parse(readFileSync(SSOT_JSON, "utf-8")); + return parseSsotServices(raw, SSOT_JSON); +} + +function requireEnv(name: string): string { + const v = process.env[name]; + if (typeof v !== "string") { + throw new Error(`resolve-verify-matrix: $${name} is required`); + } + return v; +} + +/** + * Narrow a raw env-string EVENT_NAME into the resolver's literal union. + * Replaces the prior `as 'workflow_run' | 'workflow_dispatch'` unchecked + * cast — the type system and runtime now tell the same story. The + * resolver itself ALSO guards eventName, but doing the narrowing here + * means the CLI path fails loud at the boundary with a wrapper-specific + * `::error::` annotation (EVENT_NAME, not eventName) before the resolver + * is even called. + */ +function asSupportedEventName(s: string): SupportedEventName { + if (s !== "workflow_run" && s !== "workflow_dispatch") { + throw new Error( + `::error::resolve-verify-matrix: unexpected EVENT_NAME '${s}' (expected 'workflow_run' or 'workflow_dispatch')`, + ); + } + return s; +} + +function writeGithubOutput( + githubOutput: string, + servicesCsv: string, + hasServices: boolean, +): void { + // Plain key=value lines (no heredoc): both values are simple strings + // without newlines. Matches the style of the prior bash and aligns + // with the rest of resolve-matrix's $GITHUB_OUTPUT writes. + appendFileSync(githubOutput, `services_csv=${servicesCsv}\n`); + appendFileSync( + githubOutput, + `has_services=${hasServices ? "true" : "false"}\n`, + ); +} + +function main(): void { + const githubOutput = requireEnv("GITHUB_OUTPUT"); + const eventNameRaw = requireEnv("EVENT_NAME"); + const summaryPresent = process.env.SUMMARY_PRESENT ?? ""; + const okFromRedeploy = process.env.OK_FROM_REDEPLOY ?? ""; + const dispatchService = process.env.DISPATCH_SERVICE ?? ""; + + try { + // Narrow the raw env-string to the resolver's literal union. Replaces + // the prior unchecked `as` cast — the narrowing helper throws on + // unexpected EVENT_NAME with a wrapper-specific `::error::` + // annotation, so the type system and runtime tell the same story. + // The resolver's internal eventName guard becomes defense-in-depth + // for direct (test) callers that construct input objects by hand. + const eventName: SupportedEventName = asSupportedEventName(eventNameRaw); + + const ssotServices = loadSsotServices(); + + // Surface SSOT/build drift via ::warning:: when an ok_services token + // matches no SSOT service. The resolver-level intersection already + // drops the token; without this log, the operator never learns that + // a redeploy reported success for a service the SSOT has forgotten + // about (or vice versa). Keep this in the wrapper (not the pure + // function) so the resolver stays IO-free. + const okPreview = okCsvToCanonicalNames(okFromRedeploy, ssotServices); + if (okPreview.dropped.length > 0) { + process.stderr.write( + `::warning::ok_services tokens dropped (no SSOT match): ${okPreview.dropped.join(",")}\n`, + ); + } + + const { servicesCsv, hasServices } = resolveVerifyMatrix({ + eventName, + summaryPresent, + okFromRedeploy, + dispatchService, + ssotServices, + }); + writeGithubOutput(githubOutput, servicesCsv, hasServices); + } catch (e) { + // Mirror the prior bash: print `::error::...` to stderr and exit 1. + // Throws from this module already carry the `::error::` annotation + // (Unknown service, unexpected eventName, SSOT malformed, SSOT + // missing-after-regen). + process.stderr.write(`${e instanceof Error ? e.message : String(e)}\n`); + process.exit(1); + } +} + +// Detect "this module is the entrypoint" — used to gate the CLI bottom +// half so tests can `import` the pure resolver without triggering env +// reads or filesystem IO. Intentionally NO try/catch: an ESM-interop +// failure here (e.g. `fileURLToPath` rejects `import.meta.url`) used to +// silently return false → CLI no-ops → $GITHUB_OUTPUT never written → +// downstream `verify:` step skips (false-green). Let it crash loud +// instead; the workflow surfaces the stack and the gate stays honest. +const invokedDirectly = + typeof process !== "undefined" && + Array.isArray(process.argv) && + process.argv[1] === fileURLToPath(import.meta.url); + +if (invokedDirectly) { + main(); +} diff --git a/showcase/scripts/verify-deploy.drivers.agent.ts b/showcase/scripts/verify-deploy.drivers.agent.ts new file mode 100644 index 00000000000..45e3b9ddc0e --- /dev/null +++ b/showcase/scripts/verify-deploy.drivers.agent.ts @@ -0,0 +1,23 @@ +import type { ProbeTarget } from "./verify-deploy"; +import type { ProbeOutcome } from "./verify-deploy.drivers"; +import { probeBaseline } from "./verify-deploy.drivers.baseline"; + +/** + * Feature-level verifier for the generic agent backends — every + * `showcase-` integration service routes through this driver + * (mastra, ag2, agno, llamaindex, langgraph-*, crewai-*, pydantic-ai, + * google-adk, claude-sdk-*, ms-agent-*, langroid, spring-ai, strands, + * built-in-agent — 19 services in the SSOT at WS-C completion). + * + * Baseline (this commit): Railway deployment-SUCCESS + HTTP 200 on + * `/api/health` (the uniform health path emitted by every showcase + * integration template). Future driver-specific layer: feature-level + * fixture POST into the integration's `/api` endpoint, asserting the + * AG-UI SSE stream opens and emits at least one TextMessageStart event. + */ +export async function probeAgent(target: ProbeTarget): Promise { + return probeBaseline(target, { + driverLabel: "agent", + healthcheckPath: "/api/health", + }); +} diff --git a/showcase/scripts/verify-deploy.drivers.aimock.ts b/showcase/scripts/verify-deploy.drivers.aimock.ts new file mode 100644 index 00000000000..077618ad956 --- /dev/null +++ b/showcase/scripts/verify-deploy.drivers.aimock.ts @@ -0,0 +1,20 @@ +import type { ProbeTarget } from "./verify-deploy"; +import type { ProbeOutcome } from "./verify-deploy.drivers"; +import { probeBaseline } from "./verify-deploy.drivers.baseline"; + +/** + * Feature-level verifier for the `aimock` service (the showcase + * wrapper image baked with fixtures). + * + * Baseline (this commit): Railway deployment-SUCCESS + HTTP 200 on + * `/health` (mirrors the `health_path` for `showcase-aimock` in + * `.github/workflows/showcase_deploy.yml`). Future driver-specific + * layer: fixture replay with deterministic-response drift check + * (POST a known prompt, assert recorded response shape). + */ +export async function probeAimock(target: ProbeTarget): Promise { + return probeBaseline(target, { + driverLabel: "aimock", + healthcheckPath: "/health", + }); +} diff --git a/showcase/scripts/verify-deploy.drivers.baseline.ts b/showcase/scripts/verify-deploy.drivers.baseline.ts new file mode 100644 index 00000000000..303f7228ff6 --- /dev/null +++ b/showcase/scripts/verify-deploy.drivers.baseline.ts @@ -0,0 +1,404 @@ +import fs from "fs"; +import path from "path"; +import type { ProbeTarget } from "./verify-deploy"; +import type { ProbeOutcome } from "./verify-deploy.drivers"; +import { PRODUCTION_ENV_ID, SERVICES, STAGING_ENV_ID } from "./railway-envs"; +import type { EnvName } from "./railway-envs"; +import { RAILWAY_GRAPHQL_ENDPOINT } from "./lib/railway-graphql"; +import { resolveRailwayTokenFromConfig } from "./lib/railway-token"; + +/** + * Shared baseline implementation for every `verify-deploy` driver. Every + * driver must enforce the same two minimum invariants before any + * driver-specific feature-level checks run: + * + * 1. **deployment-SUCCESS** — query Railway GraphQL + * (`deployments(first:1, input:{serviceId, environmentId})`) for the + * service's latest deployment in the target env, and assert + * `status === "SUCCESS"`. This catches the "Railway accepted the + * image but the container crash-loops" case that a naked HTTP probe + * can miss (Railway briefly serves the previous good deploy via + * sticky routing). + * 2. **HTTP 200** — GET `https://` and assert + * `res.status === 200`. + * + * Each driver wraps `probeBaseline` with its own `driverLabel` and a + * sensible `healthcheckPath` for that service shape (Next.js shells use + * `/`, agent backends use `/api/health`, etc.; matches the Railway + * healthcheck config set by `deploy-to-railway.ts`). The + * "200 ≠ healthy" rule is still owed to the per-driver feature-level + * extensions (DOM string, fixture replay, admin login, etc.) — + * `probeBaseline` is the floor, not the ceiling. + * + * Network seams (`fetchImpl`, `getRailwayToken`) are injected so tests + * can run fully offline. Production callers omit them and get the real + * `globalThis.fetch` + the `~/.railway/config.json` resolver. + */ + +export interface RailwayDeploymentNode { + id: string; + status: string; +} + +export interface RailwayDeploymentsResponse { + data?: { + deployments?: { + edges?: Array<{ node?: RailwayDeploymentNode }>; + }; + }; + errors?: Array<{ message: string }>; +} + +export type FetchLike = ( + input: string, + init?: { + method?: string; + headers?: Record; + body?: string; + signal?: AbortSignal; + }, +) => Promise<{ + ok: boolean; + status: number; + text(): Promise; + json(): Promise; + /** + * Optional WHATWG body — exposed so we can drain/cancel it after + * reading status. Undici (Node's fetch impl) leaks sockets when the + * body is not consumed or cancelled. Test seams that return a plain + * stub omit this field; production fetch always populates it. + */ + body?: { cancel?: () => Promise } | null; +}>; + +function isAbortError(e: unknown): boolean { + if (!e || typeof e !== "object") return false; + const name = (e as { name?: unknown }).name; + return name === "AbortError"; +} + +/** + * Drain/cancel an HTTP response body so undici releases the socket. + * Safe on stubs (test seams) that lack a body — we only cancel when + * the runtime supplies one. + */ +async function releaseBody(res: Awaited>): Promise { + try { + await res.body?.cancel?.(); + } catch (e: unknown) { + // Expected benign case: undici throws when the body is already + // "locked" (a reader is attached, or it was fully read by an + // earlier `res.json()` / `res.text()`). In every such case the + // socket is already released, so this is a no-op — swallow it. + // Anything else is unexpected; surface it on stderr so we don't + // hide a real bug, but keep `releaseBody` best-effort (never + // propagate — undici socket release is an optimization, not a + // correctness invariant). + const msg = e instanceof Error ? e.message : String(e); + const locked = /lock/i.test(msg); + if (!locked) { + process.stderr.write( + `[verify-deploy] releaseBody: unexpected cancel error: ${msg}\n`, + ); + } + } +} + +export interface BaselineOpts { + /** Short driver-name tag woven into every error string for grep-ability. */ + driverLabel: string; + /** Path appended to `https://` for the healthcheck GET. */ + healthcheckPath: string; + /** + * Test seam — replaces `globalThis.fetch` for BOTH the Railway + * GraphQL call and the healthcheck call. Production callers omit + * this and get the real `fetch`. + */ + fetchImpl?: FetchLike; + /** + * Test seam — provides the Railway bearer used for the GraphQL + * call. When omitted, the real resolver walks `RAILWAY_TOKEN` env + * var → `~/.railway/config.json`. Returns `undefined` when no + * usable credential is present; the driver fails loud at that + * point rather than hitting Railway unauthenticated. + */ + getRailwayToken?: () => string | undefined; + /** Per-call timeout for each fetch (ms). Default 30s. */ + timeoutMs?: number; +} + +const DEFAULT_TIMEOUT_MS = 30_000; + +/** + * Walk `RAILWAY_TOKEN` env var → `~/.railway/config.json` and return the + * Railway public-GraphQL bearer. Mirrors the resolution chain in + * `redeploy-env.ts::getToken` but returns `undefined` on miss instead of + * exiting the process — driver code surfaces the miss as a probe + * failure so verify-deploy can keep iterating remaining targets. + */ +export function defaultGetRailwayToken(): string | undefined { + if (process.env.RAILWAY_TOKEN) return process.env.RAILWAY_TOKEN; + const home = process.env.HOME; + if (!home) return undefined; + const configPath = path.join(home, ".railway", "config.json"); + if (!fs.existsSync(configPath)) return undefined; + let raw: string; + try { + raw = fs.readFileSync(configPath, "utf-8"); + } catch (err: unknown) { + // ENOENT is the legitimate "no config file" path (TOCTOU between + // existsSync and readFileSync) — return undefined silently. + // Any OTHER error (EACCES, EISDIR, EIO, ...) is a configuration + // problem the operator needs to see; do NOT swallow it. Mirrors + // the read-vs-parse split in lib/railway-token.ts::resolveRailwayToken + // (NO_FILE vs MALFORMED) — here we keep returning undefined so the + // caller's "no token" failure path still fires, but with a clear + // stderr diagnostic identifying the offending config path. + const code = (err as NodeJS.ErrnoException | undefined)?.code; + if (code === "ENOENT") return undefined; + const msg = err instanceof Error ? err.message : String(err); + process.stderr.write( + `[verify-deploy] failed to read Railway config at ${configPath}: ${msg}\n`, + ); + return undefined; + } + let config: unknown; + try { + config = JSON.parse(raw); + } catch (err: unknown) { + // Malformed JSON is distinct from a missing file — surface the + // diagnostic, then return undefined so the caller's no-token path + // produces a clean probe failure rather than crashing verify-deploy. + const msg = err instanceof Error ? err.message : String(err); + process.stderr.write( + `[verify-deploy] malformed JSON in Railway config at ${configPath}: ${msg}\n`, + ); + return undefined; + } + return resolveRailwayTokenFromConfig( + config as Parameters[0], + ); +} + +/** + * Resolve the target env (`prod` / `staging`) for a probe target by + * matching its `host` against the SSOT's per-env domain literals. + * + * `ProbeTarget` intentionally does NOT carry the env (verify-deploy's + * `resolveProbeTargets` collapses it into the host literal so drivers + * cannot accidentally probe one env with the other env's token). We + * recover the env here by reversing the lookup against `SERVICES`. A + * service whose host matches neither env literal is a configuration + * bug — surface it as a probe failure, do not guess. + */ +export function envForTarget(target: ProbeTarget): EnvName | undefined { + const entry = SERVICES[target.name]; + if (!entry) return undefined; + if (target.host === entry.domains.staging) return "staging"; + if (target.host === entry.domains.prod) return "prod"; + return undefined; +} + +function envIdFor(env: EnvName): string { + return env === "prod" ? PRODUCTION_ENV_ID : STAGING_ENV_ID; +} + +async function fetchWithTimeout( + fetchImpl: FetchLike, + url: string, + init: Parameters[1], + timeoutMs: number, +): Promise>> { + const controller = new AbortController(); + const timer = setTimeout(() => controller.abort(), timeoutMs); + try { + return await fetchImpl(url, { ...init, signal: controller.signal }); + } finally { + clearTimeout(timer); + } +} + +/** + * Query Railway for the latest deployment of the given service in the + * given env and assert `status === "SUCCESS"`. Returns a string error + * message on any failure (network, GraphQL `errors[]`, missing edge, + * non-SUCCESS status); returns `undefined` on success. + */ +export async function checkDeploymentSuccess( + serviceId: string, + env: EnvName, + token: string, + fetchImpl: FetchLike, + timeoutMs: number, + driverLabel: string, + serviceName?: string, +): Promise { + const environmentId = envIdFor(env); + // Tag identifies the offending service in multi-service runs. When + // the caller does not supply a name we fall back to the serviceId so + // a Railway operator can still grep the diagnostic to a target. + const tag = serviceName + ? `service="${serviceName}" (serviceId=${serviceId})` + : `serviceId=${serviceId}`; + const query = `query latestDeployment($serviceId: String!, $environmentId: String!) { + deployments(first: 1, input: { serviceId: $serviceId, environmentId: $environmentId }) { + edges { node { id status } } + } +}`; + let res: Awaited>; + try { + res = await fetchWithTimeout( + fetchImpl, + RAILWAY_GRAPHQL_ENDPOINT, + { + method: "POST", + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + query, + variables: { serviceId, environmentId }, + }), + }, + timeoutMs, + ); + } catch (e: unknown) { + const msg = isAbortError(e) + ? `timed out after ${timeoutMs}ms` + : e instanceof Error + ? e.message + : String(e); + return `${driverLabel}: Railway GraphQL fetch failed [${tag}]: ${msg}`; + } + if (!res.ok) { + const body = (await res.text()).slice(0, 200); + return `${driverLabel}: Railway GraphQL HTTP ${res.status} [${tag}]: ${body}`; + } + let json: RailwayDeploymentsResponse; + try { + json = (await res.json()) as RailwayDeploymentsResponse; + } catch (e: unknown) { + const msg = e instanceof Error ? e.message : String(e); + // Release the body in the error path even if json() partially + // consumed it — undici will leak the socket otherwise. + await releaseBody(res); + return `${driverLabel}: Railway GraphQL JSON parse failed [${tag}]: ${msg}`; + } + if (json.errors?.length) { + return `${driverLabel}: Railway GraphQL errors [${tag}]: ${json.errors + .map((e) => e.message) + .join("; ")}`; + } + const node = json.data?.deployments?.edges?.[0]?.node; + if (!node) { + return `${driverLabel}: Railway returned no deployments for ${env} [${tag}]`; + } + if (node.status !== "SUCCESS") { + return `${driverLabel}: latest ${env} deployment status="${node.status}" (expected SUCCESS) [${tag}]`; + } + return undefined; +} + +/** + * GET `https://` and assert HTTP 200. Returns an + * error string on any non-200 / fetch failure; `undefined` on success. + */ +export async function checkHealthcheck200( + host: string, + healthcheckPath: string, + fetchImpl: FetchLike, + timeoutMs: number, + driverLabel: string, +): Promise { + const url = `https://${host}${healthcheckPath}`; + let res: Awaited>; + try { + res = await fetchWithTimeout( + fetchImpl, + url, + { method: "GET", headers: { "User-Agent": "verify-deploy" } }, + timeoutMs, + ); + } catch (e: unknown) { + // The AbortController abort surfaces as a generic "The operation + // was aborted" — substitute an actionable, timeout-aware message. + const msg = isAbortError(e) + ? `timed out after ${timeoutMs}ms` + : e instanceof Error + ? e.message + : String(e); + return `${driverLabel}: healthcheck GET ${url} failed: ${msg}`; + } + // We only need the status, not the body — drain/cancel it so undici + // releases the socket. Applies on BOTH the 200 and non-200 branches. + if (res.status !== 200) { + await releaseBody(res); + return `${driverLabel}: healthcheck GET ${url} returned HTTP ${res.status} (expected 200)`; + } + await releaseBody(res); + return undefined; +} + +/** + * Baseline driver body. Runs deployment-SUCCESS check, then healthcheck + * 200 check. Either failure yields a structured ProbeOutcome `{ok:false, + * error}`; both passes yield `{ok:true}`. Driver-specific extensions + * (DOM strings, fixture replay, admin login, etc.) can compose on top + * by wrapping this and adding their own checks after a green baseline. + */ +export async function probeBaseline( + target: ProbeTarget, + opts: BaselineOpts, +): Promise { + const fetchImpl: FetchLike = + opts.fetchImpl ?? (globalThis.fetch as unknown as FetchLike); + const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS; + const getToken = opts.getRailwayToken ?? defaultGetRailwayToken; + + const env = envForTarget(target); + if (!env) { + return { + ok: false, + error: `${opts.driverLabel}: cannot resolve env for host "${target.host}" (service "${target.name}" not in SSOT or domain mismatch)`, + }; + } + const entry = SERVICES[target.name]; + // envForTarget already validated entry exists by returning a defined env. + if (!entry) { + return { + ok: false, + error: `${opts.driverLabel}: service "${target.name}" missing from SSOT`, + }; + } + + const token = getToken(); + if (!token) { + return { + ok: false, + error: `${opts.driverLabel}: no Railway token (set RAILWAY_TOKEN — Railway workspace token)`, + }; + } + + const deployErr = await checkDeploymentSuccess( + entry.serviceId, + env, + token, + fetchImpl, + timeoutMs, + opts.driverLabel, + target.name, + ); + if (deployErr) return { ok: false, error: deployErr }; + + const healthErr = await checkHealthcheck200( + target.host, + opts.healthcheckPath, + fetchImpl, + timeoutMs, + opts.driverLabel, + ); + if (healthErr) return { ok: false, error: healthErr }; + + return { ok: true }; +} diff --git a/showcase/scripts/verify-deploy.drivers.dashboard.ts b/showcase/scripts/verify-deploy.drivers.dashboard.ts new file mode 100644 index 00000000000..0d6fb351267 --- /dev/null +++ b/showcase/scripts/verify-deploy.drivers.dashboard.ts @@ -0,0 +1,19 @@ +import type { ProbeTarget } from "./verify-deploy"; +import type { ProbeOutcome } from "./verify-deploy.drivers"; +import { probeBaseline } from "./verify-deploy.drivers.baseline"; + +/** + * Feature-level verifier for the `shell-dashboard` Next.js service. + * + * Baseline (this commit): Railway deployment-SUCCESS + HTTP 200 on `/`. + * Future driver-specific layer: dashboard-DOM assertion (catches the + * "rendered the 404 chrome with 200" case Next.js dev quirks produce). + */ +export async function probeDashboard( + target: ProbeTarget, +): Promise { + return probeBaseline(target, { + driverLabel: "dashboard", + healthcheckPath: "/", + }); +} diff --git a/showcase/scripts/verify-deploy.drivers.docs.ts b/showcase/scripts/verify-deploy.drivers.docs.ts new file mode 100644 index 00000000000..1e8e5ccbb4e --- /dev/null +++ b/showcase/scripts/verify-deploy.drivers.docs.ts @@ -0,0 +1,19 @@ +import type { ProbeTarget } from "./verify-deploy"; +import type { ProbeOutcome } from "./verify-deploy.drivers"; +import { probeBaseline } from "./verify-deploy.drivers.baseline"; + +/** + * Feature-level verifier for the `shell-docs` Next.js service. + * + * Baseline (this commit): Railway deployment-SUCCESS + HTTP 200 on `/`. + * Future driver-specific layer: DOM-string assertion + the structural + * `
` + `

` heuristic from `probe-shell-docs.ts` so a 200 with + * the 404 chrome is still treated as red. That extension composes on + * top of `probeBaseline` once the per-driver micro-task lands. + */ +export async function probeDocs(target: ProbeTarget): Promise { + return probeBaseline(target, { + driverLabel: "docs", + healthcheckPath: "/", + }); +} diff --git a/showcase/scripts/verify-deploy.drivers.dojo.ts b/showcase/scripts/verify-deploy.drivers.dojo.ts new file mode 100644 index 00000000000..13ac43de244 --- /dev/null +++ b/showcase/scripts/verify-deploy.drivers.dojo.ts @@ -0,0 +1,17 @@ +import type { ProbeTarget } from "./verify-deploy"; +import type { ProbeOutcome } from "./verify-deploy.drivers"; +import { probeBaseline } from "./verify-deploy.drivers.baseline"; + +/** + * Feature-level verifier for the `shell-dojo` Next.js service. + * + * Baseline (this commit): Railway deployment-SUCCESS + HTTP 200 on `/`. + * Future driver-specific layer: dojo-DOM assertion + the AG-UI dojo + * landing's known network call. + */ +export async function probeDojo(target: ProbeTarget): Promise { + return probeBaseline(target, { + driverLabel: "dojo", + healthcheckPath: "/", + }); +} diff --git a/showcase/scripts/verify-deploy.drivers.eval.ts b/showcase/scripts/verify-deploy.drivers.eval.ts new file mode 100644 index 00000000000..324d737604d --- /dev/null +++ b/showcase/scripts/verify-deploy.drivers.eval.ts @@ -0,0 +1,25 @@ +import type { ProbeTarget } from "./verify-deploy"; +import type { ProbeOutcome } from "./verify-deploy.drivers"; +import { probeBaseline } from "./verify-deploy.drivers.baseline"; + +/** + * Feature-level verifier for `showcase-eval`. + * + * No SSOT entry currently sets `probe.driver === "eval"` (eval has not + * been wired into the SSOT yet), but the enum literal exists in + * `railway-envs.ts::ProbeDriver` and the dispatch switch in + * `verify-deploy.drivers.ts` routes to this function. Per the + * cross-workstream contract (the 10-literal enum is the surface), every + * literal must have a working baseline impl — so this is NOT a stub. + * + * Baseline (this commit): Railway deployment-SUCCESS + HTTP 200 on + * `/api/health` (the standard Next.js/Express health path used by every + * other API-shaped service in the SSOT). Future driver-specific layer: + * synthetic eval-run fixture call. + */ +export async function probeEval(target: ProbeTarget): Promise { + return probeBaseline(target, { + driverLabel: "eval", + healthcheckPath: "/api/health", + }); +} diff --git a/showcase/scripts/verify-deploy.drivers.harness.ts b/showcase/scripts/verify-deploy.drivers.harness.ts new file mode 100644 index 00000000000..7c0665609e5 --- /dev/null +++ b/showcase/scripts/verify-deploy.drivers.harness.ts @@ -0,0 +1,19 @@ +import type { ProbeTarget } from "./verify-deploy"; +import type { ProbeOutcome } from "./verify-deploy.drivers"; +import { probeBaseline } from "./verify-deploy.drivers.baseline"; + +/** + * Feature-level verifier for the `showcase-harness` API service. + * + * Baseline (this commit): Railway deployment-SUCCESS + HTTP 200 on + * `/health` (the harness's documented healthcheck path; mirrors the + * `health_path` table in `.github/workflows/showcase_deploy.yml`). + * Future driver-specific layer: synthetic e2e fixture call against + * the harness's `/run` API. + */ +export async function probeHarness(target: ProbeTarget): Promise { + return probeBaseline(target, { + driverLabel: "harness", + healthcheckPath: "/health", + }); +} diff --git a/showcase/scripts/verify-deploy.drivers.pocketbase.ts b/showcase/scripts/verify-deploy.drivers.pocketbase.ts new file mode 100644 index 00000000000..61e0f921efc --- /dev/null +++ b/showcase/scripts/verify-deploy.drivers.pocketbase.ts @@ -0,0 +1,19 @@ +import type { ProbeTarget } from "./verify-deploy"; +import type { ProbeOutcome } from "./verify-deploy.drivers"; +import { probeBaseline } from "./verify-deploy.drivers.baseline"; + +/** + * Feature-level verifier for the `pocketbase` service. + * + * Baseline (this commit): Railway deployment-SUCCESS + HTTP 200 on + * `/api/health` (PocketBase's standard health endpoint). Future + * driver-specific layer: admin login + known collection list assertion. + */ +export async function probePocketbase( + target: ProbeTarget, +): Promise { + return probeBaseline(target, { + driverLabel: "pocketbase", + healthcheckPath: "/api/health", + }); +} diff --git a/showcase/scripts/verify-deploy.drivers.shell.ts b/showcase/scripts/verify-deploy.drivers.shell.ts new file mode 100644 index 00000000000..088cf10a665 --- /dev/null +++ b/showcase/scripts/verify-deploy.drivers.shell.ts @@ -0,0 +1,24 @@ +import type { ProbeTarget } from "./verify-deploy"; +import type { ProbeOutcome } from "./verify-deploy.drivers"; +import { probeBaseline } from "./verify-deploy.drivers.baseline"; + +/** + * Feature-level verifier for the Next.js `shell` service (the main + * showcase shell). Per spec §3.5 ("200 ≠ healthy") the eventual goal + * is to load the page and assert a known DOM string + a known network + * call. The baseline implemented here is the floor every driver must + * meet: + * 1. Railway deployment-SUCCESS for the resolved env (GraphQL). + * 2. HTTP 200 on `https:///` (the shell's Next.js root). + * + * Driver-specific DOM + network-call extensions can compose on top of + * `probeBaseline` (run after a green baseline) when the per-driver + * micro-tasks land. Until then, this baseline is the agreed completion + * bar — and crucially, NO driver remains a "not yet implemented" stub. + */ +export async function probeShell(target: ProbeTarget): Promise { + return probeBaseline(target, { + driverLabel: "shell", + healthcheckPath: "/", + }); +} diff --git a/showcase/scripts/verify-deploy.drivers.ts b/showcase/scripts/verify-deploy.drivers.ts new file mode 100644 index 00000000000..e0e9572b701 --- /dev/null +++ b/showcase/scripts/verify-deploy.drivers.ts @@ -0,0 +1,80 @@ +import type { ProbeDriver } from "./railway-envs"; +import type { ProbeTarget } from "./verify-deploy"; + +export type ProbeOutcome = { ok: true } | { ok: false; error: string }; + +export type ProbeRunner = (target: ProbeTarget) => Promise; + +/** + * Per-driver verifier registry. Each entry MUST: + * 1. Hit the URL with a reasonable timeout (default 30s). + * 2. Assert a feature-level property — DOM string for shells, fixture + * replay for aimock, admin login for pocketbase, etc. + * 3. Return { ok: false, error } on ANY divergence, never throw across + * the boundary (runVerify catches throws but the driver is the right + * place to phrase errors). + * + * Individual driver implementations live in verify-deploy.drivers..ts + * and are imported here; this file only does the dispatch. The exhaustive + * `never` check at the bottom of the switch guarantees that adding a new + * ProbeDriver literal to railway-envs.ts triggers a compile error here + * until the new driver is wired up. + */ +export async function runDriver(target: ProbeTarget): Promise { + try { + switch (target.driver) { + case "shell": + return (await import("./verify-deploy.drivers.shell")).probeShell( + target, + ); + case "docs": + return (await import("./verify-deploy.drivers.docs")).probeDocs(target); + case "dashboard": + return ( + await import("./verify-deploy.drivers.dashboard") + ).probeDashboard(target); + case "dojo": + return (await import("./verify-deploy.drivers.dojo")).probeDojo(target); + case "harness": + return (await import("./verify-deploy.drivers.harness")).probeHarness( + target, + ); + case "eval": + return (await import("./verify-deploy.drivers.eval")).probeEval(target); + case "aimock": + return (await import("./verify-deploy.drivers.aimock")).probeAimock( + target, + ); + case "pocketbase": + return ( + await import("./verify-deploy.drivers.pocketbase") + ).probePocketbase(target); + case "webhooks": + return (await import("./verify-deploy.drivers.webhooks")).probeWebhooks( + target, + ); + case "agent": + return (await import("./verify-deploy.drivers.agent")).probeAgent( + target, + ); + default: { + const exhaustive: never = target.driver; + return { ok: false, error: `unknown driver: ${String(exhaustive)}` }; + } + } + } catch (e: unknown) { + // A missing/broken driver module (e.g. `Cannot find module ...`) + // or any other throw inside dynamic import must NOT escape — the + // doc-comment contract is "drivers never throw across the + // boundary". Wrap with the driver label so the diagnostic is + // actionable in a multi-service run. + const msg = e instanceof Error ? e.message : String(e); + return { ok: false, error: `runDriver(${target.driver}): ${msg}` }; + } +} + +/** + * Exported for tests that want to spy on driver dispatch without doing + * real network work. Each driver module is a tiny pure-fetch boundary. + */ +export type { ProbeDriver }; diff --git a/showcase/scripts/verify-deploy.drivers.webhooks.ts b/showcase/scripts/verify-deploy.drivers.webhooks.ts new file mode 100644 index 00000000000..9a67d37b725 --- /dev/null +++ b/showcase/scripts/verify-deploy.drivers.webhooks.ts @@ -0,0 +1,20 @@ +import type { ProbeTarget } from "./verify-deploy"; +import type { ProbeOutcome } from "./verify-deploy.drivers"; +import { probeBaseline } from "./verify-deploy.drivers.baseline"; + +/** + * Feature-level verifier for `showcase-webhooks` (eval webhook relay). + * + * Baseline (this commit): Railway deployment-SUCCESS + HTTP 200 on + * `/api/health` (matches the API-shaped service convention used by + * every other agent/relay service in the SSOT). Future driver-specific + * layer: synthetic event POST + downstream-fanout confirmation. + */ +export async function probeWebhooks( + target: ProbeTarget, +): Promise { + return probeBaseline(target, { + driverLabel: "webhooks", + healthcheckPath: "/api/health", + }); +} diff --git a/showcase/scripts/verify-deploy.ts b/showcase/scripts/verify-deploy.ts new file mode 100644 index 00000000000..d19fdff226b --- /dev/null +++ b/showcase/scripts/verify-deploy.ts @@ -0,0 +1,238 @@ +#!/usr/bin/env npx tsx +/** + * verify-deploy.ts — Parameterized per-env probe driven off + * railway-envs.ts SSOT. + * + * Usage: + * npx tsx showcase/scripts/verify-deploy.ts --env + * [--services ] + * + * Behavior: + * - Iterates SERVICES from the SSOT; for every entry where + * probe[env] === true, runs the per-driver feature-level verifier + * against domainFor(name, env). HTTP 200 is necessary, not sufficient. + * - Refuses to start if a probe-required service has no domain for + * the requested env (fail loud; no silent skip). + * - Exits 0 only when every probed service is green. Any red → exit 1. + * + * Drivers live in showcase/scripts/verify-deploy.drivers.ts and dispatch on + * ProbeDriver. The driver is feature-level (DOM string + known network call + * for shells; fixture replay for aimock; admin login for pocketbase; etc.). + */ + +import { runDriver } from "./verify-deploy.drivers"; +import type { ProbeRunner } from "./verify-deploy.drivers"; +import { SERVICES, domainFor, resolveEnv } from "./railway-envs"; +import type { EnvName, ProbeDriver } from "./railway-envs"; + +export interface ParsedArgs { + env: EnvName; + services?: string[]; +} + +export function parseArgs(argv: string[]): ParsedArgs { + let envRaw: string | undefined; + let services: string[] | undefined; + for (let i = 0; i < argv.length; i++) { + const a = argv[i]; + if (a === "--env") { + const v = argv[++i]; + // Guard a bare trailing `--env` (undefined) here so the + // operator gets a precise, flag-named diagnostic instead of + // the downstream `resolveEnv` "Unknown env" / "--env required" + // surface. Mirrors the same guard on `--services`. + if (v === undefined) { + throw new Error("--env requires a value (staging|prod)"); + } + envRaw = v; + } else if (a.startsWith("--env=")) { + const v = a.slice("--env=".length); + // `--env=` (empty post-equals) is the equals-form twin of the + // bare-trailing case above; collapse both to the same precise + // error rather than deferring to `resolveEnv`. + if (v === "") { + throw new Error("--env requires a value (staging|prod)"); + } + envRaw = v; + } else if (a === "--services") { + const v = argv[++i]; + if (!v) throw new Error("--services requires a CSV value"); + services = v + .split(",") + .map((s) => s.trim()) + .filter(Boolean); + // Symmetry with the equals-form below: a raw value like `,,` + // or `" "` survives the `!v` guard but produces an empty + // post-filter list. Throw the same precise error here so both + // forms behave identically. + if (services.length === 0) { + throw new Error("--services requires a CSV value"); + } + } else if (a.startsWith("--services=")) { + const v = a.slice("--services=".length); + if (!v) throw new Error("--services requires a CSV value"); + services = v + .split(",") + .map((s) => s.trim()) + .filter(Boolean); + if (services.length === 0) { + throw new Error("--services requires a CSV value"); + } + } else { + throw new Error(`Unknown argument: ${a}`); + } + } + if (!envRaw) { + throw new Error("--env is required (staging|prod)"); + } + const { env } = resolveEnv(envRaw); + return services === undefined ? { env } : { env, services }; +} + +export interface ProbeTarget { + name: string; + host: string; + driver: ProbeDriver; +} + +export interface ResolveOpts { + env: EnvName; + services?: string[]; + /** Test seam: shallow-merge a partial entry over the SSOT before resolve. */ + overrides?: Record; +} + +export function resolveProbeTargets(opts: ResolveOpts): ProbeTarget[] { + const targets: ProbeTarget[] = []; + const filter = opts.services ? new Set(opts.services) : undefined; + // Validate user-supplied service names against the SSOT BEFORE + // filtering — a typo (`docss`) or a name that's not probe-eligible + // for the target env must surface as a clear, distinct error, not a + // silent zero-targets vacuous green. + if (filter) { + for (const name of filter) { + const entry = SERVICES[name]; + if (!entry) { + throw new Error( + `unknown service "${name}" (not in SSOT). Run \`bin/showcase services\` to list known names.`, + ); + } + if (!entry.probe[opts.env]) { + throw new Error( + `service "${name}" is not probe-eligible for env "${opts.env}" (probe.${opts.env}=false in SSOT)`, + ); + } + } + } + for (const [name, entry] of Object.entries(SERVICES)) { + if (filter && !filter.has(name)) continue; + if (!entry.probe[opts.env]) continue; + const overrideDomains = opts.overrides?.[name]?.domains; + const host = overrideDomains + ? overrideDomains[opts.env] + : domainFor(name, opts.env); + if (!host) { + throw new Error( + `Service "${name}" is probe-required for env "${opts.env}" but is missing a ${opts.env} domain.`, + ); + } + targets.push({ name, host, driver: entry.probe.driver }); + } + return targets.sort((a, b) => a.name.localeCompare(b.name)); +} + +export interface VerifyOpts { + env: EnvName; + services?: string[]; + runner?: ProbeRunner; +} + +export interface VerifySummary { + env: EnvName; + passed: Array<{ name: string }>; + failed: Array<{ name: string; error: string }>; + exitCode: number; +} + +export async function runVerify(opts: VerifyOpts): Promise { + const targets = resolveProbeTargets({ + env: opts.env, + services: opts.services, + }); + const runner = opts.runner ?? runDriver; + const passed: Array<{ name: string }> = []; + const failed: Array<{ name: string; error: string }> = []; + + // Zero-targets is NEVER a success. A verify gate that prints + // "targets=0" and exits 0 is the worst outcome — a vacuous green. + // Fail loud with a clear diagnostic so the operator knows the run + // verified nothing. + if (targets.length === 0) { + const error = + `no probe-required services resolved for env "${opts.env}" — ` + + `check --services and SSOT probe flags`; + process.stdout.write(`verify-deploy --env=${opts.env} targets=0 (FAIL)\n`); + process.stdout.write(` ${error}\n`); + return { + env: opts.env, + passed, + failed: [{ name: "(zero-targets)", error }], + exitCode: 1, + }; + } + + process.stdout.write( + `verify-deploy --env=${opts.env} targets=${targets.length}\n`, + ); + + for (const target of targets) { + process.stdout.write(` ${target.name.padEnd(36)} ${target.host} `); + try { + const outcome = await runner(target); + if (outcome.ok) { + passed.push({ name: target.name }); + process.stdout.write("OK\n"); + } else { + failed.push({ name: target.name, error: outcome.error }); + process.stdout.write(`FAIL: ${outcome.error}\n`); + } + } catch (e: unknown) { + const msg = e instanceof Error ? e.message : String(e); + failed.push({ name: target.name, error: msg }); + process.stdout.write(`FAIL (threw): ${msg}\n`); + } + } + + return { + env: opts.env, + passed, + failed, + exitCode: failed.length === 0 ? 0 : 1, + }; +} + +async function main(): Promise { + const parsed = parseArgs(process.argv.slice(2)); + const summary = await runVerify(parsed); + if (summary.failed.length > 0) { + process.stderr.write( + `\n${summary.failed.length} service(s) failed verify in ${summary.env}:\n`, + ); + for (const f of summary.failed) { + process.stderr.write(` - ${f.name}: ${f.error}\n`); + } + } + process.exit(summary.exitCode); +} + +const isMain = process.argv[1]?.endsWith("verify-deploy.ts"); +if (isMain) { + main().catch((e) => { + process.stderr.write( + `verify-deploy crashed: ${e instanceof Error ? e.message : String(e)}\n`, + ); + process.exit(2); + }); +} + +export type { ProbeRunner } from "./verify-deploy.drivers"; diff --git a/showcase/scripts/verify-railway-image-refs.test.ts b/showcase/scripts/verify-railway-image-refs.test.ts new file mode 100644 index 00000000000..63910ec99fc --- /dev/null +++ b/showcase/scripts/verify-railway-image-refs.test.ts @@ -0,0 +1,306 @@ +import { describe, expect, it } from "vitest"; +import { + findMissingServices, + validateImage, +} from "./verify-railway-image-refs"; +import { SERVICES } from "./railway-envs"; + +const ALL_GATE_VALIDATED = Object.entries(SERVICES) + .filter(([, e]) => e.gateValidated) + .map(([name]) => name); + +describe("validateImage — production env", () => { + it("accepts a digest-pinned ghcr ref matching the service name", () => { + const v = validateImage( + "ghcr.io/copilotkit/showcase-mastra@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", + { env: "prod", repoName: "showcase-mastra" }, + ); + expect(v).toBeNull(); + }); + + it("rejects :latest in prod", () => { + const v = validateImage("ghcr.io/copilotkit/showcase-mastra:latest", { + env: "prod", + repoName: "showcase-mastra", + }); + expect(v).not.toBeNull(); + expect(v!.reason).toMatch(/digest/i); + }); + + it("rejects a digest with the wrong repo name in prod", () => { + const v = validateImage( + "ghcr.io/copilotkit/showcase-ag2@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", + { env: "prod", repoName: "showcase-mastra" }, + ); + expect(v).not.toBeNull(); + expect(v!.reason).toMatch(/repo name/i); + }); + + it("rejects a non-ghcr ref in prod", () => { + const v = validateImage( + "docker.io/library/nginx@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", + { env: "prod", repoName: "showcase-mastra" }, + ); + expect(v).not.toBeNull(); + expect(v!.reason).toMatch(/canonical shape/i); + }); + + it("rejects an unset image in prod", () => { + const v = validateImage(null, { env: "prod", repoName: "showcase-mastra" }); + expect(v).not.toBeNull(); + expect(v!.reason).toMatch(/no image/i); + }); + + it("honors the showcase-aimock wrapper override in prod", () => { + // Aimock prod is digest-pinned to the WRAPPER repo `showcase-aimock` + // (the fixture-baking wrapper is the permanent, canonical aimock + // showcase image in BOTH envs). The SSOT expresses this via + // repoNameOverride.prod = "showcase-aimock". + const v = validateImage( + "ghcr.io/copilotkit/showcase-aimock@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", + { env: "prod", repoName: "showcase-aimock" }, + ); + expect(v).toBeNull(); + }); + + it("rejects the unwrapped aimock repo in prod (wrapper is the canonical image)", () => { + // Inverse of the above: a digest pin against the unwrapped + // `aimock` repo in prod must be flagged as a repo-name mismatch, + // because the canonical aimock showcase image is the wrapper. + const v = validateImage( + "ghcr.io/copilotkit/aimock@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", + { env: "prod", repoName: "showcase-aimock" }, + ); + expect(v).not.toBeNull(); + expect(v!.reason).toMatch(/repo name/i); + }); +}); + +describe("validateImage — staging env", () => { + it("accepts :latest in staging", () => { + const v = validateImage("ghcr.io/copilotkit/showcase-mastra:latest", { + env: "staging", + repoName: "showcase-mastra", + }); + expect(v).toBeNull(); + }); + + it("rejects a digest in staging", () => { + const v = validateImage( + "ghcr.io/copilotkit/showcase-mastra@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", + { env: "staging", repoName: "showcase-mastra" }, + ); + expect(v).not.toBeNull(); + expect(v!.reason).toMatch(/:latest/); + }); + + it("rejects :latest with wrong repo name in staging", () => { + const v = validateImage("ghcr.io/copilotkit/showcase-ag2:latest", { + env: "staging", + repoName: "showcase-mastra", + }); + expect(v).not.toBeNull(); + expect(v!.reason).toMatch(/repo name/i); + }); + + it("rejects a non-ghcr ref in staging", () => { + const v = validateImage("docker.io/library/nginx:latest", { + env: "staging", + repoName: "showcase-mastra", + }); + expect(v).not.toBeNull(); + expect(v!.reason).toMatch(/not on ghcr\.io\/copilotkit/i); + }); + + it("honors the showcase-aimock wrapper override in staging too", () => { + // The aimock entry has the `showcase-aimock` wrapper override in BOTH + // envs (the fixture-baking wrapper is the permanent, canonical aimock + // showcase image — no migration narrative). Staging floats :latest + // on the wrapper repo and must validate cleanly; the bare unwrapped + // `aimock:latest` ref must be rejected as a repo-name mismatch. + const ok = validateImage("ghcr.io/copilotkit/showcase-aimock:latest", { + env: "staging", + repoName: "showcase-aimock", + }); + expect(ok).toBeNull(); + + const bad = validateImage("ghcr.io/copilotkit/aimock:latest", { + env: "staging", + repoName: "showcase-aimock", + }); + expect(bad).not.toBeNull(); + expect(bad!.reason).toMatch(/repo name/i); + }); +}); + +describe("validateImage — pocketbase (first-party, non-CI-built)", () => { + // pocketbase Railway service → ghcr.io/copilotkit/showcase-pocketbase + // override applies in BOTH envs. + + it("accepts the prod digest pin", () => { + const v = validateImage( + "ghcr.io/copilotkit/showcase-pocketbase@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", + { env: "prod", repoName: "showcase-pocketbase" }, + ); + expect(v).toBeNull(); + }); + + it("accepts the staging :latest tag", () => { + const v = validateImage("ghcr.io/copilotkit/showcase-pocketbase:latest", { + env: "staging", + repoName: "showcase-pocketbase", + }); + expect(v).toBeNull(); + }); + + it("rejects :latest in prod", () => { + const v = validateImage("ghcr.io/copilotkit/showcase-pocketbase:latest", { + env: "prod", + repoName: "showcase-pocketbase", + }); + expect(v).not.toBeNull(); + expect(v!.reason).toMatch(/digest/i); + }); +}); + +describe("validateImage — webhooks (first-party, non-CI-built)", () => { + // webhooks Railway service → ghcr.io/copilotkit/showcase-eval-webhook + // override applies in BOTH envs. + + it("accepts the prod digest pin", () => { + const v = validateImage( + "ghcr.io/copilotkit/showcase-eval-webhook@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", + { env: "prod", repoName: "showcase-eval-webhook" }, + ); + expect(v).toBeNull(); + }); + + it("accepts the staging :latest tag", () => { + const v = validateImage("ghcr.io/copilotkit/showcase-eval-webhook:latest", { + env: "staging", + repoName: "showcase-eval-webhook", + }); + expect(v).toBeNull(); + }); + + it("rejects :latest in prod", () => { + const v = validateImage("ghcr.io/copilotkit/showcase-eval-webhook:latest", { + env: "prod", + repoName: "showcase-eval-webhook", + }); + expect(v).not.toBeNull(); + expect(v!.reason).toMatch(/digest/i); + }); +}); + +describe("validateImage — generic non-canonical prod tags", () => { + // Per nit #5: a prod ref that is neither `:latest` nor `@sha256:` + // — e.g. a mutable arch-tag like `:latest-arm64` or a git-SHA tag like + // `:abc123` — must hit the "not canonical prod shape" branch (NOT the + // `:latest` branch). + + it("rejects a prod ref tagged with a non-latest arch suffix", () => { + const v = validateImage("ghcr.io/copilotkit/showcase-mastra:latest-arm64", { + env: "prod", + repoName: "showcase-mastra", + }); + expect(v).not.toBeNull(); + expect(v!.reason).toMatch(/canonical prod shape/i); + expect(v!.reason).not.toMatch(/^prod must be pinned to/); + }); + + it("rejects a prod ref tagged with a short git SHA", () => { + const v = validateImage("ghcr.io/copilotkit/showcase-mastra:abc123", { + env: "prod", + repoName: "showcase-mastra", + }); + expect(v).not.toBeNull(); + expect(v!.reason).toMatch(/canonical prod shape/i); + }); +}); + +describe("findMissingServices — coverage assertion", () => { + // The gate must fail loudly when a gateValidated SSOT service is + // missing from the Railway response. Today the main() loop silently + // skips missing services because it only iterates what Railway returns. + + it("returns [] when every gateValidated service is present", () => { + const present = new Set(ALL_GATE_VALIDATED); + expect(findMissingServices("prod", present)).toEqual([]); + expect(findMissingServices("staging", present)).toEqual([]); + }); + + it("returns the omitted gateValidated service name when one is missing", () => { + const omitted = "showcase-mastra"; + const present = new Set(ALL_GATE_VALIDATED.filter((n) => n !== omitted)); + expect(findMissingServices("prod", present)).toEqual([omitted]); + expect(findMissingServices("staging", present)).toEqual([omitted]); + }); + + it("returns multiple omitted names sorted, in either env", () => { + const omitted = ["showcase-mastra", "showcase-ag2", "pocketbase"]; + const present = new Set( + ALL_GATE_VALIDATED.filter((n) => !omitted.includes(n)), + ); + const expected = [...omitted].sort(); + expect(findMissingServices("prod", present)).toEqual(expected); + expect(findMissingServices("staging", present)).toEqual(expected); + }); + + it("does NOT require non-gateValidated SSOT entries (no false positives)", () => { + // dashboard/docs/dojo/harness/shell are SSOT entries but + // gateValidated:false — they must not be reported missing. + const present = new Set(ALL_GATE_VALIDATED); + const missing = findMissingServices("prod", present); + for (const nonGV of ["dashboard", "docs", "dojo", "harness", "shell"]) { + expect(missing).not.toContain(nonGV); + } + }); + + it("ignores unknown service names in the present set", () => { + // A Railway-side service unknown to SSOT must not affect coverage. + const present = new Set([...ALL_GATE_VALIDATED, "some-future-service"]); + expect(findMissingServices("prod", present)).toEqual([]); + }); +}); + +describe("validateImage — clearer staging violation messages (bucket b)", () => { + it("non-ghcr staging image identifies the wrong registry/repo", () => { + const v = validateImage("docker.io/library/nginx:latest", { + env: "staging", + repoName: "showcase-mastra", + }); + expect(v).not.toBeNull(); + expect(v!.reason).toMatch(/not on ghcr\.io\/copilotkit/i); + expect(v!.reason).toContain("docker.io/library/nginx:latest"); + }); + + it("staging digest pin says staging must float on :latest", () => { + const v = validateImage( + "ghcr.io/copilotkit/showcase-mastra@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", + { env: "staging", repoName: "showcase-mastra" }, + ); + expect(v).not.toBeNull(); + expect(v!.reason).toMatch(/staging must float on :latest/i); + expect(v!.reason).toMatch(/@sha256:/); + }); + + it("staging ghcr ref that is not :latest says so explicitly", () => { + const v = validateImage("ghcr.io/copilotkit/showcase-mastra:abc123", { + env: "staging", + repoName: "showcase-mastra", + }); + expect(v).not.toBeNull(); + expect(v!.reason).toMatch(/not the `:latest`/i); + }); +}); + +describe("validateImage — empty-string image rendering (bucket b)", () => { + it("treats empty-string image as unset (no image)", () => { + const v = validateImage("", { env: "prod", repoName: "showcase-mastra" }); + expect(v).not.toBeNull(); + expect(v!.reason).toMatch(/no image/i); + // Normalized so the reporter renders ``, not a blank line. + expect(v!.image).toBeNull(); + }); +}); diff --git a/showcase/scripts/verify-railway-image-refs.ts b/showcase/scripts/verify-railway-image-refs.ts index 72620b86183..7069d612b60 100644 --- a/showcase/scripts/verify-railway-image-refs.ts +++ b/showcase/scripts/verify-railway-image-refs.ts @@ -1,73 +1,319 @@ #!/usr/bin/env npx tsx /** - * verify-railway-image-refs.ts — Drift assertion for Railway showcase image refs. + * verify-railway-image-refs.ts — Per-env drift assertion for Railway + * showcase image refs. * - * Fetches every service in the CopilotKit Showcase project and validates that - * the configured Docker image reference matches the canonical GHCR form: - * ghcr.io/copilotkit/:latest + * Fetches every service in the CopilotKit Showcase Railway project and + * validates the image reference configured on each env-scoped service + * instance against the canonical shape for that env: + * + * STAGING : ghcr.io/copilotkit/:latest (mutable tag) + * PROD : ghcr.io/copilotkit/@sha256: (immutable pin) + * + * defaults to the Railway service name; per-env overrides live + * in railway-envs.ts via `repoNameOverride` (currently: SSOT key + * `aimock` overrides BOTH prod and staging to repo `showcase-aimock` + * — the fixture-baking wrapper is the permanent, canonical aimock + * image; prod must be `@sha256`-pinned, staging is `:latest`. Plus + * pocketbase and webhooks, which override BOTH envs to + * `showcase-pocketbase` and `showcase-eval-webhook` respectively). * * Backstory: on 2026-04-21, 18 production services were found with malformed - * image refs of the form `ghcr.io/copilotkit/showcase-atest` (missing - * the `:` before `latest`, so Docker treats `...atest` as the tag). The root - * cause was an out-of-band MCP/manual mutation — no committed code touched - * these refs. This script exists so any future corruption, regardless of - * source, fails loudly and early in CI before a bad deploy goes out. + * image refs `ghcr.io/copilotkit/showcase-atest` (missing the `:` + * before `latest`, so Docker treats `...atest` as the tag). The root cause + * was an out-of-band MCP/manual mutation — no committed code touched them. + * This script exists so any future corruption fails loudly and early in CI + * before a bad deploy goes out. * * Usage: * npx tsx showcase/scripts/verify-railway-image-refs.ts * * Requires: RAILWAY_TOKEN env var or ~/.railway/config.json - * Exit: 0 when every service matches the canonical shape, 1 on any violation. + * Exit: 0 when every env-scoped instance matches; 1 on any violation. */ -import fs from "fs"; -import path from "path"; import { fileURLToPath } from "url"; +import { + PRODUCTION_ENV_ID, + PROJECT_ID, + SERVICES, + STAGING_ENV_ID, + repoNameFor, +} from "./railway-envs"; +import type { EnvName } from "./railway-envs"; +import { + RAILWAY_GRAPHQL_ENDPOINT, + sanitizeErrorBody, +} from "./lib/railway-graphql"; +import { RailwayTokenError, resolveRailwayToken } from "./lib/railway-token"; -const RAILWAY_API = "https://backboard.railway.com/graphql/v2"; - -const SHOWCASE = { - projectId: "6f8c6bff-a80d-4f8f-b78d-50b32bcf4479", - environmentId: "b14919f4-6417-429f-848d-c6ae2201e04f", -}; - -// Canonical shape: ghcr.io/copilotkit/:latest where is the -// service name itself. This single pattern covers showcase-, -// showcase-pocketbase, showcase-harness, and any future showcase-* service. -// Enforcing identity between Railway service -// name and image name (modulo the ghcr.io/copilotkit/ prefix and :latest -// tag) is the invariant — if these ever drift apart we want to know. -// -// The regex accepts both `showcase-*` and bare `` images to -// accommodate services like aimock whose wrapper was eliminated — the -// Railway service is still named `showcase-aimock` but the image is now -// `ghcr.io/copilotkit/aimock:latest`. -const IMAGE_SHAPE = /^ghcr\.io\/copilotkit\/[a-z0-9-]+:latest$/; - -// Services whose GHCR image name intentionally differs from the Railway -// service name. After the aimock wrapper elimination (PR #128), Railway -// pulls `ghcr.io/copilotkit/aimock:latest` directly instead of the old -// `showcase-aimock` wrapper image. The verify job must accept this -// divergence rather than requiring image === service name. -const IMAGE_OVERRIDES: Record = { - "showcase-aimock": "ghcr.io/copilotkit/aimock:latest", -}; +const RAILWAY_API = RAILWAY_GRAPHQL_ENDPOINT; -function getToken(): string { - if (process.env.RAILWAY_TOKEN) return process.env.RAILWAY_TOKEN; - const configPath = path.join( - process.env.HOME || "~", - ".railway", - "config.json", +// Canonical shapes per env. +// Staging :latest pattern — exact-match against `:latest`. +// Prod @sha256: — exact-match against `@sha256:<64 hex>`. +const STAGING_SHAPE = /^ghcr\.io\/copilotkit\/[a-z0-9-]+:latest$/; +const PROD_SHAPE = /^ghcr\.io\/copilotkit\/[a-z0-9-]+@sha256:[0-9a-f]{64}$/; + +export interface ValidateOpts { + env: EnvName; + /** Expected GHCR repo name. Caller resolves this from SERVICES + env. */ + repoName: string; +} + +export interface Violation { + service: string; + env: EnvName; + image: string | null; + reason: string; +} + +/** + * Pure, unit-testable validator. Caller is responsible for resolving + * the expected repo name from the SERVICES map (handling per-env + * overrides) and passing it in here. + * + * Returns null when valid, or a Violation describing the failure. + * The `service` field is left blank ("") so the main loop can fill it in; + * tests can ignore it. + */ +export function validateImage( + image: string | null, + opts: ValidateOpts, +): Violation | null { + const { env, repoName } = opts; + // Normalize empty-string to null so the reporter renders `` + // rather than a blank line on a missing image. + const normalizedImage = image === "" ? null : image; + if (!normalizedImage) { + return { + service: "", + env, + image: null, + reason: + "no image source configured (expected a Docker image, not a repo)", + }; + } + + if (env === "staging") { + if (!STAGING_SHAPE.test(normalizedImage)) { + if (!normalizedImage.startsWith("ghcr.io/copilotkit/")) { + return { + service: "", + env, + image: normalizedImage, + reason: `image is not on ghcr.io/copilotkit (got: ${normalizedImage}); staging expects ghcr.io/copilotkit/:latest`, + }; + } + if (/@sha256:/.test(normalizedImage)) { + return { + service: "", + env, + image: normalizedImage, + reason: + "staging must float on :latest, found a @sha256: digest pin. Promote-back-from-prod bug?", + }; + } + return { + service: "", + env, + image: normalizedImage, + reason: + "image is on ghcr.io/copilotkit but is not the `:latest` shape (staging requires the mutable :latest tag)", + }; + } + const expected = `ghcr.io/copilotkit/${repoName}:latest`; + if (normalizedImage !== expected) { + return { + service: "", + env, + image: normalizedImage, + reason: `image repo name mismatches expected (expected exactly ${expected})`, + }; + } + return null; + } + + // env === "prod" + if (!PROD_SHAPE.test(normalizedImage)) { + if (!normalizedImage.startsWith("ghcr.io/copilotkit/")) { + return { + service: "", + env, + image: normalizedImage, + reason: + "does not match canonical shape ^ghcr\\.io/copilotkit/[a-z0-9-]+@sha256:[0-9a-f]{64}$", + }; + } + if (normalizedImage.endsWith(":latest")) { + return { + service: "", + env, + image: normalizedImage, + reason: + "prod must be pinned to `@sha256:` (got `:latest`). Run `bin/railway promote` to pin from staging.", + }; + } + return { + service: "", + env, + image: normalizedImage, + reason: + "does not match canonical prod shape ^ghcr\\.io/copilotkit/[a-z0-9-]+@sha256:[0-9a-f]{64}$", + }; + } + // Validate the repo portion (everything before `@sha256:`) matches. + const repoPart = normalizedImage.split("@", 1)[0]; // "ghcr.io/copilotkit/" + const expectedRepo = `ghcr.io/copilotkit/${repoName}`; + if (repoPart !== expectedRepo) { + return { + service: "", + env, + image: normalizedImage, + reason: `image repo name mismatches expected (expected exactly ${expectedRepo}@sha256:)`, + }; + } + return null; +} + +/** + * Coverage assertion — returns the names of SSOT services with + * `gateValidated: true` that are NOT present in the Railway response + * for the given env. A non-empty result means the gate should fail + * (drift in the SSOT-vs-Railway direction: a service was deleted or + * renamed on Railway without updating the SSOT). + * + * Pure / unit-testable. Caller (main()) is responsible for collecting + * the set of seen SSOT-known service names from the Railway response. + * + * Note: `env` is accepted for symmetry and future per-env scoping, + * but currently the gateValidated flag is env-independent so the + * result does not depend on it. Result is sorted for stable output. + */ +export function findMissingServices( + _env: EnvName, + presentServiceNames: Set, +): string[] { + const missing: string[] = []; + for (const [name, entry] of Object.entries(SERVICES)) { + if (!entry.gateValidated) continue; + if (!presentServiceNames.has(name)) missing.push(name); + } + return missing.sort(); +} + +/** + * Coverage assertion — Railway → SSOT direction. Returns the names of + * Railway services that are NOT present in the SSOT. A non-empty result + * means the gate should fail (drift in the Railway→SSOT direction: an + * out-of-band service was added to the Railway project without updating + * the SSOT). + * + * Pure / unit-testable. Caller (main()) is responsible for collecting + * the set of Railway-reported service names from the GraphQL response. + * + * Note: complements `findMissingServices` (SSOT→Railway direction); see + * its docstring above. The two directions are NOT the same check — do + * NOT collapse them. + */ +export function findUntrackedServices( + railwayServiceNames: ReadonlySet, +): string[] { + const untracked: string[] = []; + for (const name of railwayServiceNames) { + const entry = SERVICES[name]; + // Any SSOT entry — gateIgnored or not — is known/accounted-for in + // the Railway->SSOT direction. Only absence from the SSOT counts. + if (entry) continue; + untracked.push(name); + } + return untracked.sort(); +} + +export interface FailureSummaryInput { + violations: Violation[]; + missingByEnv: Record; + untracked: string[]; + checked: number; + skipped: number; +} + +export interface FailureSummaryOutput { + shouldFail: boolean; + lines: string[]; +} + +/** + * Pure failure-summary builder. Takes the three classes of finding + * the gate produces and returns the lines main() should print plus a + * boolean indicating whether to exit non-zero. Extracted from main() + * so it can be unit-tested without going through GraphQL. + * + * Three failure classes (all REFUSE — none are warnings): + * 1. shape violations (Violation[]) + * 2. SSOT->Railway drift (gateValidated SSOT services missing on Railway) + * 3. Railway->SSOT drift (Railway services not in the SSOT, NOT + * opted out via gateIgnore) + */ +export function summarizeFailures( + input: FailureSummaryInput, +): FailureSummaryOutput { + const { violations, missingByEnv, untracked, checked, skipped } = input; + const totalMissing = missingByEnv.prod.length + missingByEnv.staging.length; + const shouldFail = + violations.length > 0 || totalMissing > 0 || untracked.length > 0; + const lines: string[] = []; + + if (!shouldFail) return { shouldFail, lines }; + + lines.push( + `\n✗ Railway image-ref drift detected (${violations.length} violations across ${checked} env-scoped instances; ${totalMissing} missing services; ${untracked.length} untracked Railway services; ${skipped} skipped)\n`, ); - if (fs.existsSync(configPath)) { - const config = JSON.parse(fs.readFileSync(configPath, "utf-8")); - if (config?.user?.token) return config.user.token; + for (const v of violations) { + lines.push(` ✗ [${v.env}] ${v.service}`); + lines.push(` current: ${v.image ?? ""}`); + lines.push(` reason: ${v.reason}`); + } + for (const env of ["prod", "staging"] as const) { + for (const name of missingByEnv[env]) { + lines.push(` ✗ [${env}] ${name}`); + lines.push(` current: `); + lines.push( + ` reason: gateValidated SSOT service has no serviceInstance in ${env} — was it deleted or renamed?`, + ); + } + } + for (const name of untracked) { + lines.push(` ✗ [railway] ${name}`); + lines.push(` current: `); + lines.push( + ` reason: Railway service "${name}" is not in the SSOT. Either add it to SERVICES in showcase/scripts/railway-envs.ts (preferred), or mark an existing entry with gateIgnore: true if it is deliberately unmanaged by WS4.`, + ); } - console.error( - "No Railway token found. Set RAILWAY_TOKEN or run `railway login`.", + lines.push( + `\nFix via Railway dashboard, \`bin/railway pin\`, \`bin/railway promote\`, or \`showcase/scripts/redeploy-env.ts\`.\n`, ); - process.exit(1); + return { shouldFail, lines }; +} + +// ── Railway GraphQL plumbing ──────────────────────────────────────────── + +/** + * Resolve the Railway bearer token for this run. Wraps the shared + * `resolveRailwayToken` envelope and maps any RailwayTokenError onto + * the script's exit-1 contract (operator/config error). The shared + * helper never calls process.exit — exit-code mapping lives HERE. + */ +function getToken(): string { + try { + return resolveRailwayToken().token; + } catch (e) { + if (e instanceof RailwayTokenError) { + console.error(e.message); + process.exit(1); + } + throw e; + } } async function railwayGql( @@ -84,7 +330,11 @@ async function railwayGql( body: JSON.stringify({ query, variables }), }); if (!res.ok) { - throw new Error(`Railway API error: ${res.status} ${await res.text()}`); + // sanitize: Cloudflare WAF blocks return multi-KB HTML pages — + // strip angle brackets + control chars and cap at the shared + // default to keep CI logs readable. + const body = sanitizeErrorBody(await res.text()); + throw new Error(`Railway API error: ${res.status} ${body}`); } const json = (await res.json()) as { data?: T; @@ -99,6 +349,9 @@ async function railwayGql( } interface ProjectServicesWithInstances { + // Railway returns project: null (no GraphQL `errors` block) when the + // PROJECT_ID is wrong OR the token lacks access — type accordingly so + // the null-check in main() is enforced by the compiler. project: { services: { edges: Array<{ @@ -116,44 +369,7 @@ interface ProjectServicesWithInstances { }; }>; }; - }; -} - -interface Violation { - service: string; - image: string | null; - reason: string; -} - -export function validateImage( - serviceName: string, - image: string | null, -): Violation | null { - if (!image) { - return { - service: serviceName, - image, - reason: - "no image source configured (expected a Docker image, not a repo)", - }; - } - if (!IMAGE_SHAPE.test(image)) { - return { - service: serviceName, - image, - reason: `does not match canonical shape ^ghcr\\.io/copilotkit/[a-z0-9-]+:latest$`, - }; - } - const expected = - IMAGE_OVERRIDES[serviceName] ?? `ghcr.io/copilotkit/${serviceName}:latest`; - if (image !== expected) { - return { - service: serviceName, - image, - reason: `image name mismatches service name (expected exactly ${expected})`, - }; - } - return null; + } | null; } async function main(): Promise { @@ -171,47 +387,109 @@ async function main(): Promise { } } }`, - { id: SHOWCASE.projectId }, + { id: PROJECT_ID }, ); - const services = data.project.services.edges - .map((e) => e.node) - .filter((s) => s.name.startsWith("showcase-")); + // Railway returns project: null with NO `errors` array when PROJECT_ID + // is wrong or the token lacks access — without this guard, reading + // `data.project.services` throws a confusing TypeError. + if (data.project === null || data.project === undefined) { + throw new Error( + `Railway project ${PROJECT_ID} returned null — check PROJECT_ID and that the Railway token has access to this project.`, + ); + } const violations: Violation[] = []; let checked = 0; - for (const svc of services) { - const instance = svc.serviceInstances.edges.find( - (e) => e.node.environmentId === SHOWCASE.environmentId, - ); - const image = instance?.node.source?.image ?? null; - checked++; - const v = validateImage(svc.name, image); - if (v) violations.push(v); - } + let skipped = 0; + // Per-env set of SSOT-known, gateValidated service names we actually + // saw in the Railway response. Used post-loop for coverage assertion. + const seenByEnv: Record> = { + prod: new Set(), + staging: new Set(), + }; + // Names Railway actually reported back, used post-loop for the + // Railway -> SSOT coverage assertion (findUntrackedServices). + const railwayReportedNames = new Set(); - if (violations.length > 0) { - console.error( - `\n✗ Railway image-ref drift detected (${violations.length}/${checked} services)\n`, - ); - console.error( - `Expected shape: ghcr.io/copilotkit/:latest` + - ` (note the ':' before 'latest')\n`, - ); - for (const v of violations) { - console.error(` ${v.service}`); - console.error(` current: ${v.image ?? ""}`); - console.error(` expected: ghcr.io/copilotkit/${v.service}:latest`); - console.error(` reason: ${v.reason}`); + for (const edge of data.project.services.edges) { + const svc = edge.node; + railwayReportedNames.add(svc.name); + const entry = SERVICES[svc.name]; + + // Railway -> SSOT direction is handled post-loop via + // findUntrackedServices(); do NOT log a warning here. An unknown + // service that ALSO has a shape problem will surface in the + // post-loop failure block under the "untracked" class, which is + // the right shape (we can't validate shape without an expected + // repo name, and there is no SSOT entry to derive one from). + if (!entry) continue; + + // gateIgnore: deliberately unmanaged. Skip both shape validation + // and Railway->SSOT membership reporting (the helper also honours + // this flag for that direction). + if (entry.gateIgnore) { + skipped++; + continue; } - console.error( - `\nFix via Railway dashboard or the showcase deploy-to-railway script.` + - ` A common past cause was ':' dropped from ':latest' by an out-of-band API mutation.\n`, - ); + + // Per-WS-C gate scope: only services explicitly marked + // gateValidated. After WS-C lands the 5-service flip this is + // every entry in SERVICES — the Phase-2 deferral is retired. + if (!entry.gateValidated) { + skipped++; + continue; + } + + for (const env of ["prod", "staging"] as const) { + const envId = env === "prod" ? PRODUCTION_ENV_ID : STAGING_ENV_ID; + const instance = svc.serviceInstances.edges.find( + (e) => e.node.environmentId === envId, + ); + // A gateValidated SSOT service with no serviceInstance for this + // env is genuine drift; don't count it as "seen" so the coverage + // assertion catches it. + if (!instance) continue; + seenByEnv[env].add(svc.name); + + const image = instance.node.source?.image ?? null; + + checked++; + const repoName = repoNameFor(svc.name, env); + const v = validateImage(image, { env, repoName }); + if (v) { + violations.push({ ...v, service: svc.name }); + } + } + } + + // Coverage assertions: + // - SSOT->Railway: a gateValidated SSOT service that did not show + // up in the Railway response is drift. + // - Railway->SSOT: a Railway service that has no SSOT entry (and + // is not opted out via gateIgnore) is drift. + const missingByEnv: Record = { + prod: findMissingServices("prod", seenByEnv.prod), + staging: findMissingServices("staging", seenByEnv.staging), + }; + const untracked = findUntrackedServices(railwayReportedNames); + + const summary = summarizeFailures({ + violations, + missingByEnv, + untracked, + checked, + skipped, + }); + + if (summary.shouldFail) { + for (const line of summary.lines) console.error(line); process.exit(1); } - console.log(`✓ ${checked} services verified`); + console.log( + `✓ ${checked} env-scoped instances verified (${skipped} skipped)`, + ); } if (process.argv[1] === fileURLToPath(import.meta.url)) { diff --git a/showcase/shell-dashboard/Dockerfile b/showcase/shell-dashboard/Dockerfile index 9781368e015..8a3074e99f0 100644 --- a/showcase/shell-dashboard/Dockerfile +++ b/showcase/shell-dashboard/Dockerfile @@ -28,20 +28,6 @@ COPY showcase/shell-dashboard/ ./shell-dashboard/ ENV NEXT_PUBLIC_COMMIT_SHA=${COMMIT_SHA} ENV NEXT_PUBLIC_BRANCH=${BRANCH} -# NEXT_PUBLIC_* vars are inlined by Next.js at build time. Must be -# provided as Docker build args — runtime env vars on Railway are too late. -ARG NEXT_PUBLIC_SHELL_URL -ENV NEXT_PUBLIC_SHELL_URL=${NEXT_PUBLIC_SHELL_URL} -ARG NEXT_PUBLIC_POCKETBASE_URL -ENV NEXT_PUBLIC_POCKETBASE_URL=${NEXT_PUBLIC_POCKETBASE_URL} -# `next.config.ts` reads OPS_BASE_URL inside `rewrites()` and throws if it -# is unset. `next build` evaluates rewrites at build time to generate the -# routing manifest, so the value must be in the BUILDER environment — not -# just runtime. Without this, `npx next build` aborts with the -# "OPS_BASE_URL must be set" error from next.config.ts. -ARG OPS_BASE_URL -ENV OPS_BASE_URL=${OPS_BASE_URL} - # Generate registry + docs status, then build Next.js RUN cd scripts && node node_modules/tsx/dist/cli.mjs generate-registry.ts \ && node node_modules/tsx/dist/cli.mjs probe-docs.ts \ @@ -68,10 +54,11 @@ FROM node:20-slim AS runner WORKDIR /app ENV NODE_ENV=production ENV PORT=10000 -# NEXT_PUBLIC_* are statically inlined at build time; the runtime env vars -# from the builder stage are not inherited here and would be undefined -# regardless. Don't re-declare them — they'd just be noise and create the -# illusion that changing them at runtime does anything. +# URL env vars (POCKETBASE_URL / SHELL_URL / OPS_BASE_URL) are read at +# runtime by `src/lib/runtime-config.ts` from the Railway service env — +# no Dockerfile ARG/ENV plumbing required for them. +# COMMIT_SHA / BRANCH stay build-baked because they identify the artifact, +# not the deploy. See showcase/RAILWAY.md and runtime-config.ts. # Copy build artifacts with `node:node` ownership so the runtime process # (dropped to USER node below) can read them. The `node` user/group ships diff --git a/showcase/shell-dashboard/next.config.ts b/showcase/shell-dashboard/next.config.ts index 1a33091d374..84b7685d9fb 100644 --- a/showcase/shell-dashboard/next.config.ts +++ b/showcase/shell-dashboard/next.config.ts @@ -1,4 +1,5 @@ import type { NextConfig } from "next"; +import { PHASE_PRODUCTION_BUILD } from "next/constants"; /** * Next.js config for the dashboard shell. @@ -12,28 +13,52 @@ import type { NextConfig } from "next"; * 2. We don't want the ops base URL inlined into the client bundle (it * would also force `NEXT_PUBLIC_*` exposure semantics). * - * `OPS_BASE_URL` is required at build/start. Without it the rewrite cannot - * be constructed and the dashboard would silently render "All probes idle" - * because every `/api/ops/probes` call would 404 against this app. + * `OPS_BASE_URL` is required at START — not at build. `next build` + * evaluates `rewrites()` so route metadata can be persisted into the + * artifact, AND `next start` evaluates it again at process boot. + * Throwing at build time would prevent the runtime-injection deploy + * pattern (single artifact, env supplied at start). So when invoked + * under the build phase we accept the absence with a sentinel + * destination — the rewrite is reconstructed with the real env value + * at start. At start time, missing OPS_BASE_URL still throws loudly to + * surface the wiring bug. + * + * The config is exported as a function `(phase) => NextConfig` so the + * build-vs-start distinction is reliable without relying on the + * `NEXT_PHASE` env (which Next.js does not always export to user code). */ -const nextConfig: NextConfig = { - async rewrites() { - const opsBase = process.env.OPS_BASE_URL; - if (!opsBase) { - throw new Error( - "OPS_BASE_URL must be set — see showcase/RAILWAY.md " + - "(without it, /api/ops/* requests cannot proxy to showcase-harness)", - ); - } - // Strip trailing slashes so we never produce `https://host//api/...` - // (some servers reject the double slash). Mirrors the same - // normalization in `src/lib/ops-api.ts:resolveBaseUrl` so the - // server-side rewrite and client-side fetch agree on the URL shape. - const normalized = opsBase.replace(/\/+$/, ""); - return [ - { source: "/api/ops/:path*", destination: `${normalized}/api/:path*` }, - ]; - }, -}; +const SENTINEL_OPS_BASE = "http://ops.invalid"; -export default nextConfig; +export default function nextConfig(phase: string): NextConfig { + const isBuildPhase = phase === PHASE_PRODUCTION_BUILD; + return { + async rewrites() { + const opsBase = process.env.OPS_BASE_URL; + if (!opsBase) { + if (isBuildPhase) { + // Build phase: emit a parseable sentinel destination so + // `next build` succeeds. `rewrites()` runs again at + // `next start` with the real env value (or throws below). + return [ + { + source: "/api/ops/:path*", + destination: `${SENTINEL_OPS_BASE}/api/:path*`, + }, + ]; + } + throw new Error( + "OPS_BASE_URL must be set on this Railway service — see showcase/RAILWAY.md " + + "(without it, /api/ops/* requests cannot proxy to showcase-harness)", + ); + } + // Strip trailing slashes so we never produce `https://host//api/...` + // (some servers reject the double slash). Mirrors the same + // normalization in `src/lib/ops-api.ts:resolveBaseUrl` so the + // server-side rewrite and client-side fetch agree on the URL shape. + const normalized = opsBase.replace(/\/+$/, ""); + return [ + { source: "/api/ops/:path*", destination: `${normalized}/api/:path*` }, + ]; + }, + }; +} diff --git a/showcase/shell-dashboard/src/app/layout.tsx b/showcase/shell-dashboard/src/app/layout.tsx index f673c8b018d..3b42049860d 100644 --- a/showcase/shell-dashboard/src/app/layout.tsx +++ b/showcase/shell-dashboard/src/app/layout.tsx @@ -1,5 +1,7 @@ import "./globals.css"; import type { Metadata } from "next"; +import { getRuntimeConfig } from "@/lib/runtime-config"; +import { serializeRuntimeConfig } from "@/lib/runtime-config-serialize"; export const metadata: Metadata = { title: "CopilotKit Internal Showcase", @@ -31,14 +33,37 @@ const themeInitScript = ` })(); `; +// serializeRuntimeConfig is extracted to `lib/runtime-config-serialize.ts` +// so it can be unit-tested for the OWASP escape behavior (XSS via +// ``, U+2028/U+2029 line-terminator injection) without +// importing the layout into the test runner. + export default function RootLayout({ children, }: { children: React.ReactNode; }) { + // Server-side: read live env at request time. `unstable_noStore()` + // inside getRuntimeConfig opts this segment out of the static + // cache so the inline block in the root layout. A +// hostile env value containing the closing-script substring would +// otherwise break out and let the attacker inject arbitrary HTML. +// JSON.stringify does NOT escape `<` by default, so we MUST escape +// it to `<` here. U+2028 / U+2029 must also be escaped because +// the JS string literal parser treats them as line terminators in +// older engines. +// +// shell-dashboard's variant builds the regexes via the RegExp +// constructor (`new RegExp("\\uXXXX", "g")`) — this test re-asserts +// the same XSS / parser-hazard properties hold for that variant. +// +// Tokenizer note: we build the test inputs via String.fromCharCode +// so the literal U+2028 / U+2029 codepoints never appear in this +// source file (some editors / build pipelines reject them). + +const LS = String.fromCharCode(0x2028); +const PS = String.fromCharCode(0x2029); + +describe("serializeRuntimeConfig (shell-dashboard)", () => { + it("escapes `<` so the closing-script substring cannot appear in the output", () => { + const malicious = { shellUrl: "" }; + const serialized = serializeRuntimeConfig(malicious); + expect(serialized).not.toContain(""); + expect(serialized).toContain("\\u003c"); + }); + + it("escapes U+2028 (LINE SEPARATOR) to \\u2028", () => { + const cfg = { shellUrl: `before${LS}after` }; + const serialized = serializeRuntimeConfig(cfg); + expect(serialized).not.toContain(LS); + expect(serialized).toContain("\\u2028"); + }); + + it("escapes U+2029 (PARAGRAPH SEPARATOR) to \\u2029", () => { + const cfg = { shellUrl: `before${PS}after` }; + const serialized = serializeRuntimeConfig(cfg); + expect(serialized).not.toContain(PS); + expect(serialized).toContain("\\u2029"); + }); + + it("round-trips a normal config via JSON.parse", () => { + const cfg = { + pocketbaseUrl: "https://pb.example.com", + shellUrl: "https://shell.example.com", + opsBaseUrl: "https://ops.example.com", + }; + const serialized = serializeRuntimeConfig(cfg); + expect(JSON.parse(serialized)).toEqual(cfg); + }); + + it("does not double-escape already-safe characters", () => { + const cfg = { shellUrl: "https://shell.copilotkit.ai" }; + const serialized = serializeRuntimeConfig(cfg); + expect(serialized).toBe(JSON.stringify(cfg)); + }); +}); diff --git a/showcase/shell-dashboard/src/lib/runtime-config-serialize.ts b/showcase/shell-dashboard/src/lib/runtime-config-serialize.ts new file mode 100644 index 00000000000..91947814074 --- /dev/null +++ b/showcase/shell-dashboard/src/lib/runtime-config-serialize.ts @@ -0,0 +1,29 @@ +// Serialize the runtime config for inline injection into the root +// layout's `` block. Three substrings would +// otherwise break out of (or corrupt) the parser: +// +// - `<` — guards against the `` breakout (XSS). JSON.stringify +// does NOT escape `<` by default, so a URL containing `` +// (e.g. a hostile env value) would terminate the inline script and +// inject HTML. Escape every `<` to `<` so the substring +// `` can never appear. +// - U+2028 (LINE SEPARATOR) and U+2029 (PARAGRAPH SEPARATOR) — legal +// inside JSON strings, but a syntax error inside a JS string literal +// in older engines / when the page is parsed as `text/javascript`. +// Escape both. +// +// Tokenizer note: writing the literal U+2028 / U+2029 codepoints inside +// a /regex/ literal is a parse error in TypeScript / many JS engines +// because both codepoints are line terminators that prematurely +// terminate the regex literal. Constructing via the RegExp constructor +// with `\u` escapes — resolved at runtime by the regex engine — +// sidesteps the tokenizer entirely. +// +// Canonical OWASP-recommended escape for inline JSON in HTML. + +export function serializeRuntimeConfig(cfg: unknown): string { + return JSON.stringify(cfg) + .replace(new RegExp("<", "g"), "\\u003c") + .replace(new RegExp("
", "g"), "\\u2028") + .replace(new RegExp("
", "g"), "\\u2029"); +} diff --git a/showcase/shell-dashboard/src/lib/runtime-config.client.test.ts b/showcase/shell-dashboard/src/lib/runtime-config.client.test.ts new file mode 100644 index 00000000000..9ba61c4ccd9 --- /dev/null +++ b/showcase/shell-dashboard/src/lib/runtime-config.client.test.ts @@ -0,0 +1,57 @@ +import { afterEach, beforeEach, describe, expect, it } from "vitest"; +import { getRuntimeConfig } from "./runtime-config.client"; + +describe("client getRuntimeConfig (shell-dashboard)", () => { + const originalWindow = globalThis.window; + + beforeEach(() => { + // jsdom provides `window` by default in this vitest config. + // Reset any prior injection. + delete (globalThis.window as Window & { __SHOWCASE_CONFIG__?: unknown }) + .__SHOWCASE_CONFIG__; + }); + + afterEach(() => { + (globalThis as { window?: Window }).window = originalWindow; + }); + + it("returns the injected config", () => { + (window as Window & { __SHOWCASE_CONFIG__?: unknown }).__SHOWCASE_CONFIG__ = + { + pocketbaseUrl: "https://pb.example.com", + shellUrl: "https://shell.example.com", + opsBaseUrl: "https://ops.example.com", + }; + expect(getRuntimeConfig()).toEqual({ + pocketbaseUrl: "https://pb.example.com", + shellUrl: "https://shell.example.com", + opsBaseUrl: "https://ops.example.com", + }); + }); + + it("throws when __SHOWCASE_CONFIG__ is missing (wiring bug)", () => { + expect(() => getRuntimeConfig()).toThrow( + /window\.__SHOWCASE_CONFIG__ is missing/, + ); + }); + + it("returns SSR sentinel placeholder when window is undefined", () => { + // Simulate SSR by removing window. "use client" component + // bodies execute on the server during SSR, so this reader + // MUST be SSR-safe (returns parseable-URL placeholders so + // `new URL()` in consumers doesn't throw) and NOT throw — + // otherwise the whole server-rendered HTML 500s. + const w = globalThis.window; + // @ts-expect-error — deliberately removing window for the test + delete globalThis.window; + try { + const cfg = getRuntimeConfig(); + // URL fields must be parseable. + expect(() => new URL(cfg.pocketbaseUrl)).not.toThrow(); + expect(() => new URL(cfg.shellUrl)).not.toThrow(); + expect(() => new URL(cfg.opsBaseUrl)).not.toThrow(); + } finally { + (globalThis as { window?: typeof w }).window = w; + } + }); +}); diff --git a/showcase/shell-dashboard/src/lib/runtime-config.client.ts b/showcase/shell-dashboard/src/lib/runtime-config.client.ts new file mode 100644 index 00000000000..4ee87e897d5 --- /dev/null +++ b/showcase/shell-dashboard/src/lib/runtime-config.client.ts @@ -0,0 +1,69 @@ +// Client-side runtime config reader. Reads from +// window.__SHOWCASE_CONFIG__ which the root layout injects via an +// inline `, U+2028/U+2029 line-terminator injection) without +// importing the layout into the test runner. + // Top-level route segments in src/app/ that must not be mistaken for // framework slugs by FrameworkProvider.urlFramework. If an integration // registry entry ever ships a slug colliding with one of these, the @@ -78,8 +85,14 @@ export default function RootLayout({ ? "unknown" : rawSha.slice(0, 7); - const REO_KEY = process.env.NEXT_PUBLIC_REO_KEY; - const REB2B_KEY = process.env.NEXT_PUBLIC_REB2B_KEY; + // Server-side: read live env at request time. `unstable_noStore()` + // inside getRuntimeConfig opts this segment out of the static + // cache so the inline block in the root layout. A +// hostile env value containing the closing-script substring would +// otherwise break out and let the attacker inject arbitrary HTML. +// JSON.stringify does NOT escape `<` by default, so we MUST escape +// it to `<` here. U+2028 / U+2029 must also be escaped because +// the JS string literal parser treats them as line terminators in +// older engines. +// +// Tokenizer note: we build the test inputs via String.fromCharCode +// so the literal U+2028 / U+2029 codepoints never appear in this +// source file (some editors / build pipelines reject them). + +const LS = String.fromCharCode(0x2028); +const PS = String.fromCharCode(0x2029); + +describe("serializeRuntimeConfig (shell-docs)", () => { + it("escapes `<` so the closing-script substring cannot appear in the output", () => { + const malicious = { baseUrl: "" }; + const serialized = serializeRuntimeConfig(malicious); + expect(serialized).not.toContain(""); + expect(serialized).toContain("\\u003c"); + }); + + it("escapes U+2028 (LINE SEPARATOR) to \\u2028", () => { + const cfg = { baseUrl: `before${LS}after` }; + const serialized = serializeRuntimeConfig(cfg); + expect(serialized).not.toContain(LS); + expect(serialized).toContain("\\u2028"); + }); + + it("escapes U+2029 (PARAGRAPH SEPARATOR) to \\u2029", () => { + const cfg = { baseUrl: `before${PS}after` }; + const serialized = serializeRuntimeConfig(cfg); + expect(serialized).not.toContain(PS); + expect(serialized).toContain("\\u2029"); + }); + + it("round-trips a normal config via JSON.parse", () => { + const cfg = { + baseUrl: "https://docs.example.com", + shellUrl: "https://shell.example.com", + posthogKey: "phc_real", + }; + const serialized = serializeRuntimeConfig(cfg); + // The browser receives `window.__SHOWCASE_CONFIG__=;` + // and evaluates it. JSON.parse accepts the escaped output + // identically (the JS engine resolves < / / ). + expect(JSON.parse(serialized)).toEqual(cfg); + }); + + it("does not double-escape already-safe characters", () => { + const cfg = { baseUrl: "https://docs.copilotkit.ai" }; + const serialized = serializeRuntimeConfig(cfg); + expect(serialized).toBe(JSON.stringify(cfg)); + }); +}); diff --git a/showcase/shell-docs/src/lib/runtime-config-serialize.ts b/showcase/shell-docs/src/lib/runtime-config-serialize.ts new file mode 100644 index 00000000000..b46fed00798 --- /dev/null +++ b/showcase/shell-docs/src/lib/runtime-config-serialize.ts @@ -0,0 +1,32 @@ +// Serialize the runtime config for inline injection into the root +// layout's `` block. Three substrings would +// otherwise break out of (or corrupt) the parser: +// +// - `<` -- guards against the `` breakout (XSS). JSON.stringify +// does NOT escape `<` by default, so a URL containing `` +// (e.g. a hostile env value) would terminate the inline script and +// inject HTML. Escape every `<` to `<` so the substring +// `` can never appear. +// - U+2028 (LINE SEPARATOR) and U+2029 (PARAGRAPH SEPARATOR) -- legal +// inside JSON strings, but a syntax error inside a JS string literal +// in older engines / when the page is parsed as `text/javascript`. +// Escape both. +// +// Tokenizer note: the U+2028 / U+2029 codepoints in the RegExp argument +// are written as six-character ASCII backslash-u escape sequences, +// resolved at runtime by the regex engine, NOT as the literal Unicode +// codepoints. A formatter or editor that silently normalizes line +// terminators could otherwise strip the literal codepoints, leaving a +// regex that matches nothing and a security-critical XSS escape that +// silently no-ops. The escape-string form is robust to any such +// formatter pass. This file MUST NOT contain the literal U+2028 or +// U+2029 bytes anywhere in its source. +// +// Canonical OWASP-recommended escape for inline JSON in HTML. + +export function serializeRuntimeConfig(cfg: unknown): string { + return JSON.stringify(cfg) + .replace(new RegExp("<", "g"), "\\u003c") + .replace(new RegExp("\\u2028", "g"), "\\u2028") + .replace(new RegExp("\\u2029", "g"), "\\u2029"); +} diff --git a/showcase/shell-docs/src/lib/runtime-config.client.test.ts b/showcase/shell-docs/src/lib/runtime-config.client.test.ts new file mode 100644 index 00000000000..739481a7530 --- /dev/null +++ b/showcase/shell-docs/src/lib/runtime-config.client.test.ts @@ -0,0 +1,68 @@ +import { afterEach, beforeEach, describe, expect, it } from "vitest"; +import { getRuntimeConfig, type RuntimeConfig } from "./runtime-config.client"; + +// shell-docs's vitest runs with `environment: "node"` (no jsdom) — we +// simulate the browser by attaching a minimal `window` to globalThis +// before each test and removing it after, so we exercise BOTH the +// server-path throw (no window) and the client-path read. + +type WindowWithConfig = { __SHOWCASE_CONFIG__?: RuntimeConfig }; + +const FULL_CONFIG: RuntimeConfig = { + baseUrl: "https://docs.example.com", + shellUrl: "https://shell.example.com", + intelligenceSignupUrl: "https://signup.example.com", + posthogKey: "phc_test", + posthogHost: "https://eu.i.posthog.com", + scarfPixelId: "scarf-id", + googleAnalyticsTrackingId: "G-TEST", + reb2bKey: "rb2b-key", + reoKey: "reo-key", +}; + +describe("client getRuntimeConfig (shell-docs)", () => { + beforeEach(() => { + // Attach a fresh stub `window` for each test. The cast is unavoidable + // here because globalThis.window is typed against the DOM lib and we + // are deliberately providing only the shape we need. + (globalThis as { window?: WindowWithConfig }).window = {}; + }); + + afterEach(() => { + delete (globalThis as { window?: WindowWithConfig }).window; + }); + + it("returns the injected config", () => { + (globalThis as { window?: WindowWithConfig }).window!.__SHOWCASE_CONFIG__ = + FULL_CONFIG; + expect(getRuntimeConfig()).toEqual(FULL_CONFIG); + }); + + it("throws when __SHOWCASE_CONFIG__ is missing (wiring bug)", () => { + expect(() => getRuntimeConfig()).toThrow( + /window\.__SHOWCASE_CONFIG__ is missing/, + ); + }); + + it("returns SSR sentinel placeholder when window is undefined", () => { + // Simulate SSR by removing window. "use client" component bodies + // execute on the server during SSR, so this reader MUST be SSR-safe + // (returns parseable-URL placeholders for URL fields so `new URL()` + // calls in consumers don't throw, and empty strings for analytics + // keys so `if (key)` truthiness gates fail-closed) — NOT throw, + // otherwise the whole server-rendered HTML 500s. + delete (globalThis as { window?: WindowWithConfig }).window; + const cfg = getRuntimeConfig(); + // URL fields must be parseable so `new URL()` in consumers doesn't throw. + expect(() => new URL(cfg.baseUrl)).not.toThrow(); + expect(() => new URL(cfg.shellUrl)).not.toThrow(); + expect(() => new URL(cfg.intelligenceSignupUrl)).not.toThrow(); + expect(() => new URL(cfg.posthogHost)).not.toThrow(); + // Analytics keys stay empty so `if (key)` gates fail-closed on SSR. + expect(cfg.posthogKey).toBe(""); + expect(cfg.scarfPixelId).toBe(""); + expect(cfg.googleAnalyticsTrackingId).toBe(""); + expect(cfg.reb2bKey).toBe(""); + expect(cfg.reoKey).toBe(""); + }); +}); diff --git a/showcase/shell-docs/src/lib/runtime-config.client.ts b/showcase/shell-docs/src/lib/runtime-config.client.ts new file mode 100644 index 00000000000..ce220058905 --- /dev/null +++ b/showcase/shell-docs/src/lib/runtime-config.client.ts @@ -0,0 +1,81 @@ +// Client-side runtime config reader for shell-docs. Reads from +// window.__SHOWCASE_CONFIG__ which the root layout injects via an +// inline ` tag, where three substrings would otherwise + * break out of (or corrupt) the parser: + * + * - `<` — guards against the `` breakout (XSS). + * `JSON.stringify` does NOT escape `<` by default, so a URL + * containing `` (e.g. a hostile env value) would + * terminate the inline script and inject HTML. Escape every + * `<` to `<` so the substring `` can never appear. + * - ` +` (LINE SEPARATOR) and ` +` (PARAGRAPH SEPARATOR) — + * legal inside JSON strings, but a syntax error inside a JS + * string literal in older engines / when the page is parsed as + * `text/javascript`. Escape both. + * + * Canonical OWASP-recommended escape for inline JSON in HTML. + */ +function serializeRuntimeConfig(cfg: unknown): string { + return JSON.stringify(cfg) + .replace(/ below always reflects the current Railway + // env vars. shell-dojo's RuntimeConfig is empty today; the + // injection stays for pattern symmetry across all four shells. + const runtimeConfig = getRuntimeConfig(); + const injection = `window.__SHOWCASE_CONFIG__=${serializeRuntimeConfig(runtimeConfig)};`; + return ( + {/* First child of — MUST execute before every other + head-level script so client code can read + window.__SHOWCASE_CONFIG__ during module init. Keep this + ahead of the fonts and any future next/script + beforeInteractive blocks. */} + ` tag, where three substrings would otherwise + * break out of (or corrupt) the parser: + * + * - `<` — guards against the `` breakout (XSS). + * `JSON.stringify` does NOT escape `<` by default, so a URL + * containing `` (e.g. a hostile env value) would + * terminate the inline script and inject HTML. Escape every + * `<` to `<` so the substring `` can never appear. + * - ` +` (LINE SEPARATOR) and ` +` (PARAGRAPH SEPARATOR) — + * legal inside JSON strings, but a syntax error inside a JS + * string literal in older engines / when the page is parsed as + * `text/javascript`. Escape both. + * + * IMPORTANT: the regex sources below use explicit ` +` / ` +` + * ECMAScript-Unicode escapes — the regex engine resolves the escape at + * compile time, so `/ +/` matches the actual U+2028 codepoint. + * Using a literal U+2028 / U+2029 character in the regex source would + * break the parser (those codepoints terminate a regex literal in + * pre-ES2019 engines, and are visually invisible — easy to ship + * accidentally). Reviewers MUST confirm these regexes are written with + * ` +` / ` +` escapes literally. + * + * Canonical OWASP-recommended escape for inline JSON in HTML. + */ +function serializeRuntimeConfig(cfg: unknown): string { + return JSON.stringify(cfg) + .replace(/ i.slug); + // Server-side: read live env at request time. `unstable_noStore()` + // inside getRuntimeConfig opts this segment out of the static cache + // so the inline