From 8895409b6ca708b7e3b941c789e909ad204efea1 Mon Sep 17 00:00:00 2001 From: Karan Singh Date: Wed, 27 May 2026 14:31:56 +0530 Subject: [PATCH 1/5] chore(ci): simplify issue templates and add PR size labeler --- .github/ISSUE_TEMPLATE/bug_report.yml | 40 --------------------- .github/ISSUE_TEMPLATE/feature_request.yml | 26 ++------------ .github/ISSUE_TEMPLATE/good_first_issue.yml | 26 -------------- .github/workflows/pr-size-labeler.yml | 23 ++++++++++++ .github/workflows/stale.yml | 24 ------------- 5 files changed, 26 insertions(+), 113 deletions(-) create mode 100644 .github/workflows/pr-size-labeler.yml delete mode 100644 .github/workflows/stale.yml diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 0491aac..4c9dcff 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -12,8 +12,6 @@ body: --- Before submitting, search open issues to avoid duplicates. - Vague reports without reproduction steps will be closed. - - type: textarea id: description attributes: @@ -21,41 +19,3 @@ body: description: A clear description of the bug. validations: required: true - - - type: textarea - id: steps - attributes: - label: Steps to Reproduce - description: Exact steps to reproduce the behavior. - placeholder: | - 1. Go to '...' - 2. Click on '...' - 3. See error - validations: - required: true - - - type: textarea - id: expected - attributes: - label: Expected Behavior - description: What you expected to happen. - validations: - required: true - - - type: textarea - id: environment - attributes: - label: Environment - placeholder: | - OS: macOS 14 / Windows 11 / Ubuntu 22.04 - Browser: Chrome 124 / Firefox 125 - Node.js: 20.x - Python: 3.12.x - validations: - required: true - - - type: textarea - id: additional - attributes: - label: Additional Context - description: Screenshots, error logs, or any other relevant information. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 17950f0..8bb0bf8 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -12,29 +12,9 @@ body: - A PR opened for a feature without a linked, approved issue will be closed. - Check the [Roadmap in DOCUMENTATION.md](https://github.com/jpdevhub/FreshScanAi/blob/main/DOCUMENTATION.md#14-future-roadmap) first — if your idea is already planned, comment on the existing tracking issue instead. - type: textarea - id: problem + id: description attributes: - label: Problem - description: What problem does this feature solve? + label: Description + description: What problem does this feature solve and what is your proposed solution? validations: required: true - - - type: textarea - id: solution - attributes: - label: Proposed Solution - description: Describe the feature you want to see implemented. - validations: - required: true - - - type: textarea - id: alternatives - attributes: - label: Alternatives Considered - description: Any alternative approaches you have considered. - - - type: textarea - id: additional - attributes: - label: Additional Context - description: Mockups, references, or any other relevant context. diff --git a/.github/ISSUE_TEMPLATE/good_first_issue.yml b/.github/ISSUE_TEMPLATE/good_first_issue.yml index d399683..0278785 100644 --- a/.github/ISSUE_TEMPLATE/good_first_issue.yml +++ b/.github/ISSUE_TEMPLATE/good_first_issue.yml @@ -6,7 +6,6 @@ body: attributes: value: | This issue is intended for contributors new to the project. It should be self-contained and completable without deep knowledge of the codebase. - - type: textarea id: description attributes: @@ -14,28 +13,3 @@ body: description: A clear description of what needs to be done. validations: required: true - - - type: textarea - id: context - attributes: - label: Context and Background - description: Any background information or links to relevant code the contributor needs to get started. - validations: - required: true - - - type: textarea - id: acceptance - attributes: - label: Acceptance Criteria - description: Define what done looks like for this task. - placeholder: | - - [ ] Criterion 1 - - [ ] Criterion 2 - validations: - required: true - - - type: textarea - id: hints - attributes: - label: Hints - description: Relevant files, functions, or documentation to look at first. diff --git a/.github/workflows/pr-size-labeler.yml b/.github/workflows/pr-size-labeler.yml new file mode 100644 index 0000000..39045ac --- /dev/null +++ b/.github/workflows/pr-size-labeler.yml @@ -0,0 +1,23 @@ +name: PR Size Labeler +on: + pull_request_target: + types: [opened, synchronize, reopened] + +jobs: + size-label: + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + steps: + - name: Add PR Size Label + uses: pascalgn/size-label-action@v0.5.5 + env: + GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + with: + sizes: > + { + "0": "level: easy", + "100": "level: medium", + "500": "level: hard" + } diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml deleted file mode 100644 index fe1214d..0000000 --- a/.github/workflows/stale.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: Stale Issue/PR Management - -on: - schedule: - - cron: '0 0 * * *' - -jobs: - stale: - runs-on: ubuntu-latest - permissions: - issues: write - pull-requests: write - steps: - - uses: actions/stale@v10 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Remove the stale label or comment, otherwise this will be closed in 7 days.' - stale-pr-message: 'This PR is stale because it has been open 30 days with no activity. Remove the stale label or comment, otherwise this will be closed in 7 days.' - close-issue-message: 'This issue was closed because it has been stalled for 7 days with no activity.' - close-pr-message: 'This PR was closed because it has been stalled for 7 days with no activity.' - days-before-stale: 30 - days-before-close: 7 - exempt-issue-labels: 'bug,help wanted,needs-discussion' - exempt-pr-labels: 'dependencies' From 2ac42a41e293648bd2336f957acb106889fe1dc6 Mon Sep 17 00:00:00 2001 From: Karan Singh Date: Wed, 27 May 2026 14:32:11 +0530 Subject: [PATCH 2/5] feat(auth): integrate posthog analytics and vercel reverse proxy --- .env.example | 6 + package-lock.json | 433 ++++++++++++++++++++++++++++- package.json | 3 +- src/App.tsx | 3 + src/components/Navbar.tsx | 4 + src/components/PostHogPageView.tsx | 21 ++ src/main.tsx | 23 +- src/pages/AuthPage.tsx | 11 + vercel.json | 8 + 9 files changed, 508 insertions(+), 4 deletions(-) create mode 100644 src/components/PostHogPageView.tsx diff --git a/.env.example b/.env.example index 5548efe..598cb3a 100644 --- a/.env.example +++ b/.env.example @@ -11,3 +11,9 @@ VITE_API_URL= # Dev mode — shows the "Dev Login" button that bypasses Google OAuth. # Set to "true" locally. NEVER set in Vercel env vars. VITE_DEV_MODE=true + +# ── PostHog Analytics ───────────────────────────────────────────────────────── +# Leave blank locally — PostHog is silently disabled when this key is missing. +# Set both vars in your Vercel / production environment dashboard. +VITE_POSTHOG_KEY= +VITE_POSTHOG_HOST=https://us.i.posthog.com diff --git a/package-lock.json b/package-lock.json index 129c527..4055f43 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "framer-motion": "^12.38.0", "leaflet": "^1.9.4", "lucide-react": "^1.16.0", + "posthog-js": "^1.376.2", "react": "^19.2.4", "react-dom": "^19.2.4", "react-leaflet": "^5.0.0", @@ -586,6 +587,252 @@ "@emnapi/runtime": "^1.7.1" } }, + "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.208.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.208.0.tgz", + "integrity": "sha512-CjruKY9V6NMssL/T1kAFgzosF1v9o6oeN+aX5JB/C/xPNtmgIJqcXHG7fA82Ou1zCpWGl4lROQUKwUNE1pMCyg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.3.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/core": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.2.0.tgz", + "integrity": "sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw==", + "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-http": { + "version": "0.208.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-http/-/exporter-logs-otlp-http-0.208.0.tgz", + "integrity": "sha512-jOv40Bs9jy9bZVLo/i8FwUiuCvbjWDI+ZW13wimJm4LjnlwJxGgB+N/VWOZUTpM+ah/awXeQqKdNlpLf2EjvYg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.208.0", + "@opentelemetry/core": "2.2.0", + "@opentelemetry/otlp-exporter-base": "0.208.0", + "@opentelemetry/otlp-transformer": "0.208.0", + "@opentelemetry/sdk-logs": "0.208.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/otlp-exporter-base": { + "version": "0.208.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.208.0.tgz", + "integrity": "sha512-gMd39gIfVb2OgxldxUtOwGJYSH8P1kVFFlJLuut32L6KgUC4gl1dMhn+YC2mGn0bDOiQYSk/uHOdSjuKp58vvA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.2.0", + "@opentelemetry/otlp-transformer": "0.208.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/otlp-transformer": { + "version": "0.208.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.208.0.tgz", + "integrity": "sha512-DCFPY8C6lAQHUNkzcNT9R+qYExvsk6C5Bto2pbNxgicpcSWbe2WHShLxkOxIdNcBiYPdVHv/e7vH7K6TI+C+fQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.208.0", + "@opentelemetry/core": "2.2.0", + "@opentelemetry/resources": "2.2.0", + "@opentelemetry/sdk-logs": "0.208.0", + "@opentelemetry/sdk-metrics": "2.2.0", + "@opentelemetry/sdk-trace-base": "2.2.0", + "protobufjs": "^7.3.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/otlp-transformer/node_modules/@opentelemetry/resources": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.2.0.tgz", + "integrity": "sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.2.0", + "@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/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/resources/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/sdk-logs": { + "version": "0.208.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.208.0.tgz", + "integrity": "sha512-QlAyL1jRpOeaqx7/leG1vJMp84g0xKP6gJmfELBpnI4O/9xPX+Hu5m1POk9Kl+veNkyth5t19hRlN6tNY1sjbA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.208.0", + "@opentelemetry/core": "2.2.0", + "@opentelemetry/resources": "2.2.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.4.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-logs/node_modules/@opentelemetry/resources": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.2.0.tgz", + "integrity": "sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.2.0", + "@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-metrics": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.2.0.tgz", + "integrity": "sha512-G5KYP6+VJMZzpGipQw7Giif48h6SGQ2PFKEYCybeXJsOCB4fp8azqMAAzE5lnnHK3ZVwYQrgmFbsUJO/zOnwGw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.2.0", + "@opentelemetry/resources": "2.2.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-metrics/node_modules/@opentelemetry/resources": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.2.0.tgz", + "integrity": "sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.2.0", + "@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.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.2.0.tgz", + "integrity": "sha512-xWQgL0Bmctsalg6PaXExmzdedSp3gyKV8mQBwK/j9VGdCDu2fmXIb2gAehBKbkXCpJ4HPkgv3QfoJWRT4dHWbw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.2.0", + "@opentelemetry/resources": "2.2.0", + "@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/node_modules/@opentelemetry/resources": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.2.0.tgz", + "integrity": "sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.2.0", + "@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/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/@oxc-project/types": { "version": "0.122.0", "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.122.0.tgz", @@ -595,6 +842,84 @@ "url": "https://github.com/sponsors/Boshen" } }, + "node_modules/@posthog/core": { + "version": "1.29.11", + "resolved": "https://registry.npmjs.org/@posthog/core/-/core-1.29.11.tgz", + "integrity": "sha512-/4EF7oxAFSWJgaXxppT8bdYp7MGAnWFnKz994+MetTz/T6CKbYpjqIXHCofQXtcOXjEclTYj91igA+IkVFKiSg==", + "license": "MIT", + "dependencies": { + "@posthog/types": "1.376.2" + } + }, + "node_modules/@posthog/types": { + "version": "1.376.2", + "resolved": "https://registry.npmjs.org/@posthog/types/-/types-1.376.2.tgz", + "integrity": "sha512-Y3ROpAxNqgcy2G0w6JoG5Gt+P6WNY2lkHTPMPzWqexRwemYbFegDi5AifDyD9/tstKTlOYKTTExtaJ5EBcghyQ==", + "license": "MIT" + }, + "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/@react-leaflet/core": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@react-leaflet/core/-/core-3.0.0.tgz", @@ -1155,7 +1480,6 @@ "version": "24.12.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.2.tgz", "integrity": "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==", - "devOptional": true, "license": "MIT", "dependencies": { "undici-types": "~7.16.0" @@ -1181,6 +1505,13 @@ "@types/react": "^19.2.0" } }, + "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", + "optional": true + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.58.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.58.0.tgz", @@ -1791,6 +2122,17 @@ "url": "https://opencollective.com/express" } }, + "node_modules/core-js": { + "version": "3.49.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.49.0.tgz", + "integrity": "sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg==", + "hasInstallScript": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, "node_modules/cross-env": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-10.1.0.tgz", @@ -1865,6 +2207,15 @@ "node": ">=8" } }, + "node_modules/dompurify": { + "version": "3.4.6", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.6.tgz", + "integrity": "sha512-+7gzEI8trIIQkVCvQ3ucGtNfH3nOmDgVTzc62rAAOlMxLth78pwpPoZCPc7CyRzAQF89MqcfPdEWkDwnjgqktg==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.331", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.331.tgz", @@ -2137,6 +2488,12 @@ } } }, + "node_modules/fflate": { + "version": "0.4.8", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.4.8.tgz", + "integrity": "sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==", + "license": "MIT" + }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -2763,6 +3120,12 @@ "dev": true, "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/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -2987,6 +3350,37 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/posthog-js": { + "version": "1.376.2", + "resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.376.2.tgz", + "integrity": "sha512-Anz2pCp7dcNbammTExiZpcKC08dxfrHYaJgaXH6rq5x3Zcfj/4FkcMJF2cGCrdQXel5Y4vftiVSseZda0HAQTQ==", + "license": "SEE LICENSE IN LICENSE", + "dependencies": { + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/api-logs": "^0.208.0", + "@opentelemetry/exporter-logs-otlp-http": "^0.208.0", + "@opentelemetry/resources": "^2.2.0", + "@opentelemetry/sdk-logs": "^0.208.0", + "@posthog/core": "1.29.11", + "@posthog/types": "1.376.2", + "core-js": "^3.38.1", + "dompurify": "^3.3.2", + "fflate": "^0.4.8", + "preact": "^10.28.2", + "query-selector-shadow-dom": "^1.0.1", + "web-vitals": "^5.1.0" + } + }, + "node_modules/preact": { + "version": "10.29.2", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.29.2.tgz", + "integrity": "sha512-7tNmwg/7mzzAoB/8kSg6Hl37JraAZw3Z3A0JSY7VXlZwo82Xn0G7wKbNNs2qoF4ZEEsQGTwDAroNdqKs1ofJxQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -2997,6 +3391,30 @@ "node": ">= 0.8.0" } }, + "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/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -3007,6 +3425,12 @@ "node": ">=6" } }, + "node_modules/query-selector-shadow-dom": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/query-selector-shadow-dom/-/query-selector-shadow-dom-1.0.1.tgz", + "integrity": "sha512-lT5yCqEBgfoMYpf3F2xQRK7zEr1rhIIZuceDK6+xRkJQ4NMbHTwXqk4NkwDwQMNqXgG9r9fyHnzwNVs6zV5KRw==", + "license": "MIT" + }, "node_modules/react": { "version": "19.2.4", "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", @@ -3389,7 +3813,6 @@ "version": "7.16.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", - "devOptional": true, "license": "MIT" }, "node_modules/update-browserslist-db": { @@ -3510,6 +3933,12 @@ } } }, + "node_modules/web-vitals": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-5.2.0.tgz", + "integrity": "sha512-i2z98bEmaCqSDiHEDu+gHl/dmR4Q+TxFmG3/13KkMO+o8UxQzCqWaDRCiLgEa41nlO4VpXSI0ASa1xWmO9sBlA==", + "license": "Apache-2.0" + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index f05bec4..1dd7bb7 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "framer-motion": "^12.38.0", "leaflet": "^1.9.4", "lucide-react": "^1.16.0", + "posthog-js": "^1.376.2", "react": "^19.2.4", "react-dom": "^19.2.4", "react-leaflet": "^5.0.0", @@ -43,4 +44,4 @@ "typescript-eslint": "^8.57.0", "vite": "^8.0.1" } -} \ No newline at end of file +} diff --git a/src/App.tsx b/src/App.tsx index fc4d9e2..339d66b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -7,10 +7,13 @@ import ScannerPage from './pages/ScannerPage'; import AnalysisDashboard from './pages/AnalysisDashboard'; import MarketMapPage from './pages/MarketMapPage'; import ResultsPage from './pages/ResultsPage'; +import PostHogPageView from './components/PostHogPageView'; export default function App() { return ( + {/* Fires a $pageview event to PostHog on every SPA route change */} + }> } /> diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index 346c313..059c323 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -1,11 +1,13 @@ import { Link, useLocation, useNavigate } from 'react-router-dom'; import { useState, useRef, useEffect } from 'react'; +import { usePostHog } from 'posthog-js/react'; import { api, clearToken, isAuthenticated } from '../lib/api'; import type { UserProfile } from '../lib/types'; export default function Navbar() { const location = useLocation(); const navigate = useNavigate(); + const posthog = usePostHog(); const [isDropdownOpen, setIsDropdownOpen] = useState(false); const [showToast, setShowToast] = useState(false); const [profile, setProfile] = useState(null); @@ -43,6 +45,8 @@ export default function Navbar() { const handleLogout = () => { setIsDropdownOpen(false); clearToken(); + // Reset PostHog session so next user on this device isn't tracked as this user + posthog?.reset(); navigate('/'); setShowToast(true); setTimeout(() => setShowToast(false), 3000); diff --git a/src/components/PostHogPageView.tsx b/src/components/PostHogPageView.tsx new file mode 100644 index 0000000..ed01a8c --- /dev/null +++ b/src/components/PostHogPageView.tsx @@ -0,0 +1,21 @@ +import { useEffect } from 'react'; +import { useLocation } from 'react-router-dom'; +import { usePostHog } from 'posthog-js/react'; + +/** + * Listens to React Router route changes and fires a PostHog $pageview event + * on every navigation. This is required for SPAs since the page never + * actually reloads between routes. + */ +export default function PostHogPageView() { + const location = useLocation(); + const posthog = usePostHog(); + + useEffect(() => { + posthog?.capture('$pageview', { + $current_url: window.location.href, + }); + }, [location, posthog]); + + return null; +} diff --git a/src/main.tsx b/src/main.tsx index bef5202..792d000 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,10 +1,31 @@ import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' +import posthog from 'posthog-js' +import { PostHogProvider } from 'posthog-js/react' import './index.css' import App from './App.tsx' +// PostHog is only initialized when the key is present. +// Contributors running locally without the key will have it silently disabled. +const POSTHOG_KEY = import.meta.env.VITE_POSTHOG_KEY as string | undefined; +const POSTHOG_HOST = (import.meta.env.VITE_POSTHOG_HOST as string | undefined) ?? 'https://us.i.posthog.com'; + +if (POSTHOG_KEY) { + posthog.init(POSTHOG_KEY, { + api_host: POSTHOG_HOST, + defaults: '2026-01-30', + // Loads async — won't block rendering + loaded: (ph) => { + // Disable in dev so local sessions don't pollute the dashboard + if (import.meta.env.DEV) ph.opt_out_capturing(); + }, + }); +} + createRoot(document.getElementById('root')!).render( - + + + , ) diff --git a/src/pages/AuthPage.tsx b/src/pages/AuthPage.tsx index d0203f3..78f1c3e 100644 --- a/src/pages/AuthPage.tsx +++ b/src/pages/AuthPage.tsx @@ -1,5 +1,6 @@ import { useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; +import { usePostHog } from 'posthog-js/react'; import StatusTerminal from '../components/StatusTerminal'; import { api, setToken, isAuthenticated } from '../lib/api'; @@ -9,6 +10,7 @@ const IS_DEV_MODE = import.meta.env.VITE_DEV_MODE === 'true'; export default function AuthPage() { const navigate = useNavigate(); + const posthog = usePostHog(); const [status, setStatus] = useState<'idle' | 'processing' | 'error'>('idle'); const [errorMsg, setErrorMsg] = useState(''); @@ -31,6 +33,14 @@ export default function AuthPage() { Promise.resolve().then(() => setStatus('processing')); setToken(accessToken); window.history.replaceState({}, '', '/auth'); + // Identify the user in PostHog using their JWT sub claim as the ID. + // The token is a JWT; we decode the payload to get the user ID. + try { + const payload = JSON.parse(atob(accessToken.split('.')[1])); + posthog?.identify(payload.sub, { email: payload.email }); + } catch { + // Not a JWT or malformed — skip identification silently + } navigate('/mode', { replace: true }); return; } @@ -48,6 +58,7 @@ export default function AuthPage() { /** Dev-only: skip OAuth entirely, store the bypass token, go straight to /mode */ const handleDevLogin = () => { setToken(DEV_BYPASS_TOKEN); + posthog?.identify('dev-user', { email: 'dev@local' }); navigate('/mode', { replace: true }); }; diff --git a/vercel.json b/vercel.json index 6a92648..40fd486 100644 --- a/vercel.json +++ b/vercel.json @@ -1,5 +1,13 @@ { "rewrites": [ + { + "source": "/ingest/static/:path*", + "destination": "https://us-assets.i.posthog.com/static/:path*" + }, + { + "source": "/ingest/:path*", + "destination": "https://us.i.posthog.com/:path*" + }, { "source": "/(.*)", "destination": "/" } ] } From e30031a4aec85dbc1bcbdf0788c2c1e0186a6a7d Mon Sep 17 00:00:00 2001 From: Karan Singh Date: Wed, 27 May 2026 14:32:21 +0530 Subject: [PATCH 3/5] docs(api): finalize open-source instructions and remove emojis --- CONTRIBUTING.md | 4 ++-- DOCUMENTATION.md | 6 +++--- README.md | 2 +- scripts/dev-setup.sh | 10 +++++----- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 38f90fe..de0865d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -126,7 +126,7 @@ Do not open a PR if any of these are failing locally. Fix them first. ### Manual requirements (checked during review) - [ ] Branch is up to date with `main` — rebase, do not merge -- [ ] No `.env` files, credentials, secrets, or API keys are present anywhere in the diff +- [ ] No `.env` or `.env.local` files, credentials, secrets, or API keys are present anywhere in the diff - [ ] No model weight files (`.pth`, `.pt`, `.onnx`, `.bin`) are committed - [ ] No `__pycache__/` directories, `.pyc` files, or macOS `._*` metadata files are committed - [ ] New endpoints include corresponding tests in `backend/tests/` or `backend/test_*.py` @@ -192,7 +192,7 @@ PRs will be closed without review if they: - Are submitted without a prior issue or maintainer approval for non-trivial changes - Fail any automated CI gate -- Contain `.env` files, secrets, model weights, or binary artifacts +- Contain `.env` or `.env.local` files, secrets, model weights, or binary artifacts - Touch the deployment configuration (`vercel.json`, `Dockerfile`, `startup.sh`) without approval - Introduce a new dependency (npm or PyPI) without prior discussion - Rewrite existing, working logic without a documented reason diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index f05e970..93eb255 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -862,9 +862,9 @@ npm run dev | Mode | Endpoint | Models Required | Use Case | |------|---------|----------------|----------| -| Demo | `/api/v1/scan-auto` | ❌ No | Demo presentations, CI | -| Auto (real) | `/api/v1/scan-auto` | ✅ Yes | Single-image mobile scan | -| Full scan | `/api/v1/scan` | ✅ Yes | Multi-region expert mode | +| Demo | `/api/v1/scan-auto` | No | Demo presentations, CI | +| Auto (real) | `/api/v1/scan-auto` | Yes | Single-image mobile scan | +| Full scan | `/api/v1/scan` | Yes | Multi-region expert mode | ### Production Deployment - Frontend: Vercel / Netlify (static export) diff --git a/README.md b/README.md index 948ec2d..f6436e4 100644 --- a/README.md +++ b/README.md @@ -116,7 +116,7 @@ DEV_BYPASS_AUTH=true # never set true in production | Script | Description | |---|---| -| `npm run setup` | Install dependencies, write `.env`, optionally start local Supabase | +| `npm run setup` | Install dependencies, write `.env.local` (frontend) & `.env` (backend), optionally start local Supabase | | `npm run dev` | Start frontend and backend concurrently | | `npm run build` | Production build (TypeScript + Vite) | | `npm run lint` | Run ESLint across the codebase | diff --git a/scripts/dev-setup.sh b/scripts/dev-setup.sh index 9a78fc1..2e5b9b7 100644 --- a/scripts/dev-setup.sh +++ b/scripts/dev-setup.sh @@ -15,8 +15,8 @@ BOLD='\033[1m' RESET='\033[0m' log() { echo -e "${CYAN}[setup]${RESET} $1"; } -ok() { echo -e "${GREEN} ✓${RESET} $1"; } -warn() { echo -e "${YELLOW} ⚠${RESET} $1"; } +ok() { echo -e "${GREEN} [OK]${RESET} $1"; } +warn() { echo -e "${YELLOW} [WARN]${RESET} $1"; } echo "" echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}" @@ -32,7 +32,7 @@ ok "node_modules ready" # ── Step 2: Frontend env ────────────────────────────────────────────────────── if [ ! -f "$REPO_ROOT/.env.local" ]; then - log "Creating .env.local (enables ⚡ Dev Login button)..." + log "Creating .env.local (enables Dev Login button)..." echo "VITE_DEV_MODE=true" > "$REPO_ROOT/.env.local" ok ".env.local created" else @@ -113,11 +113,11 @@ fi # ── Done ─────────────────────────────────────────────────────────────────────── echo "" echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}" -echo -e "${GREEN}${BOLD} ✓ Setup complete!${RESET}" +echo -e "${GREEN}${BOLD} [OK] Setup complete!${RESET}" echo "" echo -e " Run: ${BOLD}npm run dev${RESET}" echo -e " Open: ${BOLD}http://localhost:5173${RESET}" -echo -e " Auth: Click ${BOLD}⚡ DEV LOGIN${RESET} — no Google account needed" +echo -e " Auth: Click ${BOLD}DEV LOGIN${RESET} — no Google account needed" if [ "$USE_LOCAL_SUPABASE" = true ]; then echo -e " DB: Local Docker — ${BOLD}fully isolated from production${RESET}" else From 8d750c19ce2fb20ad057367789af725f8b6ff115 Mon Sep 17 00:00:00 2001 From: Karan Singh Date: Wed, 27 May 2026 14:36:45 +0530 Subject: [PATCH 4/5] fix(lint): add posthog to useEffect dependency array --- src/pages/AuthPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/AuthPage.tsx b/src/pages/AuthPage.tsx index 78f1c3e..26e9b64 100644 --- a/src/pages/AuthPage.tsx +++ b/src/pages/AuthPage.tsx @@ -49,7 +49,7 @@ export default function AuthPage() { if (isAuthenticated()) { navigate('/mode', { replace: true }); } - }, [navigate]); + }, [navigate, posthog]); const handleGoogleLogin = () => { window.location.href = api.loginUrl(); From ad6a9a630634023fb7367ad6292256fde97eb219 Mon Sep 17 00:00:00 2001 From: Karan Singh Date: Wed, 27 May 2026 14:40:18 +0530 Subject: [PATCH 5/5] chore(deps): sync package-lock.json with package.json --- package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index c3d80a0..4b8563c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1477,9 +1477,9 @@ } }, "node_modules/@types/node": { - "version": "24.12.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.2.tgz", - "integrity": "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==", + "version": "25.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.1.tgz", + "integrity": "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==", "license": "MIT", "dependencies": { "undici-types": ">=7.24.0 <7.24.7" @@ -3810,9 +3810,9 @@ } }, "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", + "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", "license": "MIT" }, "node_modules/update-browserslist-db": {