From ac04cd8f3e9a6510199f3d9433c9d7aa865056e2 Mon Sep 17 00:00:00 2001 From: Hayden Koch Date: Wed, 8 Apr 2026 03:20:07 -0700 Subject: [PATCH 01/11] feat: harden runtime config and delivery flows --- .env.example | 3 + .env.local.example | 3 + .github/CODEOWNERS | 1 + .github/dependabot.yml | 12 + .github/workflows/ci.yml | 54 +- .prettierignore | 4 + CONTRIBUTING.md | 42 + LICENSE | 21 + README.md | 66 +- SECURITY.md | 19 + docs/PRODUCTION_RUNBOOK.md | 36 + drizzle.config.ts | 10 +- package.json | 26 +- pnpm-lock.yaml | 733 ++++++++++++++---- scripts/audit-toolbox-content.ts | 55 +- scripts/check-migrations-ci.mjs | 85 ++ scripts/check-secrets.ts | 79 ++ scripts/host-verified-jobs-funding-logos.mjs | 18 +- scripts/lib/curated-funding-sync.mjs | 6 +- scripts/push-local-data-to-railway.mjs | 1 + scripts/seed-coils.js | 34 - .../components/molecules/FundingCard.svelte | 18 +- .../molecules/LocationRadiusControl.svelte | 10 +- .../organisms/CoilDetailActionRail.svelte | 16 +- .../organisms/CoilDetailHero.svelte | 8 +- .../components/organisms/KbActionDock.svelte | 16 +- .../organisms/ListLocationMap.svelte | 82 +- .../toolbox/ToolboxPdfViewer.svelte | 9 +- src/lib/components/ui/slider/slider.svelte | 8 +- src/lib/config/runtime-config-core.ts | 472 +++++++++++ src/lib/config/runtime-secrets.ts | 465 +++++++++++ src/lib/server/admin-users.ts | 5 +- src/lib/server/auth.ts | 9 +- src/lib/server/db/index.ts | 7 +- src/lib/server/email.ts | 19 +- .../server/ingestion/adapters/csv-import.ts | 79 +- src/lib/server/marketing-email.ts | 15 +- src/lib/server/meilisearch.ts | 22 +- src/lib/server/object-storage.ts | 20 +- src/lib/server/runtime-config.ts | 295 +------ src/lib/server/runtime-secrets.ts | 29 + src/lib/server/search-service.ts | 4 +- src/routes/+page.svelte | 4 +- .../settings/integrations/+page.server.ts | 11 +- .../admin/settings/integrations/+page.svelte | 4 +- src/routes/admin/users/+page.svelte | 71 +- src/routes/api/reindex/+server.ts | 4 +- src/routes/api/source-ops/run-due/+server.ts | 4 +- src/routes/cookies/+page.svelte | 4 +- src/routes/o/+page.svelte | 10 +- src/routes/o/[slug]/+page.svelte | 164 ++-- src/routes/red-pages/[slug]/+page.svelte | 148 ++-- src/routes/toolbox/+page.svelte | 4 +- src/routes/uploads/[...path]/+server.ts | 4 +- src/routes/v/+page.server.ts | 11 +- src/routes/v/+page.svelte | 15 +- tests/admin-users-page.test.ts | 22 +- tests/csv-import-adapter.test.ts | 112 +++ tests/runtime-config.test.ts | 73 ++ tests/toolbox-presentation.test.ts | 8 +- vite.config.ts | 14 +- 61 files changed, 2679 insertions(+), 924 deletions(-) create mode 100644 .github/CODEOWNERS create mode 100644 .github/dependabot.yml create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 SECURITY.md create mode 100644 scripts/check-migrations-ci.mjs create mode 100644 scripts/check-secrets.ts create mode 100644 src/lib/config/runtime-config-core.ts create mode 100644 src/lib/config/runtime-secrets.ts create mode 100644 src/lib/server/runtime-secrets.ts create mode 100644 tests/csv-import-adapter.test.ts diff --git a/.env.example b/.env.example index 08cdf24..0fbb72e 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,8 @@ # Production or staging environment template. # Set these in your hosting provider's environment-variable UI instead of committing a real .env file. +# Prefer provider-managed secrets or mounted files. Any private key below can also be supplied as `${NAME}_FILE=/run/secrets/${NAME}`. + +KB_ENVIRONMENT="production" # Optional: Mapbox access token for event location/address geocoding (server). # Get a token at https://account.mapbox.com/access-tokens/ diff --git a/.env.local.example b/.env.local.example index e897d43..02bb773 100644 --- a/.env.local.example +++ b/.env.local.example @@ -1,5 +1,8 @@ # Local development template. # Copy this file to `.env` for everyday local development. +# Mounted secret files also work locally. Example: `BETTER_AUTH_SECRET_FILE=.secrets/better-auth-secret`. + +KB_ENVIRONMENT="development" # Optional: Mapbox access token for event location/address geocoding (server). # Get a token at https://account.mapbox.com/access-tokens/ diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..a7619ae --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @haydenkoch diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..e19f00d --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,12 @@ +version: 2 +updates: + - package-ecosystem: npm + directory: / + schedule: + interval: weekly + open-pull-requests-limit: 10 + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + open-pull-requests-limit: 10 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d506751..e299984 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -58,15 +58,15 @@ jobs: run: pnpm build steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 with: fetch-depth: 0 - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 with: version: 10 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 with: node-version: 24 cache: pnpm @@ -74,12 +74,16 @@ jobs: - name: Install run: pnpm install --frozen-lockfile + - name: Verify committed migrations + if: matrix.task == 'typecheck' + run: node scripts/check-migrations-ci.mjs + - name: Run ${{ matrix.task }} run: ${{ matrix.run }} - name: Upload build artifact if: matrix.task == 'build' - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 with: name: app-build path: build @@ -107,13 +111,13 @@ jobs: shard: [1, 2, 3] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 with: version: 10 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 with: node-version: 24 cache: pnpm @@ -164,13 +168,13 @@ jobs: --health-retries 5 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 with: version: 10 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 with: node-version: 24 cache: pnpm @@ -179,7 +183,7 @@ jobs: run: pnpm install --frozen-lockfile - name: Download build artifact - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 with: name: app-build path: build @@ -264,13 +268,13 @@ jobs: --health-retries 10 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 with: version: 10 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 with: node-version: 24 cache: pnpm @@ -279,7 +283,7 @@ jobs: run: pnpm install --frozen-lockfile - name: Download build artifact - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 with: name: app-build path: build @@ -362,13 +366,13 @@ jobs: --health-retries 5 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 with: version: 10 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 with: node-version: 24 cache: pnpm @@ -377,7 +381,7 @@ jobs: run: pnpm install --frozen-lockfile - name: Download build artifact - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 with: name: app-build path: build @@ -455,13 +459,13 @@ jobs: --health-retries 5 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 with: version: 10 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 with: node-version: 24 cache: pnpm @@ -470,13 +474,13 @@ jobs: run: pnpm install --frozen-lockfile - name: Download build artifact - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 with: name: app-build path: build - name: Restore Playwright browser cache - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 with: path: ~/.cache/ms-playwright key: ${{ runner.os }}-playwright-${{ hashFiles('pnpm-lock.yaml') }} @@ -519,7 +523,7 @@ jobs: - name: Upload Playwright artifacts if: failure() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 with: name: playwright-artifacts-${{ matrix.project }} path: | diff --git a/.prettierignore b/.prettierignore index 4926fc5..276db21 100644 --- a/.prettierignore +++ b/.prettierignore @@ -10,3 +10,7 @@ bun.lockb /drizzle/ /.claude/ /test-results/ +/AGENTS.md +/CLAUDE.md +/docs/plans/** +/docs/reviews/** diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..e191a67 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,42 @@ +# Contributing + +## Local setup + +```sh +nvm use +pnpm install --frozen-lockfile +cp .env.local.example .env +pnpm dev +``` + +Use Node `24.14.0` and pnpm `10.32.1`. + +## Quality gates + +Before opening a pull request, run: + +```sh +pnpm check +pnpm lint +pnpm build +``` + +Run targeted test commands when you touch the corresponding area, and prefer the full CI matrix before merging release-sensitive changes. + +## Database changes + +- Use `pnpm db:push` only for local iteration. +- Commit generated migrations for any schema-affecting change. +- Production and staging deploys must use committed migrations via `pnpm db:deploy`. + +## Branch and PR workflow + +- Open changes through pull requests. +- Expect protected branches to require review and passing CI. +- Keep PRs scoped so required checks stay readable and actionable. + +## Repository conventions + +- `.vscode/*`, `.cursor/mcp.json`, and similar workspace files are intentionally tracked to document the supported editor and MCP setup. +- Avoid committing real secrets or environment files. +- Do not force-push protected branches. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..1d6274c --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Hayden Koch + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 2655050..02b70e2 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ Use [docs/README.md](/Users/hayden/Desktop/kb/site/docs/README.md) as the map of From the `site/` directory: ```sh +nvm use pnpm install cp .env.local.example .env pnpm dev @@ -71,6 +72,55 @@ curl http://localhost:7700/health Use `.env.local.example` as the local development template and copy it to `site/.env`. Use `.env.example` as the production or staging checklist for your hosting environment variables. +Use `.env.staging.example` for a staging deploy baseline and `.env.ci.example` for CI/app-test boot. + +## Secrets Management + +The repo now treats environment management as one versioned contract: + +- Keep the templates in git: + - `.env.local.example` + - `.env.staging.example` + - `.env.example` + - `.env.ci.example` +- Keep real values in your provider secret store, never in git. +- Prefer `*_FILE` for mounted secrets when your platform supports it. + - Example: `BETTER_AUTH_SECRET_FILE=/run/secrets/better_auth_secret` +- Set `KB_ENVIRONMENT` explicitly in staging, production, and CI. + - `development` + - `ci` + - `staging` + - `production` +- Validate before deploy or before running CI: + +```sh +pnpm secrets:check +pnpm secrets:check:staging +pnpm secrets:check:production +pnpm secrets:check:ci +``` + +Recommended environment split: + +- Local development: + - Use local Postgres/MinIO/Meilisearch values from `.env.local.example` + - Keep OAuth, Sentry, and SMTP optional unless you are actively testing them +- Staging: + - Use the same variable names as production + - Point them at staging-only DB, bucket, search host, SMTP sender, and OAuth callback origin + - Generate separate `BETTER_AUTH_SECRET`, `REINDEX_SECRET`, and `SOURCE_OPS_SECRET` +- Production: + - Same contract as staging, with production URLs and production-only secrets +- CI: + - Only the boot-critical app values are required by default + - Add search/object-storage vars only if the CI job actually exercises those services + +The easiest way to keep environments in sync is to treat template changes like schema changes: + +1. Update the example files in git whenever the app contract changes. +2. Run `pnpm secrets:check:` locally or in CI. +3. Copy only the changed keys into Railway/GitHub Actions/your secret store. +4. Keep staging and production on the same key set, differing only by values. Public placeholder/branding assets are now expected to already exist in MinIO, with `PUBLIC_ASSET_BASE_URL` pointing at a public bucket root or CDN origin. `pnpm images:sync` @@ -95,6 +145,7 @@ pnpm auth:schema Notes: +- `pnpm db:push` is for local development only. Do not use it in staging or production deploys. - `pnpm db:seed` now runs the launch-data seed: events, non-event coils, and source registry seeds when the shared source seed file is available. - `pnpm db:seed:events` runs the legacy events CSV seed only. - `pnpm db:seed:coils` seeds Funding, Red Pages, Jobs, and Toolbox sample content. @@ -106,7 +157,7 @@ Notes: - `POST /api/reindex` is protected in production. Use an admin/moderator session or send `x-reindex-secret` matching `REINDEX_SECRET`. - The admin UI also exposes search reindexing at `/admin/settings/search`. - `sitemap.xml`, `robots.txt`, and `manifest.webmanifest` are generated as part of the app surface. -- `pnpm start` now runs `pnpm db:migrate` before booting the Node server so deploys apply schema changes as part of startup on single-instance environments. +- `pnpm start` now runs `pnpm db:migrate` before booting the Node server so deploys apply committed migrations as part of startup on single-instance environments. ## Production Environment Contract @@ -127,6 +178,7 @@ Recommended observability settings: - `ERROR_WEBHOOK_URL` In production, startup now validates this contract and fails fast on missing or clearly invalid required settings. +Staging uses the same contract and should set `KB_ENVIRONMENT=staging`. ## Railway Deployment @@ -148,6 +200,7 @@ Recommended Railway setup: Operational notes: - Railway injects `PORT`; the Node adapter will bind to it automatically. +- Railway pre-deploys should run committed migrations only. Keep `pnpm db:push` for local iteration and use `pnpm db:deploy` / `pnpm db:migrate` for deploy environments. - The generated `RAILWAY_PUBLIC_DOMAIN` is good enough for a first deploy, but switch `ORIGIN` to your custom domain before finalizing Google OAuth or canonical URLs. - If you later split this repo into multiple Railway services, move the start command into each service's dashboard settings so the shared `railway.toml` does not force the same process everywhere. @@ -156,7 +209,7 @@ Operational notes: As of the current takeover baseline: - `pnpm check` should pass. -- `pnpm lint` should pass. +- `pnpm lint` should pass for the tracked app, config, script, and test files enforced by this repo. - `pnpm test` runs the smoke and handler tests. - `pnpm test:search:indexed` validates the Meilisearch-backed contract. - `pnpm test:search:degraded` validates compatibility-mode search when Meilisearch is unavailable. @@ -176,6 +229,15 @@ As of the current takeover baseline: - `docs/ops-content-workflows.md` - `docs/SOURCE_OPS_HANDOFF.md` +## Toolchain + +Use the pinned local toolchain when contributing: + +- Node `24.14.0` via `.nvmrc` +- pnpm `10.32.1` via `packageManager` + +`pnpm install --frozen-lockfile`, `pnpm check`, `pnpm lint`, and `pnpm build` are the baseline local gates before opening a PR. + ## Short-Term Direction The current implementation direction is: diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..d18b1ad --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,19 @@ +# Security Policy + +## Reporting a vulnerability + +Please do not open public issues for suspected security vulnerabilities. + +Use one of these paths instead: + +- Open a private GitHub security advisory if you have repository access. +- Contact the maintainer directly through GitHub if you do not. + +Include: + +- a short description of the issue +- affected paths or features +- reproduction steps or a proof of concept +- impact assessment if known + +We will triage reports privately and coordinate a fix before public disclosure when possible. diff --git a/docs/PRODUCTION_RUNBOOK.md b/docs/PRODUCTION_RUNBOOK.md index 2c51a00..61e4d8c 100644 --- a/docs/PRODUCTION_RUNBOOK.md +++ b/docs/PRODUCTION_RUNBOOK.md @@ -14,6 +14,34 @@ This runbook is the current operational baseline for launching and maintaining K - `ERROR_WEBHOOK_URL` for forwarding structured server errors - `LOG_LEVEL` to tune structured stdout logging (`debug`, `info`, `warn`, `error`) +## Secrets strategy + +- Keep env templates in git: + - `.env.local.example` + - `.env.staging.example` + - `.env.example` + - `.env.ci.example` +- Keep real values in provider secret managers or deploy-platform secret UIs. +- Prefer file-backed secrets where possible: + - `DATABASE_URL_FILE` + - `BETTER_AUTH_SECRET_FILE` + - `MEILISEARCH_API_KEY_FILE` + - `MINIO_SECRET_KEY_FILE` + - `SMTP_PASS_FILE` + - `REINDEX_SECRET_FILE` + - `SOURCE_OPS_SECRET_FILE` +- Set `KB_ENVIRONMENT` explicitly per environment: + - local dev: `development` + - CI: `ci` + - staging: `staging` + - production: `production` +- Validate the contract before deploy: + - `pnpm secrets:check:staging` + - `pnpm secrets:check:production` + - `pnpm secrets:check:ci` + +Staging should mirror production keys exactly. The values should differ, but the key set should not. + ## Railway baseline - `railway.toml` is the deployment source of truth for the web service: @@ -23,6 +51,7 @@ This runbook is the current operational baseline for launching and maintaining K - healthcheck: `GET /api/health` - Generate a Railway public domain before first launch. - Set `ORIGIN` explicitly once the final public URL is known. +- Set `KB_ENVIRONMENT` to `staging` or `production` in each Railway environment. - If `ORIGIN` is temporarily omitted, the app can fall back to `RAILWAY_PUBLIC_DOMAIN`, but treat that as a bootstrap convenience rather than the steady-state production setting. ## Health checks @@ -129,6 +158,13 @@ Run one full restore drill in staging before launch and record the exact timesta 6. Spot-check `/search`, `/events`, `/account`, and a representative uploaded asset. 7. Trigger `POST /api/source-ops/run-due` once and confirm the scheduler path still works after restore. +## Environment drift prevention + +1. When a new env var is introduced, update the example templates in the same PR. +2. Run the matching `pnpm secrets:check:` command before merging. +3. Apply the new key to staging first, then production. +4. Do not create staging-only variable names when a staging-specific value under the production key name will do. + ## Incident checks - Search looks empty: diff --git a/drizzle.config.ts b/drizzle.config.ts index 4aa04fc..2af1a3c 100644 --- a/drizzle.config.ts +++ b/drizzle.config.ts @@ -1,6 +1,12 @@ import { defineConfig } from 'drizzle-kit'; +import { readFileSync } from 'node:fs'; +import { resolveRuntimeConfigValue } from './src/lib/config/runtime-secrets'; -if (!process.env.DATABASE_URL) throw new Error('DATABASE_URL is not set'); +const databaseUrl = resolveRuntimeConfigValue(process.env, 'DATABASE_URL', { + readFile: (path) => readFileSync(path, 'utf8') +}).value; + +if (!databaseUrl) throw new Error('DATABASE_URL is not set'); const isDeployEnvironment = process.env.CI === 'true' || @@ -10,7 +16,7 @@ const isDeployEnvironment = export default defineConfig({ schema: './src/lib/server/db/schema/index.ts', dialect: 'postgresql', - dbCredentials: { url: process.env.DATABASE_URL }, + dbCredentials: { url: databaseUrl }, verbose: !isDeployEnvironment, strict: true }); diff --git a/package.json b/package.json index 2a51e76..0957167 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,17 @@ "name": "site", "private": true, "version": "0.0.1", + "packageManager": "pnpm@10.32.1", "type": "module", + "engines": { + "node": "24.x", + "pnpm": "10.x" + }, + "pnpm": { + "overrides": { + "picomatch": "^4.0.4" + } + }, "scripts": { "dev": "vite dev", "build": "vite build", @@ -22,8 +32,12 @@ "test:e2e": "playwright test", "test:e2e:ci": "node scripts/run-playwright-ci.mjs", "format": "prettier --write .", + "secrets:check": "tsx scripts/check-secrets.ts", + "secrets:check:ci": "tsx scripts/check-secrets.ts --environment=ci", + "secrets:check:staging": "tsx scripts/check-secrets.ts --environment=staging", + "secrets:check:production": "tsx scripts/check-secrets.ts --environment=production", "db:push": "drizzle-kit push", - "db:deploy": "drizzle-kit push --force", + "db:deploy": "drizzle-kit migrate", "db:generate": "drizzle-kit generate", "db:migrate": "drizzle-kit migrate", "db:studio": "drizzle-kit studio", @@ -60,8 +74,8 @@ "bits-ui": "^2.16.3", "clsx": "^2.1.1", "dotenv": "^16.6.1", - "drizzle-kit": "^0.31.8", - "drizzle-orm": "^0.45.1", + "drizzle-kit": "^0.31.10", + "drizzle-orm": "^0.45.2", "embla-carousel-svelte": "^8.6.0", "eslint": "^9.39.2", "eslint-config-prettier": "^10.1.8", @@ -88,7 +102,7 @@ "typescript": "^5.9.3", "typescript-eslint": "^8.54.0", "vaul-svelte": "1.0.0-next.7", - "vite": "^7.3.1", + "vite": "^7.3.2", "vitest": "^3.2.4" }, "dependencies": { @@ -100,6 +114,7 @@ "@sentry/sveltekit": "^10.47.0", "cheerio": "^1.2.0", "csv-parse": "^6.2.1", + "exceljs": "^4.4.0", "fast-xml-parser": "^5.5.9", "mapbox-gl": "^3.19.1", "meilisearch": "^0.55.0", @@ -111,7 +126,6 @@ "site": "link:", "svelte-dnd-action": "^0.9.69", "temporal-polyfill": "^0.3.2", - "tinymce": "^8.3.2", - "xlsx": "^0.18.5" + "tinymce": "^8.3.2" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3a9094c..92aa91d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,6 +4,9 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +overrides: + picomatch: ^4.0.4 + importers: .: @@ -25,13 +28,16 @@ importers: version: 4.3.1 '@sentry/sveltekit': specifier: ^10.47.0 - version: 10.47.0(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.8)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8)(typescript@5.9.3)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(rollup@4.59.0)(svelte@5.53.8)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)) + version: 10.47.0(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.8)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8)(typescript@5.9.3)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(rollup@4.59.0)(svelte@5.53.8)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)) cheerio: specifier: ^1.2.0 version: 1.2.0 csv-parse: specifier: ^6.2.1 version: 6.2.1 + exceljs: + specifier: ^4.4.0 + version: 4.4.0 fast-xml-parser: specifier: ^5.5.9 version: 5.5.9 @@ -68,16 +74,13 @@ importers: tinymce: specifier: ^8.3.2 version: 8.3.2 - xlsx: - specifier: ^0.18.5 - version: 0.18.5 devDependencies: '@axe-core/playwright': specifier: ^4.11.1 version: 4.11.1(playwright-core@1.59.1) '@better-auth/cli': specifier: ~1.4.21 - version: 1.4.21(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.8)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8)(typescript@5.9.3)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(better-call@1.1.8(zod@4.3.6))(drizzle-kit@0.31.9)(jose@6.2.1)(kysely@0.28.11)(nanostores@1.1.1)(postgres@3.4.8)(svelte@5.53.8)(vitest@3.2.4(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)) + version: 1.4.21(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.8)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8)(typescript@5.9.3)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(better-call@1.1.8(zod@4.3.6))(drizzle-kit@0.31.10)(jose@6.2.1)(kysely@0.28.11)(nanostores@1.1.1)(postgres@3.4.8)(svelte@5.53.8)(vitest@3.2.4(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)) '@eslint/compat': specifier: ^2.0.2 version: 2.0.3(eslint@9.39.4(jiti@2.6.1)) @@ -95,13 +98,13 @@ importers: version: 1.59.1 '@sveltejs/adapter-node': specifier: ^5.3.3 - version: 5.5.4(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.8)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8)(typescript@5.9.3)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0))) + version: 5.5.4(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.8)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8)(typescript@5.9.3)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0))) '@sveltejs/kit': specifier: ^2.50.2 - version: 2.53.4(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.8)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8)(typescript@5.9.3)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)) + version: 2.53.4(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.8)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8)(typescript@5.9.3)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)) '@sveltejs/vite-plugin-svelte': specifier: ^6.2.4 - version: 6.2.4(svelte@5.53.8)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)) + version: 6.2.4(svelte@5.53.8)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)) '@tailwindcss/forms': specifier: ^0.5.11 version: 0.5.11(tailwindcss@4.2.1) @@ -110,7 +113,7 @@ importers: version: 0.5.19(tailwindcss@4.2.1) '@tailwindcss/vite': specifier: ^4.1.18 - version: 4.2.1(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)) + version: 4.2.1(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)) '@tanstack/table-core': specifier: ^8.21.3 version: 8.21.3 @@ -125,10 +128,10 @@ importers: version: 2.16.1 better-auth: specifier: ~1.4.21 - version: 1.4.21(@prisma/client@5.22.0)(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.8)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8)(typescript@5.9.3)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(better-sqlite3@12.6.2)(drizzle-kit@0.31.9)(drizzle-orm@0.45.1(@opentelemetry/api@1.9.1)(@prisma/client@5.22.0)(@types/pg@8.18.0)(better-sqlite3@12.6.2)(kysely@0.28.11)(pg@8.20.0)(postgres@3.4.8))(pg@8.20.0)(svelte@5.53.8)(vitest@3.2.4(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)) + version: 1.4.21(@prisma/client@5.22.0)(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.8)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8)(typescript@5.9.3)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(better-sqlite3@12.6.2)(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@opentelemetry/api@1.9.1)(@prisma/client@5.22.0)(@types/pg@8.18.0)(better-sqlite3@12.6.2)(kysely@0.28.11)(pg@8.20.0)(postgres@3.4.8))(pg@8.20.0)(svelte@5.53.8)(vitest@3.2.4(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)) bits-ui: specifier: ^2.16.3 - version: 2.16.3(@internationalized/date@3.12.0)(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.8)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8)(typescript@5.9.3)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8) + version: 2.16.3(@internationalized/date@3.12.0)(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.8)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8)(typescript@5.9.3)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8) clsx: specifier: ^2.1.1 version: 2.1.1 @@ -136,11 +139,11 @@ importers: specifier: ^16.6.1 version: 16.6.1 drizzle-kit: - specifier: ^0.31.8 - version: 0.31.9 + specifier: ^0.31.10 + version: 0.31.10 drizzle-orm: - specifier: ^0.45.1 - version: 0.45.1(@opentelemetry/api@1.9.1)(@prisma/client@5.22.0)(@types/pg@8.18.0)(better-sqlite3@12.6.2)(kysely@0.28.11)(pg@8.20.0)(postgres@3.4.8) + specifier: ^0.45.2 + version: 0.45.2(@opentelemetry/api@1.9.1)(@prisma/client@5.22.0)(@types/pg@8.18.0)(better-sqlite3@12.6.2)(kysely@0.28.11)(pg@8.20.0)(postgres@3.4.8) embla-carousel-svelte: specifier: ^8.6.0 version: 8.6.0(svelte@5.53.8) @@ -155,7 +158,7 @@ importers: version: 3.15.1(eslint@9.39.4(jiti@2.6.1))(svelte@5.53.8) formsnap: specifier: ^2.0.1 - version: 2.0.1(svelte@5.53.8)(sveltekit-superforms@2.30.0(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.8)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8)(typescript@5.9.3)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(@types/json-schema@7.0.15)(svelte@5.53.8)(typescript@5.9.3)) + version: 2.0.1(svelte@5.53.8)(sveltekit-superforms@2.30.0(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.8)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8)(typescript@5.9.3)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(@types/json-schema@7.0.15)(svelte@5.53.8)(typescript@5.9.3)) globals: specifier: ^17.3.0 version: 17.4.0 @@ -188,13 +191,13 @@ importers: version: 5.53.8 svelte-check: specifier: ^4.4.2 - version: 4.4.5(picomatch@4.0.3)(svelte@5.53.8)(typescript@5.9.3) + version: 4.4.5(picomatch@4.0.4)(svelte@5.53.8)(typescript@5.9.3) svelte-sonner: specifier: ^1.1.0 version: 1.1.0(svelte@5.53.8) sveltekit-superforms: specifier: ^2.30.0 - version: 2.30.0(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.8)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8)(typescript@5.9.3)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(@types/json-schema@7.0.15)(svelte@5.53.8)(typescript@5.9.3) + version: 2.30.0(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.8)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8)(typescript@5.9.3)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(@types/json-schema@7.0.15)(svelte@5.53.8)(typescript@5.9.3) tailwind-merge: specifier: ^3.5.0 version: 3.5.0 @@ -220,8 +223,8 @@ importers: specifier: 1.0.0-next.7 version: 1.0.0-next.7(svelte@5.53.8) vite: - specifier: ^7.3.1 - version: 7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0) + specifier: ^7.3.2 + version: 7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0) vitest: specifier: ^3.2.4 version: 3.2.4(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0) @@ -1123,6 +1126,12 @@ packages: '@exodus/schemasafe@1.3.0': resolution: {integrity: sha512-5Aap/GaRupgNx/feGBwLLTVv8OQFfv3pq2lPRzPg9R+IOBnDgghTGW7l7EuVXOvg5cc/xSAlRW8rBrjIC3Nvqw==} + '@fast-csv/format@4.3.5': + resolution: {integrity: sha512-8iRn6QF3I8Ak78lNAa+Gdl5MJJBM5vRHivFtMRUWINdevNo00K7OXxS2PshawLKTejVwieIlPmK5YlLu6w4u8A==} + + '@fast-csv/parse@4.3.6': + resolution: {integrity: sha512-uRsLYksqpbDmWaSmzvJcuApSEe38+6NQZBUsuAyMZKqHxH0g1wcJgsKUvN3WC8tewaqFjBMMGrkHmC+T7k8LvA==} + '@fastify/otel@0.18.0': resolution: {integrity: sha512-3TASCATfw+ctICSb4ymrv7iCm0qJ0N9CarB+CZ7zIJ7KqNbwI5JjyDL1/sxoC0ccTO1Zyd1iQ+oqncPg5FJXaA==} peerDependencies: @@ -2512,6 +2521,9 @@ packages: '@types/mysql@2.15.27': resolution: {integrity: sha512-YfWiV16IY0OeBfBCk8+hXKmdTKrKlwKN1MNKAPBu5JYxLwBEZl7QzeEpGnlZb3VMGJrrGmB84gXiH+ofs/TezA==} + '@types/node@14.18.63': + resolution: {integrity: sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==} + '@types/node@24.12.0': resolution: {integrity: sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ==} @@ -2680,10 +2692,6 @@ packages: engines: {node: '>=0.4.0'} hasBin: true - adler-32@1.3.1: - resolution: {integrity: sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==} - engines: {node: '>=0.8'} - agent-base@6.0.2: resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} engines: {node: '>= 6.0.0'} @@ -2695,6 +2703,18 @@ packages: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} + archiver-utils@2.1.0: + resolution: {integrity: sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==} + engines: {node: '>= 6'} + + archiver-utils@3.0.4: + resolution: {integrity: sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==} + engines: {node: '>= 10'} + + archiver@5.3.2: + resolution: {integrity: sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==} + engines: {node: '>= 10'} + argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} @@ -2712,6 +2732,9 @@ packages: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} + async@3.2.6: + resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} + axe-core@4.11.2: resolution: {integrity: sha512-byD6KPdvo72y/wj2T/4zGEvvlis+PsZsn/yPS3pEO+sFpcrqRpX/TJCxvVaEsNeMrfQbCr7w163YqoD9IYwHXw==} engines: {node: '>=4'} @@ -2809,6 +2832,13 @@ packages: resolution: {integrity: sha512-8VYKM3MjCa9WcaSAI3hzwhmyHVlH8tiGFwf0RlTsZPWJ1I5MkzjiudCo4KC4DxOaL/53A5B1sI/IbldNFDbsKA==} engines: {node: 20.x || 22.x || 23.x || 24.x || 25.x} + big-integer@1.6.52: + resolution: {integrity: sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==} + engines: {node: '>=0.6'} + + binary@0.3.0: + resolution: {integrity: sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg==} + bindings@1.5.0: resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} @@ -2822,6 +2852,9 @@ packages: bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + bluebird@3.4.7: + resolution: {integrity: sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==} + boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} @@ -2831,6 +2864,9 @@ packages: brace-expansion@1.1.12: resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + brace-expansion@2.0.3: + resolution: {integrity: sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==} + brace-expansion@5.0.4: resolution: {integrity: sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==} engines: {node: 18 || 20 || >=22} @@ -2840,12 +2876,23 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true + buffer-crc32@0.2.13: + resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} + buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + buffer-indexof-polyfill@1.0.2: + resolution: {integrity: sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==} + engines: {node: '>=0.10'} + buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + buffers@0.1.1: + resolution: {integrity: sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==} + engines: {node: '>=0.2.0'} + bundle-name@4.1.0: resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} engines: {node: '>=18'} @@ -2873,14 +2920,13 @@ packages: caniuse-lite@1.0.30001778: resolution: {integrity: sha512-PN7uxFL+ExFJO61aVmP1aIEG4i9whQd4eoSCebav62UwDyp5OHh06zN4jqKSMePVgxHifCw1QJxdRkA1Pisekg==} - cfb@1.2.2: - resolution: {integrity: sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==} - engines: {node: '>=0.8'} - chai@5.3.3: resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} engines: {node: '>=18'} + chainsaw@0.1.0: + resolution: {integrity: sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==} + chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -2933,10 +2979,6 @@ packages: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} - codepage@1.15.0: - resolution: {integrity: sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==} - engines: {node: '>=0.8'} - color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -2962,6 +3004,10 @@ packages: commondir@1.0.1: resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} + compress-commons@4.1.2: + resolution: {integrity: sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==} + engines: {node: '>= 10'} + concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} @@ -2982,11 +3028,18 @@ packages: core-js@3.49.0: resolution: {integrity: sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg==} + core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + crc-32@1.2.2: resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==} engines: {node: '>=0.8'} hasBin: true + crc32-stream@4.0.3: + resolution: {integrity: sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==} + engines: {node: '>= 10'} + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -3202,8 +3255,8 @@ packages: resolution: {integrity: sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==} engines: {node: '>=12'} - drizzle-kit@0.31.9: - resolution: {integrity: sha512-GViD3IgsXn7trFyBUUHyTFBpH/FsHTxYJ66qdbVggxef4UBPHRYxQaRzYLTuekYnk9i5FIEL9pbBIwMqX/Uwrg==} + drizzle-kit@0.31.10: + resolution: {integrity: sha512-7OZcmQUrdGI+DUNNsKBn1aW8qSoKuTH7d0mYgSP8bAzdFzKoovxEFnoGQp2dVs82EOJeYycqRtciopszwUf8bw==} hasBin: true drizzle-orm@0.41.0: @@ -3295,8 +3348,8 @@ packages: sqlite3: optional: true - drizzle-orm@0.45.1: - resolution: {integrity: sha512-Te0FOdKIistGNPMq2jscdqngBRfBpC8uMFVwqjf6gtTVJHIQ/dosgV/CLBU2N4ZJBsXL5savCba9b0YJskKdcA==} + drizzle-orm@0.45.2: + resolution: {integrity: sha512-kY0BSaTNYWnoDMVoyY8uxmyHjpJW1geOmBMdSSicKo9CIIWkSxMIj2rkeSR51b8KAPB7m+qysjuHme5nKP+E5Q==} peerDependencies: '@aws-sdk/client-rds-data': '>=3' '@cloudflare/workers-types': '>=4' @@ -3387,6 +3440,9 @@ packages: sqlite3: optional: true + duplexer2@0.1.4: + resolution: {integrity: sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==} + earcut@3.0.2: resolution: {integrity: sha512-X7hshQbLyMJ/3RPhyObLARM2sNxxmRALLKx1+NVFFnQ9gKzmCrxm9+uLIAdBcvc8FNLpctqlQ2V6AE92Ol9UDQ==} @@ -3434,11 +3490,6 @@ packages: es-module-lexer@1.7.0: resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} - esbuild-register@3.6.0: - resolution: {integrity: sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==} - peerDependencies: - esbuild: '>=0.12 <1' - esbuild@0.18.20: resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==} engines: {node: '>=12'} @@ -3536,6 +3587,10 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + exceljs@4.4.0: + resolution: {integrity: sha512-XctvKaEMaj1Ii9oDOqbW/6e1gXknSY4g/aLCDicOXqBE4M0nRWkUu0PTp++UPNzoFY12BNHMfs/VadKIS6llvg==} + engines: {node: '>=8.3.0'} + expand-template@2.0.3: resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} engines: {node: '>=6'} @@ -3551,6 +3606,10 @@ packages: resolution: {integrity: sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==} engines: {node: '>=8.0.0'} + fast-csv@4.3.6: + resolution: {integrity: sha512-2RNSpuwwsJGP0frGsOmTb9oUF+VkFSM4SyLTDgwf2ciHWTarN0lQTC+F2f/t5J9QjW+c65VFIAAu85GsvMIusw==} + engines: {node: '>=10.0.0'} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -3575,7 +3634,7 @@ packages: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} peerDependencies: - picomatch: ^3 || ^4 + picomatch: ^4.0.4 peerDependenciesMeta: picomatch: optional: true @@ -3611,13 +3670,12 @@ packages: forwarded-parse@2.1.2: resolution: {integrity: sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==} - frac@1.1.2: - resolution: {integrity: sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==} - engines: {node: '>=0.8'} - fs-constants@1.0.0: resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + fsevents@2.3.2: resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -3628,6 +3686,11 @@ packages: engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] + fstream@1.0.12: + resolution: {integrity: sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==} + engines: {node: '>=0.6'} + deprecated: This package is no longer supported. + function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} @@ -3659,6 +3722,10 @@ packages: resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} engines: {node: 18 || 20 || >=22} + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + globals@14.0.0: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} @@ -3713,6 +3780,9 @@ packages: resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} engines: {node: '>= 4'} + immediate@3.0.6: + resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} + import-fresh@3.3.1: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} @@ -3728,6 +3798,10 @@ packages: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} @@ -3786,6 +3860,9 @@ packages: resolution: {integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==} engines: {node: '>=16'} + isarray@1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} @@ -3835,6 +3912,9 @@ packages: engines: {node: '>=6'} hasBin: true + jszip@3.10.1: + resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==} + kdbush@4.0.2: resolution: {integrity: sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA==} @@ -3861,6 +3941,10 @@ packages: peerDependencies: svelte: ^5.0.0 + lazystream@1.0.1: + resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==} + engines: {node: '>= 0.6.3'} + levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} @@ -3868,6 +3952,9 @@ packages: libphonenumber-js@1.12.39: resolution: {integrity: sha512-MW79m7HuOqBk8mwytiXYTMELJiBbV3Zl9Y39dCCn1yC8K+WGNSq1QGvzywbylp5vGShEztMScCWHX/XFOS0rXg==} + lie@3.3.0: + resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} + lightningcss-android-arm64@1.31.1: resolution: {integrity: sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg==} engines: {node: '>= 12.0.0'} @@ -3946,6 +4033,9 @@ packages: resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} engines: {node: '>=10'} + listenercount@1.0.1: + resolution: {integrity: sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ==} + locate-character@3.0.0: resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==} @@ -3956,9 +4046,49 @@ packages: lodash-es@4.17.23: resolution: {integrity: sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==} + lodash.defaults@4.2.0: + resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==} + + lodash.difference@4.5.0: + resolution: {integrity: sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==} + + lodash.escaperegexp@4.1.2: + resolution: {integrity: sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==} + + lodash.flatten@4.4.0: + resolution: {integrity: sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==} + + lodash.groupby@4.6.0: + resolution: {integrity: sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==} + + lodash.isboolean@3.0.3: + resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} + + lodash.isequal@4.5.0: + resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} + deprecated: This package is deprecated. Use require('node:util').isDeepStrictEqual instead. + + lodash.isfunction@3.0.9: + resolution: {integrity: sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==} + + lodash.isnil@4.0.0: + resolution: {integrity: sha512-up2Mzq3545mwVnMhTDMdfoG1OurpA/s5t88JmQX809eH3C8491iu2sfKhTfhQtKY78oPNhiaHJUpT/dUDAAtng==} + + lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + + lodash.isundefined@3.0.1: + resolution: {integrity: sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA==} + lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + lodash.union@4.6.0: + resolution: {integrity: sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==} + + lodash.uniq@4.5.0: + resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} + lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} @@ -4017,6 +4147,10 @@ packages: minimatch@3.1.5: resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} + minimatch@5.1.9: + resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==} + engines: {node: '>=10'} + minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} @@ -4027,6 +4161,10 @@ packages: mkdirp-classic@0.5.3: resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + mkdirp@0.5.6: + resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} + hasBin: true + mode-watcher@1.1.0: resolution: {integrity: sha512-mUT9RRGPDYenk59qJauN1rhsIMKBmWA3xMF+uRwE8MW/tjhaDSCCARqkSuDTq8vr4/2KcAxIGVjACxTjdk5C3g==} peerDependencies: @@ -4094,6 +4232,10 @@ packages: resolution: {integrity: sha512-k+jf6N8PfQJ0Fe8ZhJlgqU5qJU44Lpvp2yvidH3vp1lPnVQMgi4yEEMPXg5eJS1gFIJTVq1NHBk7Ia9ARdSBdQ==} engines: {node: '>=6.0.0'} + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + normalize-url@8.1.1: resolution: {integrity: sha512-JYc0DPlpGWB40kH5g07gGTrYuMqV653k3uBKY6uITPWds3M0ov3GaWGp9lbE3Bzngx8+XkfzgvASb9vk9JDFXQ==} engines: {node: '>=14.16'} @@ -4131,6 +4273,9 @@ packages: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} + pako@1.0.11: + resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} + paneforge@1.0.2: resolution: {integrity: sha512-KzmIXQH1wCfwZ4RsMohD/IUtEjVhteR+c+ulb/CHYJHX8SuDXoJmChtsc/Xs5Wl8NHS4L5Q7cxL8MG40gSU1bA==} peerDependencies: @@ -4160,6 +4305,10 @@ packages: resolution: {integrity: sha512-DwmPWeFn+tq7TiyJ2CxezCAirXjFxvaiD03npak3cRjlP9+OjTmSy1EpIrEbh+l6JgUundniloMLDQ/6VTdhLQ==} engines: {node: '>=14.0.0'} + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} @@ -4226,8 +4375,8 @@ packages: picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - picomatch@4.0.3: - resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} engines: {node: '>=12'} pkg-types@2.3.0: @@ -4384,6 +4533,9 @@ packages: engines: {node: '>=14'} hasBin: true + process-nextick-args@2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + progress@2.0.3: resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} engines: {node: '>=0.4.0'} @@ -4428,10 +4580,16 @@ packages: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true + readable-stream@2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + readable-stream@3.6.2: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} + readdir-glob@1.1.3: + resolution: {integrity: sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==} + readdirp@4.1.2: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} @@ -4462,6 +4620,11 @@ packages: engines: {node: '>= 0.4'} hasBin: true + rimraf@2.7.1: + resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + robust-predicates@2.0.4: resolution: {integrity: sha512-l4NwboJM74Ilm4VKfbAtFeGq7aEjWL+5kVFcmgFA2MrdnQWx9iE/tUGvxY5HyMI7o/WpSIUFLbC5fbeaHgSCYg==} @@ -4524,6 +4687,9 @@ packages: resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} engines: {node: '>=6'} + safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} @@ -4533,6 +4699,10 @@ packages: sanitize-html@2.17.2: resolution: {integrity: sha512-EnffJUl46VE9uvZ0XeWzObHLurClLlT12gsOk1cHyP2Ol1P0BnBnsXmShlBmWVJM+dKieQI68R0tsPY5m/B+Jg==} + saxes@5.0.1: + resolution: {integrity: sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==} + engines: {node: '>=10'} + semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true @@ -4548,6 +4718,9 @@ packages: set-cookie-parser@3.0.1: resolution: {integrity: sha512-n7Z7dXZhJbwuAHhNzkTti6Aw9QDDjZtm3JTpTGATIdNzdQz5GuFs22w90BcvF4INfnrL5xrX3oGsuqO5Dx3A1Q==} + setimmediate@1.0.5: + resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} + sharp@0.33.5: resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -4601,16 +4774,15 @@ packages: resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} engines: {node: '>= 10.x'} - ssf@0.11.2: - resolution: {integrity: sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==} - engines: {node: '>=0.8'} - stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} std-env@3.10.0: resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} @@ -4781,6 +4953,10 @@ packages: resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==} engines: {node: '>=14.0.0'} + tmp@0.2.5: + resolution: {integrity: sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==} + engines: {node: '>=14.14'} + toposort@2.0.2: resolution: {integrity: sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==} @@ -4791,6 +4967,9 @@ packages: tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + traverse@0.3.9: + resolution: {integrity: sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==} + ts-algebra@2.0.0: resolution: {integrity: sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==} @@ -4848,6 +5027,9 @@ packages: resolution: {integrity: sha512-H/nlJ/h0ggGC+uRL3ovD+G0i4bqhvsDOpbDv7At5eFLlj2b41L8QliGbnl2H7SnDiYhENphh1tQFJZf+MyfLsQ==} engines: {node: '>=20.18.1'} + unzipper@0.10.14: + resolution: {integrity: sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g==} + update-browserslist-db@1.2.3: resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} hasBin: true @@ -4860,6 +5042,10 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + uuid@8.3.2: + resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + hasBin: true + valibot@1.2.0: resolution: {integrity: sha512-mm1rxUsmOxzrwnX5arGS+U4T25RdvpPjPN4yR0u9pUBov9+zGVtO84tif1eY4r6zWxVxu3KzIyknJy3rxfRZZg==} peerDependencies: @@ -4883,8 +5069,8 @@ packages: engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true - vite@7.3.1: - resolution: {integrity: sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==} + vite@7.3.2: + resolution: {integrity: sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -4987,18 +5173,10 @@ packages: engines: {node: '>=8'} hasBin: true - wmf@1.0.2: - resolution: {integrity: sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==} - engines: {node: '>=0.8'} - word-wrap@1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} - word@0.3.0: - resolution: {integrity: sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==} - engines: {node: '>=0.8'} - wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} @@ -5006,10 +5184,8 @@ packages: resolution: {integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==} engines: {node: '>=18'} - xlsx@0.18.5: - resolution: {integrity: sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==} - engines: {node: '>=0.8'} - hasBin: true + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} xtend@4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} @@ -5040,6 +5216,10 @@ packages: zimmerframe@1.1.4: resolution: {integrity: sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==} + zip-stream@4.1.1: + resolution: {integrity: sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==} + engines: {node: '>= 10'} + zod-v3-to-json-schema@4.0.0: resolution: {integrity: sha512-KixLrhX/uPmRFnDgsZrzrk4x5SSJA+PmaE5adbfID9+3KPJcdxqRobaHU397EfWBqfQircrjKqvEqZ/mW5QH6w==} peerDependencies: @@ -5737,7 +5917,7 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 - '@better-auth/cli@1.4.21(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.8)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8)(typescript@5.9.3)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(better-call@1.1.8(zod@4.3.6))(drizzle-kit@0.31.9)(jose@6.2.1)(kysely@0.28.11)(nanostores@1.1.1)(postgres@3.4.8)(svelte@5.53.8)(vitest@3.2.4(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0))': + '@better-auth/cli@1.4.21(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.8)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8)(typescript@5.9.3)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(better-call@1.1.8(zod@4.3.6))(drizzle-kit@0.31.10)(jose@6.2.1)(kysely@0.28.11)(nanostores@1.1.1)(postgres@3.4.8)(svelte@5.53.8)(vitest@3.2.4(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0))': dependencies: '@babel/core': 7.29.0 '@babel/preset-react': 7.28.5(@babel/core@7.29.0) @@ -5749,7 +5929,7 @@ snapshots: '@mrleebo/prisma-ast': 0.13.1 '@prisma/client': 5.22.0 '@types/pg': 8.18.0 - better-auth: 1.4.21(@prisma/client@5.22.0)(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.8)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8)(typescript@5.9.3)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(better-sqlite3@12.6.2)(drizzle-kit@0.31.9)(drizzle-orm@0.41.0(@opentelemetry/api@1.9.1)(@prisma/client@5.22.0)(@types/pg@8.18.0)(better-sqlite3@12.6.2)(kysely@0.28.11)(pg@8.20.0)(postgres@3.4.8))(pg@8.20.0)(svelte@5.53.8)(vitest@3.2.4(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)) + better-auth: 1.4.21(@prisma/client@5.22.0)(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.8)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8)(typescript@5.9.3)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(better-sqlite3@12.6.2)(drizzle-kit@0.31.10)(drizzle-orm@0.41.0(@opentelemetry/api@1.9.1)(@prisma/client@5.22.0)(@types/pg@8.18.0)(better-sqlite3@12.6.2)(kysely@0.28.11)(pg@8.20.0)(postgres@3.4.8))(pg@8.20.0)(svelte@5.53.8)(vitest@3.2.4(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)) better-sqlite3: 12.6.2 c12: 3.3.3 chalk: 5.6.2 @@ -6160,6 +6340,25 @@ snapshots: '@exodus/schemasafe@1.3.0': optional: true + '@fast-csv/format@4.3.5': + dependencies: + '@types/node': 14.18.63 + lodash.escaperegexp: 4.1.2 + lodash.isboolean: 3.0.3 + lodash.isequal: 4.5.0 + lodash.isfunction: 3.0.9 + lodash.isnil: 4.0.0 + + '@fast-csv/parse@4.3.6': + dependencies: + '@types/node': 14.18.63 + lodash.escaperegexp: 4.1.2 + lodash.groupby: 4.6.0 + lodash.isfunction: 3.0.9 + lodash.isnil: 4.0.0 + lodash.isundefined: 3.0.1 + lodash.uniq: 4.5.0 + '@fastify/otel@0.18.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 @@ -6785,10 +6984,10 @@ snapshots: '@rollup/pluginutils': 5.3.0(rollup@4.59.0) commondir: 1.0.1 estree-walker: 2.0.2 - fdir: 6.5.0(picomatch@4.0.3) + fdir: 6.5.0(picomatch@4.0.4) is-reference: 1.2.1 magic-string: 0.30.21 - picomatch: 4.0.3 + picomatch: 4.0.4 optionalDependencies: rollup: 4.59.0 @@ -6812,7 +7011,7 @@ snapshots: dependencies: '@types/estree': 1.0.8 estree-walker: 2.0.2 - picomatch: 4.0.3 + picomatch: 4.0.4 optionalDependencies: rollup: 4.59.0 @@ -7083,7 +7282,7 @@ snapshots: magic-string: 0.30.21 svelte: 5.53.8 - '@sentry/sveltekit@10.47.0(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.8)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8)(typescript@5.9.3)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(rollup@4.59.0)(svelte@5.53.8)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0))': + '@sentry/sveltekit@10.47.0(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.8)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8)(typescript@5.9.3)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(rollup@4.59.0)(svelte@5.53.8)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0))': dependencies: '@sentry/cloudflare': 10.47.0 '@sentry/core': 10.47.0 @@ -7091,12 +7290,12 @@ snapshots: '@sentry/svelte': 10.47.0(svelte@5.53.8) '@sentry/vite-plugin': 5.1.1(rollup@4.59.0) '@sveltejs/acorn-typescript': 1.0.9(acorn@8.16.0) - '@sveltejs/kit': 2.53.4(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.8)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8)(typescript@5.9.3)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)) + '@sveltejs/kit': 2.53.4(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.8)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8)(typescript@5.9.3)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)) acorn: 8.16.0 magic-string: 0.30.21 sorcery: 1.0.0 optionalDependencies: - vite: 7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0) + vite: 7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0) transitivePeerDependencies: - '@cloudflare/workers-types' - '@opentelemetry/exporter-trace-otlp-http' @@ -7463,19 +7662,19 @@ snapshots: dependencies: acorn: 8.16.0 - '@sveltejs/adapter-node@5.5.4(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.8)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8)(typescript@5.9.3)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))': + '@sveltejs/adapter-node@5.5.4(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.8)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8)(typescript@5.9.3)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))': dependencies: '@rollup/plugin-commonjs': 29.0.2(rollup@4.59.0) '@rollup/plugin-json': 6.1.0(rollup@4.59.0) '@rollup/plugin-node-resolve': 16.0.3(rollup@4.59.0) - '@sveltejs/kit': 2.53.4(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.8)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8)(typescript@5.9.3)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)) + '@sveltejs/kit': 2.53.4(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.8)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8)(typescript@5.9.3)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)) rollup: 4.59.0 - '@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.8)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8)(typescript@5.9.3)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0))': + '@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.8)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8)(typescript@5.9.3)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0))': dependencies: '@standard-schema/spec': 1.1.0 '@sveltejs/acorn-typescript': 1.0.9(acorn@8.16.0) - '@sveltejs/vite-plugin-svelte': 6.2.4(svelte@5.53.8)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)) + '@sveltejs/vite-plugin-svelte': 6.2.4(svelte@5.53.8)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)) '@types/cookie': 0.6.0 acorn: 8.16.0 cookie: 0.6.0 @@ -7487,27 +7686,27 @@ snapshots: set-cookie-parser: 3.0.1 sirv: 3.0.2 svelte: 5.53.8 - vite: 7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0) + vite: 7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0) optionalDependencies: '@opentelemetry/api': 1.9.1 typescript: 5.9.3 - '@sveltejs/vite-plugin-svelte-inspector@5.0.2(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.8)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0))': + '@sveltejs/vite-plugin-svelte-inspector@5.0.2(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.8)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0))': dependencies: - '@sveltejs/vite-plugin-svelte': 6.2.4(svelte@5.53.8)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)) + '@sveltejs/vite-plugin-svelte': 6.2.4(svelte@5.53.8)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)) obug: 2.1.1 svelte: 5.53.8 - vite: 7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0) + vite: 7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0) - '@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.8)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0))': + '@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.8)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0))': dependencies: - '@sveltejs/vite-plugin-svelte-inspector': 5.0.2(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.8)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)) + '@sveltejs/vite-plugin-svelte-inspector': 5.0.2(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.8)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)) deepmerge: 4.3.1 magic-string: 0.30.21 obug: 2.1.1 svelte: 5.53.8 - vite: 7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0) - vitefu: 1.1.2(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)) + vite: 7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0) + vitefu: 1.1.2(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)) '@swc/helpers@0.5.19': dependencies: @@ -7584,12 +7783,12 @@ snapshots: postcss-selector-parser: 6.0.10 tailwindcss: 4.2.1 - '@tailwindcss/vite@4.2.1(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0))': + '@tailwindcss/vite@4.2.1(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0))': dependencies: '@tailwindcss/node': 4.2.1 '@tailwindcss/oxide': 4.2.1 tailwindcss: 4.2.1 - vite: 7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0) + vite: 7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0) '@tanstack/table-core@8.21.3': {} @@ -7620,6 +7819,8 @@ snapshots: dependencies: '@types/node': 24.12.0 + '@types/node@14.18.63': {} + '@types/node@24.12.0': dependencies: undici-types: 7.16.0 @@ -7798,13 +7999,13 @@ snapshots: chai: 5.3.3 tinyrainbow: 2.0.0 - '@vitest/mocker@3.2.4(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0))': + '@vitest/mocker@3.2.4(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0))': dependencies: '@vitest/spy': 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0) + vite: 7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0) '@vitest/pretty-format@3.2.4': dependencies: @@ -7842,8 +8043,6 @@ snapshots: acorn@8.16.0: {} - adler-32@1.3.1: {} - agent-base@6.0.2: dependencies: debug: 4.4.3 @@ -7861,6 +8060,42 @@ snapshots: dependencies: color-convert: 2.0.1 + archiver-utils@2.1.0: + dependencies: + glob: 7.2.3 + graceful-fs: 4.2.11 + lazystream: 1.0.1 + lodash.defaults: 4.2.0 + lodash.difference: 4.5.0 + lodash.flatten: 4.4.0 + lodash.isplainobject: 4.0.6 + lodash.union: 4.6.0 + normalize-path: 3.0.0 + readable-stream: 2.3.8 + + archiver-utils@3.0.4: + dependencies: + glob: 7.2.3 + graceful-fs: 4.2.11 + lazystream: 1.0.1 + lodash.defaults: 4.2.0 + lodash.difference: 4.5.0 + lodash.flatten: 4.4.0 + lodash.isplainobject: 4.0.6 + lodash.union: 4.6.0 + normalize-path: 3.0.0 + readable-stream: 3.6.2 + + archiver@5.3.2: + dependencies: + archiver-utils: 2.1.0 + async: 3.2.6 + buffer-crc32: 0.2.13 + readable-stream: 3.6.2 + readdir-glob: 1.1.3 + tar-stream: 2.2.0 + zip-stream: 4.1.1 + argparse@2.0.1: {} aria-query@5.3.1: {} @@ -7879,6 +8114,8 @@ snapshots: assertion-error@2.0.1: {} + async@3.2.6: {} + axe-core@4.11.2: {} axobject-query@4.1.0: {} @@ -7891,7 +8128,7 @@ snapshots: baseline-browser-mapping@2.10.0: {} - better-auth@1.4.21(@prisma/client@5.22.0)(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.8)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8)(typescript@5.9.3)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(better-sqlite3@12.6.2)(drizzle-kit@0.31.9)(drizzle-orm@0.41.0(@opentelemetry/api@1.9.1)(@prisma/client@5.22.0)(@types/pg@8.18.0)(better-sqlite3@12.6.2)(kysely@0.28.11)(pg@8.20.0)(postgres@3.4.8))(pg@8.20.0)(svelte@5.53.8)(vitest@3.2.4(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)): + better-auth@1.4.21(@prisma/client@5.22.0)(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.8)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8)(typescript@5.9.3)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(better-sqlite3@12.6.2)(drizzle-kit@0.31.10)(drizzle-orm@0.41.0(@opentelemetry/api@1.9.1)(@prisma/client@5.22.0)(@types/pg@8.18.0)(better-sqlite3@12.6.2)(kysely@0.28.11)(pg@8.20.0)(postgres@3.4.8))(pg@8.20.0)(svelte@5.53.8)(vitest@3.2.4(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)): dependencies: '@better-auth/core': 1.4.21(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.2.1)(kysely@0.28.11)(nanostores@1.1.1) '@better-auth/telemetry': 1.4.21(@better-auth/core@1.4.21(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.2.1)(kysely@0.28.11)(nanostores@1.1.1)) @@ -7907,15 +8144,15 @@ snapshots: zod: 4.3.6 optionalDependencies: '@prisma/client': 5.22.0 - '@sveltejs/kit': 2.53.4(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.8)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8)(typescript@5.9.3)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)) + '@sveltejs/kit': 2.53.4(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.8)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8)(typescript@5.9.3)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)) better-sqlite3: 12.6.2 - drizzle-kit: 0.31.9 + drizzle-kit: 0.31.10 drizzle-orm: 0.41.0(@opentelemetry/api@1.9.1)(@prisma/client@5.22.0)(@types/pg@8.18.0)(better-sqlite3@12.6.2)(kysely@0.28.11)(pg@8.20.0)(postgres@3.4.8) pg: 8.20.0 svelte: 5.53.8 vitest: 3.2.4(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0) - better-auth@1.4.21(@prisma/client@5.22.0)(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.8)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8)(typescript@5.9.3)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(better-sqlite3@12.6.2)(drizzle-kit@0.31.9)(drizzle-orm@0.45.1(@opentelemetry/api@1.9.1)(@prisma/client@5.22.0)(@types/pg@8.18.0)(better-sqlite3@12.6.2)(kysely@0.28.11)(pg@8.20.0)(postgres@3.4.8))(pg@8.20.0)(svelte@5.53.8)(vitest@3.2.4(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)): + better-auth@1.4.21(@prisma/client@5.22.0)(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.8)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8)(typescript@5.9.3)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(better-sqlite3@12.6.2)(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@opentelemetry/api@1.9.1)(@prisma/client@5.22.0)(@types/pg@8.18.0)(better-sqlite3@12.6.2)(kysely@0.28.11)(pg@8.20.0)(postgres@3.4.8))(pg@8.20.0)(svelte@5.53.8)(vitest@3.2.4(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)): dependencies: '@better-auth/core': 1.4.21(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.2.1)(kysely@0.28.11)(nanostores@1.1.1) '@better-auth/telemetry': 1.4.21(@better-auth/core@1.4.21(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.2.1)(kysely@0.28.11)(nanostores@1.1.1)) @@ -7931,10 +8168,10 @@ snapshots: zod: 4.3.6 optionalDependencies: '@prisma/client': 5.22.0 - '@sveltejs/kit': 2.53.4(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.8)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8)(typescript@5.9.3)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)) + '@sveltejs/kit': 2.53.4(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.8)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8)(typescript@5.9.3)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)) better-sqlite3: 12.6.2 - drizzle-kit: 0.31.9 - drizzle-orm: 0.45.1(@opentelemetry/api@1.9.1)(@prisma/client@5.22.0)(@types/pg@8.18.0)(better-sqlite3@12.6.2)(kysely@0.28.11)(pg@8.20.0)(postgres@3.4.8) + drizzle-kit: 0.31.10 + drizzle-orm: 0.45.2(@opentelemetry/api@1.9.1)(@prisma/client@5.22.0)(@types/pg@8.18.0)(better-sqlite3@12.6.2)(kysely@0.28.11)(pg@8.20.0)(postgres@3.4.8) pg: 8.20.0 svelte: 5.53.8 vitest: 3.2.4(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0) @@ -7953,19 +8190,26 @@ snapshots: bindings: 1.5.0 prebuild-install: 7.1.3 + big-integer@1.6.52: {} + + binary@0.3.0: + dependencies: + buffers: 0.1.1 + chainsaw: 0.1.0 + bindings@1.5.0: dependencies: file-uri-to-path: 1.0.0 - bits-ui@2.16.3(@internationalized/date@3.12.0)(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.8)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8)(typescript@5.9.3)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8): + bits-ui@2.16.3(@internationalized/date@3.12.0)(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.8)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8)(typescript@5.9.3)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8): dependencies: '@floating-ui/core': 1.7.5 '@floating-ui/dom': 1.7.6 '@internationalized/date': 3.12.0 esm-env: 1.2.2 - runed: 0.35.1(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.8)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8)(typescript@5.9.3)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8) + runed: 0.35.1(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.8)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8)(typescript@5.9.3)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8) svelte: 5.53.8 - svelte-toolbelt: 0.10.6(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.8)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8)(typescript@5.9.3)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8) + svelte-toolbelt: 0.10.6(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.8)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8)(typescript@5.9.3)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8) tabbable: 6.4.0 transitivePeerDependencies: - '@sveltejs/kit' @@ -7976,6 +8220,8 @@ snapshots: inherits: 2.0.4 readable-stream: 3.6.2 + bluebird@3.4.7: {} + boolbase@1.0.0: {} bowser@2.14.1: {} @@ -7985,6 +8231,10 @@ snapshots: balanced-match: 1.0.2 concat-map: 0.0.1 + brace-expansion@2.0.3: + dependencies: + balanced-match: 1.0.2 + brace-expansion@5.0.4: dependencies: balanced-match: 4.0.4 @@ -7997,13 +8247,19 @@ snapshots: node-releases: 2.0.36 update-browserslist-db: 1.2.3(browserslist@4.28.1) + buffer-crc32@0.2.13: {} + buffer-from@1.1.2: {} + buffer-indexof-polyfill@1.0.2: {} + buffer@5.7.1: dependencies: base64-js: 1.5.1 ieee754: 1.2.1 + buffers@0.1.1: {} + bundle-name@4.1.0: dependencies: run-applescript: 7.1.0 @@ -8032,11 +8288,6 @@ snapshots: caniuse-lite@1.0.30001778: {} - cfb@1.2.2: - dependencies: - adler-32: 1.3.1 - crc-32: 1.2.2 - chai@5.3.3: dependencies: assertion-error: 2.0.1 @@ -8045,6 +8296,10 @@ snapshots: loupe: 3.2.1 pathval: 2.0.1 + chainsaw@0.1.0: + dependencies: + traverse: 0.3.9 + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 @@ -8115,8 +8370,6 @@ snapshots: clsx@2.1.1: {} - codepage@1.15.0: {} - color-convert@2.0.1: dependencies: color-name: 1.1.4 @@ -8139,6 +8392,13 @@ snapshots: commondir@1.0.1: {} + compress-commons@4.1.2: + dependencies: + buffer-crc32: 0.2.13 + crc32-stream: 4.0.3 + normalize-path: 3.0.0 + readable-stream: 3.6.2 + concat-map@0.0.1: {} confbox@0.2.4: {} @@ -8151,8 +8411,15 @@ snapshots: core-js@3.49.0: {} + core-util-is@1.0.3: {} + crc-32@1.2.2: {} + crc32-stream@4.0.3: + dependencies: + crc-32: 1.2.2 + readable-stream: 3.6.2 + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -8275,8 +8542,7 @@ snapshots: d3-delaunay: 6.0.4 d3-scale: 4.0.2 - dayjs@1.11.19: - optional: true + dayjs@1.11.19: {} debug@4.4.3: dependencies: @@ -8346,14 +8612,12 @@ snapshots: dotenv@17.3.1: {} - drizzle-kit@0.31.9: + drizzle-kit@0.31.10: dependencies: '@drizzle-team/brocli': 0.10.2 '@esbuild-kit/esm-loader': 2.6.5 esbuild: 0.25.12 - esbuild-register: 3.6.0(esbuild@0.25.12) - transitivePeerDependencies: - - supports-color + tsx: 4.21.0 drizzle-orm@0.41.0(@opentelemetry/api@1.9.1)(@prisma/client@5.22.0)(@types/pg@8.18.0)(better-sqlite3@12.6.2)(kysely@0.28.11)(pg@8.20.0)(postgres@3.4.8): optionalDependencies: @@ -8365,7 +8629,7 @@ snapshots: pg: 8.20.0 postgres: 3.4.8 - drizzle-orm@0.45.1(@opentelemetry/api@1.9.1)(@prisma/client@5.22.0)(@types/pg@8.18.0)(better-sqlite3@12.6.2)(kysely@0.28.11)(pg@8.20.0)(postgres@3.4.8): + drizzle-orm@0.45.2(@opentelemetry/api@1.9.1)(@prisma/client@5.22.0)(@types/pg@8.18.0)(better-sqlite3@12.6.2)(kysely@0.28.11)(pg@8.20.0)(postgres@3.4.8): optionalDependencies: '@opentelemetry/api': 1.9.1 '@prisma/client': 5.22.0 @@ -8375,6 +8639,10 @@ snapshots: pg: 8.20.0 postgres: 3.4.8 + duplexer2@0.1.4: + dependencies: + readable-stream: 2.3.8 + earcut@3.0.2: {} effect@3.19.19: @@ -8419,13 +8687,6 @@ snapshots: es-module-lexer@1.7.0: {} - esbuild-register@3.6.0(esbuild@0.25.12): - dependencies: - debug: 4.4.3 - esbuild: 0.25.12 - transitivePeerDependencies: - - supports-color - esbuild@0.18.20: optionalDependencies: '@esbuild/android-arm': 0.18.20 @@ -8617,6 +8878,18 @@ snapshots: esutils@2.0.3: {} + exceljs@4.4.0: + dependencies: + archiver: 5.3.2 + dayjs: 1.11.19 + fast-csv: 4.3.6 + jszip: 3.10.1 + readable-stream: 3.6.2 + saxes: 5.0.1 + tmp: 0.2.5 + unzipper: 0.10.14 + uuid: 8.3.2 + expand-template@2.0.3: {} expect-type@1.3.0: {} @@ -8628,6 +8901,11 @@ snapshots: pure-rand: 6.1.0 optional: true + fast-csv@4.3.6: + dependencies: + '@fast-csv/format': 4.3.5 + '@fast-csv/parse': 4.3.6 + fast-deep-equal@3.1.3: {} fast-json-stable-stringify@2.1.0: {} @@ -8650,9 +8928,9 @@ snapshots: path-expression-matcher: 1.2.0 strnum: 2.2.2 - fdir@6.5.0(picomatch@4.0.3): + fdir@6.5.0(picomatch@4.0.4): optionalDependencies: - picomatch: 4.0.3 + picomatch: 4.0.4 fflate@0.4.8: {} @@ -8674,24 +8952,31 @@ snapshots: flatted@3.4.1: {} - formsnap@2.0.1(svelte@5.53.8)(sveltekit-superforms@2.30.0(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.8)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8)(typescript@5.9.3)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(@types/json-schema@7.0.15)(svelte@5.53.8)(typescript@5.9.3)): + formsnap@2.0.1(svelte@5.53.8)(sveltekit-superforms@2.30.0(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.8)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8)(typescript@5.9.3)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(@types/json-schema@7.0.15)(svelte@5.53.8)(typescript@5.9.3)): dependencies: svelte: 5.53.8 svelte-toolbelt: 0.5.0(svelte@5.53.8) - sveltekit-superforms: 2.30.0(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.8)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8)(typescript@5.9.3)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(@types/json-schema@7.0.15)(svelte@5.53.8)(typescript@5.9.3) + sveltekit-superforms: 2.30.0(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.8)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8)(typescript@5.9.3)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(@types/json-schema@7.0.15)(svelte@5.53.8)(typescript@5.9.3) forwarded-parse@2.1.2: {} - frac@1.1.2: {} - fs-constants@1.0.0: {} + fs.realpath@1.0.0: {} + fsevents@2.3.2: optional: true fsevents@2.3.3: optional: true + fstream@1.0.12: + dependencies: + graceful-fs: 4.2.11 + inherits: 2.0.4 + mkdirp: 0.5.6 + rimraf: 2.7.1 + function-bind@1.1.2: {} gensync@1.0.0-beta.2: {} @@ -8725,6 +9010,15 @@ snapshots: minipass: 7.1.3 path-scurry: 2.0.2 + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.5 + once: 1.4.0 + path-is-absolute: 1.0.1 + globals@14.0.0: {} globals@16.5.0: {} @@ -8769,6 +9063,8 @@ snapshots: ignore@7.0.5: {} + immediate@3.0.6: {} + import-fresh@3.3.1: dependencies: parent-module: 1.0.1 @@ -8790,6 +9086,11 @@ snapshots: imurmurhash@0.1.4: {} + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + inherits@2.0.4: {} ini@1.3.8: {} @@ -8834,6 +9135,8 @@ snapshots: dependencies: is-inside-container: 1.0.0 + isarray@1.0.0: {} + isexe@2.0.0: {} jiti@2.6.1: {} @@ -8875,6 +9178,13 @@ snapshots: json5@2.2.3: {} + jszip@3.10.1: + dependencies: + lie: 3.3.0 + pako: 1.0.11 + readable-stream: 2.3.8 + setimmediate: 1.0.5 + kdbush@4.0.2: {} keyv@4.5.4: @@ -8920,6 +9230,10 @@ snapshots: runed: 0.31.1(svelte@5.53.8) svelte: 5.53.8 + lazystream@1.0.1: + dependencies: + readable-stream: 2.3.8 + levn@0.4.1: dependencies: prelude-ls: 1.2.1 @@ -8928,6 +9242,10 @@ snapshots: libphonenumber-js@1.12.39: optional: true + lie@3.3.0: + dependencies: + immediate: 3.0.6 + lightningcss-android-arm64@1.31.1: optional: true @@ -8979,6 +9297,8 @@ snapshots: lilconfig@2.1.0: {} + listenercount@1.0.1: {} + locate-character@3.0.0: {} locate-path@6.0.0: @@ -8987,8 +9307,34 @@ snapshots: lodash-es@4.17.23: {} + lodash.defaults@4.2.0: {} + + lodash.difference@4.5.0: {} + + lodash.escaperegexp@4.1.2: {} + + lodash.flatten@4.4.0: {} + + lodash.groupby@4.6.0: {} + + lodash.isboolean@3.0.3: {} + + lodash.isequal@4.5.0: {} + + lodash.isfunction@3.0.9: {} + + lodash.isnil@4.0.0: {} + + lodash.isplainobject@4.0.6: {} + + lodash.isundefined@3.0.1: {} + lodash.merge@4.6.2: {} + lodash.union@4.6.0: {} + + lodash.uniq@4.5.0: {} + lodash@4.17.21: {} long@5.3.2: {} @@ -9062,12 +9408,20 @@ snapshots: dependencies: brace-expansion: 1.1.12 + minimatch@5.1.9: + dependencies: + brace-expansion: 2.0.3 + minimist@1.2.8: {} minipass@7.1.3: {} mkdirp-classic@0.5.3: {} + mkdirp@0.5.6: + dependencies: + minimist: 1.2.8 + mode-watcher@1.1.0(svelte@5.53.8): dependencies: runed: 0.25.0(svelte@5.53.8) @@ -9114,6 +9468,8 @@ snapshots: nodemailer@8.0.4: {} + normalize-path@3.0.0: {} + normalize-url@8.1.1: optional: true @@ -9159,6 +9515,8 @@ snapshots: dependencies: p-limit: 3.1.0 + pako@1.0.11: {} + paneforge@1.0.2(svelte@5.53.8): dependencies: runed: 0.23.4(svelte@5.53.8) @@ -9188,6 +9546,8 @@ snapshots: path-expression-matcher@1.2.0: {} + path-is-absolute@1.0.1: {} + path-key@3.1.1: {} path-parse@1.0.7: {} @@ -9249,7 +9609,7 @@ snapshots: picocolors@1.1.1: {} - picomatch@4.0.3: {} + picomatch@4.0.4: {} pkg-types@2.3.0: dependencies: @@ -9358,6 +9718,8 @@ snapshots: prettier@3.8.1: {} + process-nextick-args@2.0.1: {} + progress@2.0.3: {} prompts@2.4.2: @@ -9413,12 +9775,26 @@ snapshots: minimist: 1.2.8 strip-json-comments: 2.0.1 + readable-stream@2.3.8: + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + readable-stream@3.6.2: dependencies: inherits: 2.0.4 string_decoder: 1.3.0 util-deprecate: 1.0.2 + readdir-glob@1.1.3: + dependencies: + minimatch: 5.1.9 + readdirp@4.1.2: {} readdirp@5.0.0: {} @@ -9446,6 +9822,10 @@ snapshots: path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 + rimraf@2.7.1: + dependencies: + glob: 7.2.3 + robust-predicates@2.0.4: {} robust-predicates@3.0.2: {} @@ -9514,14 +9894,14 @@ snapshots: esm-env: 1.2.2 svelte: 5.53.8 - runed@0.35.1(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.8)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8)(typescript@5.9.3)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8): + runed@0.35.1(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.8)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8)(typescript@5.9.3)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8): dependencies: dequal: 2.0.3 esm-env: 1.2.2 lz-string: 1.5.0 svelte: 5.53.8 optionalDependencies: - '@sveltejs/kit': 2.53.4(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.8)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8)(typescript@5.9.3)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)) + '@sveltejs/kit': 2.53.4(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.8)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8)(typescript@5.9.3)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)) rw@1.3.3: {} @@ -9529,6 +9909,8 @@ snapshots: dependencies: mri: 1.2.0 + safe-buffer@5.1.2: {} + safe-buffer@5.2.1: {} safer-buffer@2.1.2: {} @@ -9542,6 +9924,10 @@ snapshots: parse-srcset: 1.0.2 postcss: 8.5.8 + saxes@5.0.1: + dependencies: + xmlchars: 2.2.0 + semver@6.3.1: {} semver@7.7.4: {} @@ -9550,6 +9936,8 @@ snapshots: set-cookie-parser@3.0.1: {} + setimmediate@1.0.5: {} + sharp@0.33.5: dependencies: color: 4.2.3 @@ -9623,14 +10011,14 @@ snapshots: split2@4.2.0: {} - ssf@0.11.2: - dependencies: - frac: 1.1.2 - stackback@0.0.2: {} std-env@3.10.0: {} + string_decoder@1.1.1: + dependencies: + safe-buffer: 5.1.2 + string_decoder@1.3.0: dependencies: safe-buffer: 5.2.1 @@ -9662,11 +10050,11 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - svelte-check@4.4.5(picomatch@4.0.3)(svelte@5.53.8)(typescript@5.9.3): + svelte-check@4.4.5(picomatch@4.0.4)(svelte@5.53.8)(typescript@5.9.3): dependencies: '@jridgewell/trace-mapping': 0.3.31 chokidar: 4.0.3 - fdir: 6.5.0(picomatch@4.0.3) + fdir: 6.5.0(picomatch@4.0.4) picocolors: 1.1.1 sade: 1.8.1 svelte: 5.53.8 @@ -9695,10 +10083,10 @@ snapshots: runed: 0.28.0(svelte@5.53.8) svelte: 5.53.8 - svelte-toolbelt@0.10.6(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.8)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8)(typescript@5.9.3)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8): + svelte-toolbelt@0.10.6(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.8)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8)(typescript@5.9.3)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8): dependencies: clsx: 2.1.1 - runed: 0.35.1(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.8)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8)(typescript@5.9.3)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8) + runed: 0.35.1(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.8)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8)(typescript@5.9.3)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8) style-to-object: 1.0.14 svelte: 5.53.8 transitivePeerDependencies: @@ -9743,9 +10131,9 @@ snapshots: magic-string: 0.30.21 zimmerframe: 1.1.4 - sveltekit-superforms@2.30.0(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.8)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8)(typescript@5.9.3)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(@types/json-schema@7.0.15)(svelte@5.53.8)(typescript@5.9.3): + sveltekit-superforms@2.30.0(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.8)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8)(typescript@5.9.3)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(@types/json-schema@7.0.15)(svelte@5.53.8)(typescript@5.9.3): dependencies: - '@sveltejs/kit': 2.53.4(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.8)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8)(typescript@5.9.3)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)) + '@sveltejs/kit': 2.53.4(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.8)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.8)(typescript@5.9.3)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)) devalue: 5.6.3 memoize-weak: 1.0.2 svelte: 5.53.8 @@ -9822,8 +10210,8 @@ snapshots: tinyglobby@0.2.15: dependencies: - fdir: 6.5.0(picomatch@4.0.3) - picomatch: 4.0.3 + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 tinymce@8.3.2: {} @@ -9835,6 +10223,8 @@ snapshots: tinyspy@4.0.4: {} + tmp@0.2.5: {} + toposort@2.0.2: optional: true @@ -9842,6 +10232,8 @@ snapshots: tr46@0.0.3: {} + traverse@0.3.9: {} + ts-algebra@2.0.0: optional: true @@ -9893,6 +10285,19 @@ snapshots: undici@7.24.7: {} + unzipper@0.10.14: + dependencies: + big-integer: 1.6.52 + binary: 0.3.0 + bluebird: 3.4.7 + buffer-indexof-polyfill: 1.0.2 + duplexer2: 0.1.4 + fstream: 1.0.12 + graceful-fs: 4.2.11 + listenercount: 1.0.1 + readable-stream: 2.3.8 + setimmediate: 1.0.5 + update-browserslist-db@1.2.3(browserslist@4.28.1): dependencies: browserslist: 4.28.1 @@ -9905,6 +10310,8 @@ snapshots: util-deprecate@1.0.2: {} + uuid@8.3.2: {} + valibot@1.2.0(typescript@5.9.3): optionalDependencies: typescript: 5.9.3 @@ -9925,7 +10332,7 @@ snapshots: debug: 4.4.3 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0) + vite: 7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0) transitivePeerDependencies: - '@types/node' - jiti @@ -9940,11 +10347,11 @@ snapshots: - tsx - yaml - vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0): + vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0): dependencies: esbuild: 0.27.3 - fdir: 6.5.0(picomatch@4.0.3) - picomatch: 4.0.3 + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 postcss: 8.5.8 rollup: 4.59.0 tinyglobby: 0.2.15 @@ -9955,15 +10362,15 @@ snapshots: lightningcss: 1.31.1 tsx: 4.21.0 - vitefu@1.1.2(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)): + vitefu@1.1.2(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)): optionalDependencies: - vite: 7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0) + vite: 7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0) vitest@3.2.4(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0): dependencies: '@types/chai': 5.2.3 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)) + '@vitest/mocker': 3.2.4(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 @@ -9974,14 +10381,14 @@ snapshots: expect-type: 1.3.0 magic-string: 0.30.21 pathe: 2.0.3 - picomatch: 4.0.3 + picomatch: 4.0.4 std-env: 3.10.0 tinybench: 2.9.0 tinyexec: 0.3.2 tinyglobby: 0.2.15 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0) + vite: 7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0) vite-node: 3.2.4(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0) why-is-node-running: 2.3.0 optionalDependencies: @@ -10024,27 +10431,15 @@ snapshots: siginfo: 2.0.0 stackback: 0.0.2 - wmf@1.0.2: {} - word-wrap@1.2.5: {} - word@0.3.0: {} - wrappy@1.0.2: {} wsl-utils@0.1.0: dependencies: is-wsl: 3.1.1 - xlsx@0.18.5: - dependencies: - adler-32: 1.3.1 - cfb: 1.2.2 - codepage: 1.15.0 - crc-32: 1.2.2 - ssf: 0.11.2 - wmf: 1.0.2 - word: 0.3.0 + xmlchars@2.2.0: {} xtend@4.0.2: {} @@ -10070,6 +10465,12 @@ snapshots: zimmerframe@1.1.4: {} + zip-stream@4.1.1: + dependencies: + archiver-utils: 3.0.4 + compress-commons: 4.1.2 + readable-stream: 3.6.2 + zod-v3-to-json-schema@4.0.0(zod@4.3.6): dependencies: zod: 4.3.6 diff --git a/scripts/audit-toolbox-content.ts b/scripts/audit-toolbox-content.ts index 2f9cf93..e8c4711 100644 --- a/scripts/audit-toolbox-content.ts +++ b/scripts/audit-toolbox-content.ts @@ -68,7 +68,13 @@ type Change = { notes: string[]; }; -const REQUIRED_ENV_VARS = ['DATABASE_URL', 'MINIO_ENDPOINT', 'MINIO_ACCESS_KEY', 'MINIO_SECRET_KEY', 'MINIO_BUCKET']; +const REQUIRED_ENV_VARS = [ + 'DATABASE_URL', + 'MINIO_ENDPOINT', + 'MINIO_ACCESS_KEY', + 'MINIO_SECRET_KEY', + 'MINIO_BUCKET' +]; const DIRECT_PDF_PATTERNS = [/\.pdf(?:$|[?#])/i, /\/printable\/pdf(?:$|[?#])/i]; @@ -97,10 +103,9 @@ const REPLACEMENTS: Record = { 'native-language-preservation-toolkit': { externalUrl: 'https://fpcc.ca/stories/fpcc-language-resources/' }, - 'rebuilding-native-nations-strategies-for-governance-and-development-native-nations-institute': - { - externalUrl: 'https://nni.arizona.edu/our-work/digital-resources' - }, + 'rebuilding-native-nations-strategies-for-governance-and-development-native-nations-institute': { + externalUrl: 'https://nni.arizona.edu/our-work/digital-resources' + }, 'self-governance-compacting-a-guide-for-tribes-self-governance-communication-and-education-tribal-con': { externalUrl: 'https://www.tribalselfgov.org/about/sgcetc/' @@ -266,7 +271,7 @@ async function auditResource( const candidateExternalUrl = replacement ? Object.prototype.hasOwnProperty.call(replacement, 'externalUrl') - ? replacement.externalUrl ?? null + ? (replacement.externalUrl ?? null) : row.external_url : row.external_url; const resolvedExternalUrl = replacement?.dropExternal ? null : candidateExternalUrl; @@ -304,7 +309,12 @@ async function auditResource( ); } - if (!next.imageUrl && resolvedExternalUrl && audit?.ok && audit.contentType?.includes('text/html')) { + if ( + !next.imageUrl && + resolvedExternalUrl && + audit?.ok && + audit.contentType?.includes('text/html') + ) { try { const metadata = await extractPageMetadata(resolvedExternalUrl); if (metadata.imageUrl) { @@ -397,7 +407,11 @@ async function resolvePdfSource( } function normalizeLoosePath(value: string) { - return value.replace(/\u00a0/g, ' ').replace(/\s+/g, ' ').trim().toLowerCase(); + return value + .replace(/\u00a0/g, ' ') + .replace(/\s+/g, ' ') + .trim() + .toLowerCase(); } async function resolveExistingResourcePath(baseDir: string, relativePath: string): Promise { @@ -406,11 +420,16 @@ async function resolveExistingResourcePath(baseDir: string, relativePath: string const target = normalizeLoosePath(relativePath); const candidates = await walkFiles(baseDir); - const match = candidates.find((candidate) => normalizeLoosePath(candidate.relativePath) === target); + const match = candidates.find( + (candidate) => normalizeLoosePath(candidate.relativePath) === target + ); return match?.absolutePath ?? directPath; } -async function walkFiles(baseDir: string, currentDir = baseDir): Promise> { +async function walkFiles( + baseDir: string, + currentDir = baseDir +): Promise> { const entries = await readdir(currentDir, { withFileTypes: true }); const out: Array<{ absolutePath: string; relativePath: string }> = []; @@ -496,17 +515,11 @@ async function downloadText(url: string, options?: { insecureTls?: boolean }) { async function downloadBuffer(url: string, options?: { insecureTls?: boolean }) { if (options?.insecureTls) { - const { stdout } = await execFile('curl', [ - '-k', - '-L', - '--silent', - '--show-error', - '--max-time', - '30', - '-A', - 'Mozilla/5.0', - url - ], { encoding: 'buffer', maxBuffer: 1024 * 1024 * 30 }); + const { stdout } = await execFile( + 'curl', + ['-k', '-L', '--silent', '--show-error', '--max-time', '30', '-A', 'Mozilla/5.0', url], + { encoding: 'buffer', maxBuffer: 1024 * 1024 * 30 } + ); return { bytes: stdout as Buffer, contentType: guessContentTypeFromExtension(url) diff --git a/scripts/check-migrations-ci.mjs b/scripts/check-migrations-ci.mjs new file mode 100644 index 0000000..02dc761 --- /dev/null +++ b/scripts/check-migrations-ci.mjs @@ -0,0 +1,85 @@ +#!/usr/bin/env node +import { execFileSync } from 'node:child_process'; +import { existsSync } from 'node:fs'; +import path from 'node:path'; + +const repoRoot = process.cwd(); + +const schemaPatterns = [ + 'src/lib/server/db/schema/', + 'src/lib/server/db/auth.schema.ts', + 'drizzle.config.ts' +]; + +const migrationPatterns = ['drizzle/', 'drizzle/meta/']; + +function runGit(args, { allowFailure = false } = {}) { + try { + return execFileSync('git', args, { + cwd: repoRoot, + encoding: 'utf8', + stdio: ['ignore', 'pipe', 'pipe'] + }).trim(); + } catch (error) { + if (allowFailure) return ''; + throw error; + } +} + +function getChangedFiles() { + const baseRef = process.env.CI_GIT_BASE_REF?.trim(); + if (baseRef) { + const diff = runGit(['diff', '--name-only', `origin/${baseRef}...HEAD`], { + allowFailure: true + }); + if (diff) return diff.split('\n').filter(Boolean); + } + + const baseSha = process.env.CI_GIT_BASE_SHA?.trim(); + if (baseSha && !/^0+$/.test(baseSha)) { + const diff = runGit(['diff', '--name-only', `${baseSha}...HEAD`], { allowFailure: true }); + if (diff) return diff.split('\n').filter(Boolean); + } + + const previousCommitDiff = runGit(['diff', '--name-only', 'HEAD^...HEAD'], { + allowFailure: true + }); + if (previousCommitDiff) return previousCommitDiff.split('\n').filter(Boolean); + + return runGit(['diff', '--name-only', '--cached'], { allowFailure: true }) + .split('\n') + .filter(Boolean); +} + +const changedFiles = [...new Set(getChangedFiles())] + .filter(Boolean) + .filter((file) => existsSync(path.join(repoRoot, file))); + +if (changedFiles.length === 0) { + console.log('[migrations-ci] No changed files detected; skipping.'); + process.exit(0); +} + +const schemaChanged = changedFiles.some((file) => + schemaPatterns.some((pattern) => file === pattern || file.startsWith(pattern)) +); + +if (!schemaChanged) { + console.log('[migrations-ci] No schema-affecting files changed.'); + process.exit(0); +} + +const migrationChanged = changedFiles.some((file) => + migrationPatterns.some((pattern) => file === pattern || file.startsWith(pattern)) +); + +if (migrationChanged) { + console.log('[migrations-ci] Schema changes include committed migration artifacts.'); + process.exit(0); +} + +console.error( + '[migrations-ci] Schema-affecting changes were detected without a committed migration.' +); +console.error('[migrations-ci] Update drizzle migrations before merging this change.'); +process.exit(1); diff --git a/scripts/check-secrets.ts b/scripts/check-secrets.ts new file mode 100644 index 0000000..966dc67 --- /dev/null +++ b/scripts/check-secrets.ts @@ -0,0 +1,79 @@ +import { readFileSync } from 'node:fs'; +import process from 'node:process'; +import { config as loadDotenv } from 'dotenv'; +import { inspectRuntimeConfig } from '../src/lib/config/runtime-config-core'; +import { detectDeploymentEnvironment } from '../src/lib/config/runtime-secrets'; + +function parseArgs(argv: string[]) { + const args = new Map(); + + for (const arg of argv) { + if (!arg.startsWith('--')) continue; + + const [key, value] = arg.slice(2).split('='); + if (key) args.set(key, value ?? 'true'); + } + + return { + environment: args.get('environment'), + envFile: args.get('env-file') + }; +} + +function loadEnvironment(envFile: string | undefined) { + if (envFile) { + loadDotenv({ path: envFile, override: false }); + return; + } + + loadDotenv({ override: false }); +} + +function printIssueGroup(label: string, issues: { key: string; message: string }[]) { + if (issues.length === 0) return; + + console.log(`\n${label}:`); + for (const issue of issues) { + console.log(`- ${issue.key}: ${issue.message}`); + } +} + +const { environment, envFile } = parseArgs(process.argv.slice(2)); +loadEnvironment(envFile); + +const mergedValues = { + ...process.env, + ...(environment ? { KB_ENVIRONMENT: environment } : {}) +}; + +const detectedEnvironment = detectDeploymentEnvironment(mergedValues); +const health = inspectRuntimeConfig(mergedValues, { + enforceProduction: detectedEnvironment === 'staging' || detectedEnvironment === 'production', + readFile: (path) => readFileSync(path, 'utf8') +}); + +console.log(`Secrets check for ${health.environment}`); + +if (envFile) { + console.log(`Loaded dotenv file: ${envFile}`); +} + +const required = health.inventory.filter((entry) => entry.required); +const configured = required.filter((entry) => entry.configured); + +console.log(`Required entries configured: ${configured.length}/${required.length}`); + +console.log('\nResolved sources:'); +for (const entry of required) { + console.log( + `- ${entry.key}: ${entry.configured ? 'configured' : 'missing'} via ${entry.source} (${entry.category}, ${entry.classification})` + ); +} + +printIssueGroup('Missing', health.missing); +printIssueGroup('Invalid', health.invalid); +printIssueGroup('Warnings', health.warnings); + +if (!health.ok) { + process.exitCode = 1; +} diff --git a/scripts/host-verified-jobs-funding-logos.mjs b/scripts/host-verified-jobs-funding-logos.mjs index eb23bb2..23e8010 100644 --- a/scripts/host-verified-jobs-funding-logos.mjs +++ b/scripts/host-verified-jobs-funding-logos.mjs @@ -59,7 +59,8 @@ const logoMappings = [ }, { objectKey: 'logos/jobs/blue-lake-rancheria.png', - remoteUrl: 'https://www.bluelakerancheria-nsn.gov/wp-content/uploads/2024/08/BLR_Tribal-Seal.png', + remoteUrl: + 'https://www.bluelakerancheria-nsn.gov/wp-content/uploads/2024/08/BLR_Tribal-Seal.png', target: { table: 'jobs', column: 'employer_name', value: 'Blue Lake Rancheria' } }, { @@ -79,12 +80,14 @@ const logoMappings = [ }, { objectKey: 'logos/jobs/news-from-native-california.png', - remoteUrl: 'https://newsfromnativecalifornia.com/wp-content/uploads/2020/07/NNC_Logo_white_80.png', + remoteUrl: + 'https://newsfromnativecalifornia.com/wp-content/uploads/2020/07/NNC_Logo_white_80.png', target: { table: 'jobs', column: 'employer_name', value: 'News From Native California' } }, { objectKey: 'logos/jobs/karuk-tribe.png', - remoteUrl: 'https://cdn.prod.website-files.com/6840684a84781283cd1b68e3/687012149e5340bcb616ab51_Karuk%20Seal.png', + remoteUrl: + 'https://cdn.prod.website-files.com/6840684a84781283cd1b68e3/687012149e5340bcb616ab51_Karuk%20Seal.png', target: { table: 'jobs', column: 'employer_name', value: 'Karuk Tribe' } }, { @@ -214,7 +217,10 @@ async function uploadRemoteAsset(objectKey, remoteUrl) { throw new Error(`Failed to fetch ${remoteUrl}: ${response.status} ${response.statusText}`); } - const contentType = inferContentType(response.url || remoteUrl, response.headers.get('content-type')); + const contentType = inferContentType( + response.url || remoteUrl, + response.headers.get('content-type') + ); const body = Buffer.from(await response.arrayBuffer()); await s3.send( @@ -303,7 +309,9 @@ async function main() { imageUrl, updated }); - console.log(`Uploaded ${mapping.objectKey} and updated ${updated} ${mapping.target.table} row(s) for ${mapping.target.value}`); + console.log( + `Uploaded ${mapping.objectKey} and updated ${updated} ${mapping.target.table} row(s) for ${mapping.target.value}` + ); } const counts = await verifyTargetCounts(); diff --git a/scripts/lib/curated-funding-sync.mjs b/scripts/lib/curated-funding-sync.mjs index d9dce8e..ae32eb3 100644 --- a/scripts/lib/curated-funding-sync.mjs +++ b/scripts/lib/curated-funding-sync.mjs @@ -109,7 +109,8 @@ function stripHtml(value) { function buildFundingSearchDoc(row) { const summary = stripHtml(row.description).slice(0, 280) || undefined; - const publishedAt = toIsoString(row.published_at) ?? toIsoString(row.updated_at) ?? new Date(0).toISOString(); + const publishedAt = + toIsoString(row.published_at) ?? toIsoString(row.updated_at) ?? new Date(0).toISOString(); const updatedAt = toIsoString(row.updated_at) ?? publishedAt; return { id: row.id, @@ -125,8 +126,7 @@ function buildFundingSearchDoc(row) { updatedAt, publishedAt, sortDate: toIsoString(row.deadline) ?? publishedAt, - openRank: - row.application_status === 'open' || row.application_status === 'rolling' ? 1 : 0, + openRank: row.application_status === 'open' || row.application_status === 'rolling' ? 1 : 0, featuredRank: row.featured ? 1 : 0, imageUrl: row.image_url ?? undefined, region: row.region ?? undefined, diff --git a/scripts/push-local-data-to-railway.mjs b/scripts/push-local-data-to-railway.mjs index 821cd08..ce017c2 100644 --- a/scripts/push-local-data-to-railway.mjs +++ b/scripts/push-local-data-to-railway.mjs @@ -90,6 +90,7 @@ function parseArgs(argv) { case '--help': printUsage(); process.exit(0); + break; case '--dry-run': options.dryRun = true; break; diff --git a/scripts/seed-coils.js b/scripts/seed-coils.js index c1c2096..79e0a21 100644 --- a/scripts/seed-coils.js +++ b/scripts/seed-coils.js @@ -5,8 +5,6 @@ */ import 'dotenv/config'; import postgres from 'postgres'; -import { readFileSync, existsSync } from 'fs'; -import { join } from 'path'; import { randomUUID } from 'crypto'; import { syncCuratedFunding } from './lib/curated-funding-sync.mjs'; import { syncCuratedRedPages } from './lib/curated-red-pages-sync.mjs'; @@ -40,38 +38,6 @@ function uniqueSlug(base, seen) { return slug; } -function parseCSV(content) { - const rows = []; - let row = []; - let field = ''; - let inQuotes = false; - for (let i = 0; i < content.length; i++) { - const c = content[i]; - if (inQuotes) { - if (c === '"' && content[i + 1] === '"') { - field += '"'; - i++; - } else if (c === '"') inQuotes = false; - else field += c; - } else { - if (c === '"') inQuotes = true; - else if (c === ',' || c === '\n') { - row.push(field.trim()); - field = ''; - if (c === '\n') { - rows.push(row); - row = []; - } - } else field += c; - } - } - if (field || row.length) { - row.push(field.trim()); - rows.push(row); - } - return rows; -} - async function upsertOrganization(seed) { const rows = await sql` INSERT INTO organizations ( diff --git a/src/lib/components/molecules/FundingCard.svelte b/src/lib/components/molecules/FundingCard.svelte index ed54553..ee972d4 100644 --- a/src/lib/components/molecules/FundingCard.svelte +++ b/src/lib/components/molecules/FundingCard.svelte @@ -4,16 +4,16 @@ import { stripHtml } from '$lib/utils/format'; import { getPlaceholderImage } from '$lib/data/placeholders'; -let { item, index = 0 }: { item: FundingItem; index?: number } = $props(); + let { item, index = 0 }: { item: FundingItem; index?: number } = $props(); -const href = $derived(`/funding/${item.slug ?? item.id}`); -const plainDesc = $derived(item.description ? stripHtml(String(item.description)) : ''); -const mediaClass = $derived( - 'relative flex h-[148px] items-center justify-center overflow-hidden bg-[var(--color-lakebed-950,#10181e)]' -); -const applicationStatusLabel = $derived( - item.applicationStatus - ? formatDisplayValue(item.applicationStatus, { key: 'applicationStatus' }) + const href = $derived(`/funding/${item.slug ?? item.id}`); + const plainDesc = $derived(item.description ? stripHtml(String(item.description)) : ''); + const mediaClass = $derived( + 'relative flex h-[148px] items-center justify-center overflow-hidden bg-[var(--color-lakebed-950,#10181e)]' + ); + const applicationStatusLabel = $derived( + item.applicationStatus + ? formatDisplayValue(item.applicationStatus, { key: 'applicationStatus' }) : null ); const fundingTypeLabel = $derived( diff --git a/src/lib/components/molecules/LocationRadiusControl.svelte b/src/lib/components/molecules/LocationRadiusControl.svelte index 0917133..ccd459f 100644 --- a/src/lib/components/molecules/LocationRadiusControl.svelte +++ b/src/lib/components/molecules/LocationRadiusControl.svelte @@ -252,7 +252,8 @@ place: placeName }); } catch (err) { - const message = err instanceof GeolocationPositionError ? err.message : 'Unable to get location'; + const message = + err instanceof GeolocationPositionError ? err.message : 'Unable to get location'; geoError = message; } finally { geolocating = false; @@ -313,7 +314,7 @@ role="option" aria-selected={i === activeIndex} > - + {s.placeName} {/each} @@ -556,7 +557,6 @@ cursor: default; } - .kb-radius__error { margin: 0; font-size: 0.72rem; @@ -590,7 +590,9 @@ align-items: baseline; gap: 0.3rem; font-variant-numeric: tabular-nums; - transition: color 0.15s ease, transform 0.15s ease; + transition: + color 0.15s ease, + transform 0.15s ease; } .kb-radius__slider-readout strong { diff --git a/src/lib/components/organisms/CoilDetailActionRail.svelte b/src/lib/components/organisms/CoilDetailActionRail.svelte index 425ff23..c006b18 100644 --- a/src/lib/components/organisms/CoilDetailActionRail.svelte +++ b/src/lib/components/organisms/CoilDetailActionRail.svelte @@ -90,11 +90,7 @@ } = $props(); -
+
{#if breadcrumb}
@@ -257,12 +253,7 @@ color: var(--muted-foreground); font-variant-numeric: tabular-nums; - mask-image: linear-gradient( - to right, - black 0%, - black calc(100% - 1.75rem), - transparent 100% - ); + mask-image: linear-gradient(to right, black 0%, black calc(100% - 1.75rem), transparent 100%); -webkit-mask-image: linear-gradient( to right, black 0%, @@ -326,8 +317,7 @@ border-color: var(--coil-accent, var(--teal)); color: var(--coil-accent, var(--teal)); transform: translateY(-1px); - box-shadow: 0 2px 12px - color-mix(in srgb, var(--coil-accent, var(--teal)) 20%, transparent); + box-shadow: 0 2px 12px color-mix(in srgb, var(--coil-accent, var(--teal)) 20%, transparent); } .coil-rail-heart:active { transform: scale(0.92); diff --git a/src/lib/components/organisms/CoilDetailHero.svelte b/src/lib/components/organisms/CoilDetailHero.svelte index 83dfc98..1d6777e 100644 --- a/src/lib/components/organisms/CoilDetailHero.svelte +++ b/src/lib/components/organisms/CoilDetailHero.svelte @@ -80,7 +80,13 @@ > {#if hasBanner} {#if imageFit === 'logo-on-cover'} - + {:else}
@@ -168,12 +168,7 @@ {/if} - -
{:else} -
+
{/if} @@ -966,7 +963,9 @@ } .kb-pdf__thumb:focus-visible .kb-pdf__thumb-preview { - box-shadow: 0 0 0 2px var(--background), 0 0 0 4px var(--ring, var(--foreground)); + box-shadow: + 0 0 0 2px var(--background), + 0 0 0 4px var(--ring, var(--foreground)); } .kb-pdf__thumb.is-active { diff --git a/src/lib/components/ui/slider/slider.svelte b/src/lib/components/ui/slider/slider.svelte index 359b49b..afffeb1 100644 --- a/src/lib/components/ui/slider/slider.svelte +++ b/src/lib/components/ui/slider/slider.svelte @@ -31,23 +31,23 @@ 'relative flex w-full touch-none items-center select-none data-[disabled]:opacity-50 data-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-44 data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col', className )} - {...(restProps as any)} + {...restProps as any} > {#snippet children({ thumbs }: { thumbs: number[] })} {#each thumbs as index (index)} {/each} {/snippet} diff --git a/src/lib/config/runtime-config-core.ts b/src/lib/config/runtime-config-core.ts new file mode 100644 index 0000000..5279271 --- /dev/null +++ b/src/lib/config/runtime-config-core.ts @@ -0,0 +1,472 @@ +import { + type DeploymentEnvironment, + type RuntimeConfigCategory, + type RuntimeConfigClassification, + type RuntimeConfigKey, + type RuntimeConfigSource, + type RuntimeConfigVisibility, + RUNTIME_CONFIG_CATALOG, + isProductionLikeEnvironment, + resolveRuntimeConfigSnapshot +} from './runtime-secrets'; + +export type RuntimeConfigIssue = { + key: string; + message: string; +}; + +export type RuntimeConfigInventoryEntry = { + key: RuntimeConfigKey; + category: RuntimeConfigCategory; + visibility: RuntimeConfigVisibility; + classification: RuntimeConfigClassification; + description: string; + required: boolean; + configured: boolean; + source: RuntimeConfigSource; +}; + +export type RuntimeConfigHealth = { + ok: boolean; + environment: DeploymentEnvironment; + enforceProduction: boolean; + missing: RuntimeConfigIssue[]; + invalid: RuntimeConfigIssue[]; + warnings: RuntimeConfigIssue[]; + inventory: RuntimeConfigInventoryEntry[]; +}; + +export type RuntimeConfigOptions = { + enforceProduction?: boolean; + readFile?: (path: string) => string; +}; + +function isBlank(value: string | undefined): boolean { + return !value?.trim(); +} + +function stripTrailingSlash(value: string): string { + return value.replace(/\/+$/, ''); +} + +function addMissing(target: RuntimeConfigIssue[], key: string, message: string) { + target.push({ key, message }); +} + +function addInvalid(target: RuntimeConfigIssue[], key: string, message: string) { + target.push({ key, message }); +} + +function addWarning(target: RuntimeConfigIssue[], key: string, message: string) { + target.push({ key, message }); +} + +function readBoolean(value: string | undefined, fallback = false): boolean { + if (!value) return fallback; + + switch (value.trim().toLowerCase()) { + case '1': + case 'true': + case 'yes': + case 'on': + return true; + case '0': + case 'false': + case 'no': + case 'off': + return false; + default: + return fallback; + } +} + +function isLikelyLocalhost(hostname: string): boolean { + return hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '0.0.0.0'; +} + +function looksLikePlaceholder(value: string | undefined): boolean { + if (!value) return false; + + const normalized = value.trim().toLowerCase(); + if (!normalized) return false; + + return ( + [ + 'change-me', + 'changeme', + 'replace-me', + 'placeholder', + 'generate-a-random-32-plus-character-secret', + 'generate-a-random-secret', + 'your-app.up.railway.app', + 'phc_your_project_api_key', + 'pk_your_production_token' + ].includes(normalized) || /(replace[-_ ]me|changeme|placeholder)/i.test(normalized) + ); +} + +export function resolveRuntimeOriginFromValues( + rawValues: Record, + options: RuntimeConfigOptions = {} +): string | undefined { + const snapshot = resolveRuntimeConfigSnapshot(rawValues, { readFile: options.readFile }); + const values = snapshot.values; + + if (!isBlank(values.ORIGIN)) { + return stripTrailingSlash(values.ORIGIN!.trim()); + } + + if (isBlank(values.RAILWAY_PUBLIC_DOMAIN)) { + return undefined; + } + + const railwayPublicDomain = stripTrailingSlash(values.RAILWAY_PUBLIC_DOMAIN!.trim()); + return /^https?:\/\//i.test(railwayPublicDomain) + ? railwayPublicDomain + : `https://${railwayPublicDomain}`; +} + +function validateOrigin( + values: Record, + environment: DeploymentEnvironment, + options: RuntimeConfigOptions +): string | null { + const origin = resolveRuntimeOriginFromValues(values, options); + if (isBlank(origin)) { + return 'ORIGIN is required unless Railway provides RAILWAY_PUBLIC_DOMAIN.'; + } + + try { + const parsed = new URL(origin!); + if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') { + return 'ORIGIN must use http or https.'; + } + if ( + environment !== 'development' && + environment !== 'test' && + environment !== 'ci' && + isLikelyLocalhost(parsed.hostname) + ) { + return 'ORIGIN must not point at localhost outside development or test.'; + } + return null; + } catch { + return 'ORIGIN must be a valid absolute URL.'; + } +} + +function validateOpaqueSecret( + secret: string | undefined, + key: string, + environment: DeploymentEnvironment, + minLength = 32 +): string | null { + if (isBlank(secret)) return `${key} is required.`; + if (secret!.trim().length < minLength) return `${key} must be at least ${minLength} characters.`; + if (isProductionLikeEnvironment(environment) && looksLikePlaceholder(secret)) { + return `${key} must be replaced with a real secret before ${environment}.`; + } + return null; +} + +function validatePort(port: string | undefined, key: string): string | null { + if (isBlank(port)) return `${key} is required.`; + const parsed = Number.parseInt(port!, 10); + if (!Number.isInteger(parsed) || parsed <= 0 || parsed > 65535) { + return `${key} must be a valid TCP port.`; + } + return null; +} + +function validateLogLevel(logLevel: string | undefined): string | null { + if (isBlank(logLevel)) return null; + const normalized = logLevel!.trim().toLowerCase(); + return ['debug', 'info', 'warn', 'error'].includes(normalized) + ? null + : 'LOG_LEVEL must be one of debug, info, warn, or error.'; +} + +function validateAbsoluteUrl( + value: string | undefined, + key: string, + { allowLocalhost = true }: { allowLocalhost?: boolean } = {} +): string | null { + if (isBlank(value)) return `${key} is required.`; + + try { + const parsed = new URL(value!.trim()); + if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') { + return `${key} must use http or https.`; + } + if (!allowLocalhost && isLikelyLocalhost(parsed.hostname)) { + return `${key} must not point at localhost outside development or test.`; + } + if (parsed.pathname !== '/' && parsed.pathname.endsWith('/')) { + return `${key} must not include a trailing slash.`; + } + if (value!.trim().endsWith('/')) { + return `${key} must not include a trailing slash.`; + } + if (!allowLocalhost && looksLikePlaceholder(value)) { + return `${key} must be replaced with a real ${key.toLowerCase()} before deploy.`; + } + return null; + } catch { + return `${key} must be a valid absolute URL.`; + } +} + +function validateDatabaseUrl( + value: string | undefined, + environment: DeploymentEnvironment +): string | null { + if (isBlank(value)) return 'DATABASE_URL is required.'; + + try { + const parsed = new URL(value!.trim()); + if (!['postgres:', 'postgresql:'].includes(parsed.protocol)) { + return 'DATABASE_URL must use postgres or postgresql.'; + } + if (isProductionLikeEnvironment(environment) && isLikelyLocalhost(parsed.hostname)) { + return 'DATABASE_URL must not point at localhost in staging or production.'; + } + if (isProductionLikeEnvironment(environment) && looksLikePlaceholder(value)) { + return 'DATABASE_URL must be replaced with a real database connection string before deploy.'; + } + return null; + } catch { + return 'DATABASE_URL must be a valid connection string.'; + } +} + +function buildInventory( + values: Record, + environment: DeploymentEnvironment, + options: RuntimeConfigOptions +): RuntimeConfigInventoryEntry[] { + const snapshot = resolveRuntimeConfigSnapshot(values, { readFile: options.readFile }); + + return RUNTIME_CONFIG_CATALOG.map((entry) => ({ + key: entry.key, + category: entry.category, + visibility: entry.visibility, + classification: entry.classification, + description: entry.description, + required: (entry.requiredIn as readonly DeploymentEnvironment[]).includes(environment), + configured: !isBlank(snapshot.values[entry.key]), + source: snapshot.resolved[entry.key].source + })); +} + +export function inspectRuntimeConfig( + rawValues: Record, + options: RuntimeConfigOptions = {} +): RuntimeConfigHealth { + const snapshot = resolveRuntimeConfigSnapshot(rawValues, { readFile: options.readFile }); + const values = snapshot.values; + const environment = snapshot.environment; + const enforceProduction = options.enforceProduction ?? false; + const strictEnvironment = enforceProduction || isProductionLikeEnvironment(environment); + + const missing: RuntimeConfigIssue[] = []; + const invalid: RuntimeConfigIssue[] = []; + const warnings: RuntimeConfigIssue[] = []; + + for (const entry of Object.values(snapshot.resolved)) { + if (entry.source === 'file_error' && entry.error) { + addInvalid(invalid, entry.key, entry.error); + } + } + + if (strictEnvironment || environment === 'ci') { + const databaseUrlIssue = validateDatabaseUrl(values.DATABASE_URL, environment); + if (databaseUrlIssue) addInvalid(invalid, 'DATABASE_URL', databaseUrlIssue); + + const originIssue = validateOrigin(values, environment, options); + if (originIssue) addInvalid(invalid, 'ORIGIN', originIssue); + if (isBlank(values.ORIGIN) && !isBlank(values.RAILWAY_PUBLIC_DOMAIN)) { + addWarning( + warnings, + 'ORIGIN', + 'Using Railway public domain as the runtime origin fallback. Set ORIGIN explicitly before switching domains or configuring OAuth callbacks.' + ); + } + + const authSecretIssue = validateOpaqueSecret( + values.BETTER_AUTH_SECRET, + 'BETTER_AUTH_SECRET', + environment + ); + if (authSecretIssue) addInvalid(invalid, 'BETTER_AUTH_SECRET', authSecretIssue); + } + + if (strictEnvironment) { + for (const key of [ + 'SMTP_HOST', + 'SMTP_FROM', + 'MEILISEARCH_HOST', + 'MEILISEARCH_API_KEY', + 'MINIO_ENDPOINT', + 'MINIO_ACCESS_KEY', + 'MINIO_SECRET_KEY', + 'MINIO_BUCKET', + 'REINDEX_SECRET', + 'SOURCE_OPS_SECRET' + ] as const) { + if (isBlank(values[key])) { + addMissing(missing, key, `${key} is required.`); + } + } + + if (isBlank(values.PUBLIC_ASSET_BASE_URL)) { + addMissing(missing, 'PUBLIC_ASSET_BASE_URL', 'PUBLIC_ASSET_BASE_URL is required.'); + } + + for (const [key, value] of [ + ['MINIO_ENDPOINT', values.MINIO_ENDPOINT], + ['MEILISEARCH_HOST', values.MEILISEARCH_HOST], + ['PUBLIC_ASSET_BASE_URL', values.PUBLIC_ASSET_BASE_URL] + ] as const) { + if (!isBlank(value)) { + const issue = validateAbsoluteUrl(value, key, { allowLocalhost: false }); + if (issue) addInvalid(invalid, key, issue); + } + } + + for (const [key, value, minLength] of [ + ['REINDEX_SECRET', values.REINDEX_SECRET, 32], + ['SOURCE_OPS_SECRET', values.SOURCE_OPS_SECRET, 32], + ['MEILISEARCH_API_KEY', values.MEILISEARCH_API_KEY, 12], + ['MINIO_ACCESS_KEY', values.MINIO_ACCESS_KEY, 8], + ['MINIO_SECRET_KEY', values.MINIO_SECRET_KEY, 8] + ] as const) { + if (!isBlank(value)) { + const issue = validateOpaqueSecret(value, key, environment, minLength); + if (issue) addInvalid(invalid, key, issue); + } + } + + const smtpPortIssue = validatePort(values.SMTP_PORT, 'SMTP_PORT'); + if (smtpPortIssue) addInvalid(invalid, 'SMTP_PORT', smtpPortIssue); + + if (!readBoolean(values.SMTP_SECURE) && !readBoolean(values.SMTP_REQUIRE_TLS)) { + addWarning( + warnings, + 'SMTP_SECURE', + 'Production email should enable SMTPS (`SMTP_SECURE=true`) or STARTTLS (`SMTP_REQUIRE_TLS=true`).' + ); + } + } + + if (environment === 'ci') { + for (const key of [ + 'DATABASE_URL', + 'BETTER_AUTH_SECRET', + 'ORIGIN', + 'PUBLIC_ASSET_BASE_URL', + 'REINDEX_SECRET', + 'SOURCE_OPS_SECRET' + ] as const) { + if (isBlank(values[key])) { + addMissing(missing, key, `${key} is required for CI app/test boot.`); + } + } + + if (!isBlank(values.PUBLIC_ASSET_BASE_URL)) { + const publicAssetBaseUrlIssue = validateAbsoluteUrl( + values.PUBLIC_ASSET_BASE_URL, + 'PUBLIC_ASSET_BASE_URL' + ); + if (publicAssetBaseUrlIssue) { + addInvalid(invalid, 'PUBLIC_ASSET_BASE_URL', publicAssetBaseUrlIssue); + } + } + } + + const logLevelIssue = validateLogLevel(values.LOG_LEVEL); + if (logLevelIssue) addInvalid(invalid, 'LOG_LEVEL', logLevelIssue); + + if (isBlank(values.SENTRY_DSN) && isBlank(values.PUBLIC_SENTRY_DSN)) { + addWarning( + warnings, + 'SENTRY_DSN', + 'Sentry is not configured; production error tracking will rely on stdout and webhooks only.' + ); + } + + const googleClientIdConfigured = !isBlank(values.GOOGLE_CLIENT_ID); + const googleClientSecretConfigured = !isBlank(values.GOOGLE_CLIENT_SECRET); + + if (googleClientIdConfigured !== googleClientSecretConfigured) { + addInvalid( + invalid, + googleClientIdConfigured ? 'GOOGLE_CLIENT_SECRET' : 'GOOGLE_CLIENT_ID', + 'Google sign-in requires both GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET.' + ); + } + + if (googleClientSecretConfigured && strictEnvironment) { + const googleSecretIssue = validateOpaqueSecret( + values.GOOGLE_CLIENT_SECRET, + 'GOOGLE_CLIENT_SECRET', + environment, + 12 + ); + if (googleSecretIssue) addInvalid(invalid, 'GOOGLE_CLIENT_SECRET', googleSecretIssue); + } + + if (!googleClientIdConfigured) { + addWarning( + warnings, + 'GOOGLE_CLIENT_ID', + 'Google sign-in is not configured; authentication will be limited to email and password.' + ); + } + + if (!!values.SMTP_USER?.trim() !== !!values.SMTP_PASS?.trim()) { + addInvalid( + invalid, + values.SMTP_USER?.trim() ? 'SMTP_PASS' : 'SMTP_USER', + 'Authenticated SMTP requires both SMTP_USER and SMTP_PASS.' + ); + } + + if (values.SMTP_PASS?.trim() && strictEnvironment) { + const smtpPassIssue = validateOpaqueSecret(values.SMTP_PASS, 'SMTP_PASS', environment, 12); + if (smtpPassIssue) addInvalid(invalid, 'SMTP_PASS', smtpPassIssue); + } + + if (isBlank(values.PUBLIC_POSTHOG_KEY)) { + addWarning( + warnings, + 'PUBLIC_POSTHOG_KEY', + 'PostHog is not configured; product analytics and session replay are disabled.' + ); + } + + if (isBlank(values.ERROR_WEBHOOK_URL)) { + addWarning( + warnings, + 'ERROR_WEBHOOK_URL', + 'ERROR_WEBHOOK_URL is not configured; structured server errors will not fan out to an alerting endpoint.' + ); + } + + if (strictEnvironment && isBlank(values.KB_ENVIRONMENT)) { + addWarning( + warnings, + 'KB_ENVIRONMENT', + 'Set KB_ENVIRONMENT explicitly to staging or production so deploy validation is unambiguous.' + ); + } + + return { + ok: missing.length === 0 && invalid.length === 0, + environment, + enforceProduction, + missing, + invalid, + warnings, + inventory: buildInventory(rawValues, environment, options) + }; +} diff --git a/src/lib/config/runtime-secrets.ts b/src/lib/config/runtime-secrets.ts new file mode 100644 index 0000000..845cf9f --- /dev/null +++ b/src/lib/config/runtime-secrets.ts @@ -0,0 +1,465 @@ +export const DEPLOYMENT_ENVIRONMENTS = [ + 'development', + 'test', + 'ci', + 'staging', + 'production' +] as const; + +export type DeploymentEnvironment = (typeof DEPLOYMENT_ENVIRONMENTS)[number]; + +export type RuntimeConfigCategory = + | 'platform' + | 'core' + | 'auth' + | 'email' + | 'search' + | 'storage' + | 'ops' + | 'observability' + | 'analytics' + | 'maps' + | 'ai'; + +export type RuntimeConfigVisibility = 'private' | 'public'; +export type RuntimeConfigClassification = 'secret' | 'config'; +export type RuntimeConfigSource = 'env' | 'file' | 'missing' | 'file_error'; + +export type RuntimeConfigCatalogEntry = { + key: string; + category: RuntimeConfigCategory; + visibility: RuntimeConfigVisibility; + classification: RuntimeConfigClassification; + requiredIn: readonly DeploymentEnvironment[]; + description: string; +}; + +export const RUNTIME_CONFIG_CATALOG = [ + { + key: 'KB_ENVIRONMENT', + category: 'platform', + visibility: 'private', + classification: 'config', + requiredIn: [], + description: 'Explicit deployment environment name: development, ci, staging, or production.' + }, + { + key: 'DATABASE_URL', + category: 'core', + visibility: 'private', + classification: 'secret', + requiredIn: ['ci', 'staging', 'production'], + description: 'Primary Postgres connection string.' + }, + { + key: 'BETTER_AUTH_SECRET', + category: 'auth', + visibility: 'private', + classification: 'secret', + requiredIn: ['ci', 'staging', 'production'], + description: 'Application auth signing secret.' + }, + { + key: 'ORIGIN', + category: 'core', + visibility: 'private', + classification: 'config', + requiredIn: ['ci', 'staging', 'production'], + description: 'Canonical application origin.' + }, + { + key: 'RAILWAY_PUBLIC_DOMAIN', + category: 'platform', + visibility: 'private', + classification: 'config', + requiredIn: [], + description: 'Railway bootstrap origin fallback.' + }, + { + key: 'GOOGLE_CLIENT_ID', + category: 'auth', + visibility: 'private', + classification: 'config', + requiredIn: [], + description: 'Google OAuth client ID.' + }, + { + key: 'GOOGLE_CLIENT_SECRET', + category: 'auth', + visibility: 'private', + classification: 'secret', + requiredIn: [], + description: 'Google OAuth client secret.' + }, + { + key: 'SMTP_HOST', + category: 'email', + visibility: 'private', + classification: 'config', + requiredIn: ['staging', 'production'], + description: 'SMTP host for transactional mail.' + }, + { + key: 'SMTP_PORT', + category: 'email', + visibility: 'private', + classification: 'config', + requiredIn: ['staging', 'production'], + description: 'SMTP port.' + }, + { + key: 'SMTP_SECURE', + category: 'email', + visibility: 'private', + classification: 'config', + requiredIn: [], + description: 'Whether SMTPS is enabled.' + }, + { + key: 'SMTP_REQUIRE_TLS', + category: 'email', + visibility: 'private', + classification: 'config', + requiredIn: [], + description: 'Whether STARTTLS is enforced.' + }, + { + key: 'SMTP_USER', + category: 'email', + visibility: 'private', + classification: 'config', + requiredIn: [], + description: 'SMTP username.' + }, + { + key: 'SMTP_PASS', + category: 'email', + visibility: 'private', + classification: 'secret', + requiredIn: [], + description: 'SMTP password.' + }, + { + key: 'SMTP_FROM', + category: 'email', + visibility: 'private', + classification: 'config', + requiredIn: ['staging', 'production'], + description: 'Default transactional sender.' + }, + { + key: 'PUBLIC_SENTRY_DSN', + category: 'observability', + visibility: 'public', + classification: 'config', + requiredIn: [], + description: 'Client-side Sentry DSN.' + }, + { + key: 'SENTRY_DSN', + category: 'observability', + visibility: 'private', + classification: 'config', + requiredIn: [], + description: 'Server-side Sentry DSN.' + }, + { + key: 'SENTRY_AUTH_TOKEN', + category: 'observability', + visibility: 'private', + classification: 'secret', + requiredIn: [], + description: 'Build-time source map upload token.' + }, + { + key: 'SENTRY_ORG', + category: 'observability', + visibility: 'private', + classification: 'config', + requiredIn: [], + description: 'Sentry organization slug.' + }, + { + key: 'SENTRY_PROJECT', + category: 'observability', + visibility: 'private', + classification: 'config', + requiredIn: [], + description: 'Sentry project slug.' + }, + { + key: 'PUBLIC_POSTHOG_KEY', + category: 'analytics', + visibility: 'public', + classification: 'config', + requiredIn: [], + description: 'Public PostHog project key.' + }, + { + key: 'PUBLIC_POSTHOG_HOST', + category: 'analytics', + visibility: 'public', + classification: 'config', + requiredIn: [], + description: 'PostHog API host.' + }, + { + key: 'MINIO_ENDPOINT', + category: 'storage', + visibility: 'private', + classification: 'config', + requiredIn: ['staging', 'production'], + description: 'S3-compatible object storage endpoint.' + }, + { + key: 'MINIO_ACCESS_KEY', + category: 'storage', + visibility: 'private', + classification: 'secret', + requiredIn: ['staging', 'production'], + description: 'S3-compatible access key.' + }, + { + key: 'MINIO_SECRET_KEY', + category: 'storage', + visibility: 'private', + classification: 'secret', + requiredIn: ['staging', 'production'], + description: 'S3-compatible secret key.' + }, + { + key: 'MINIO_BUCKET', + category: 'storage', + visibility: 'private', + classification: 'config', + requiredIn: ['staging', 'production'], + description: 'Uploads bucket name.' + }, + { + key: 'PUBLIC_ASSET_BASE_URL', + category: 'storage', + visibility: 'public', + classification: 'config', + requiredIn: ['ci', 'staging', 'production'], + description: 'Public asset origin or CDN base URL.' + }, + { + key: 'MEILISEARCH_HOST', + category: 'search', + visibility: 'private', + classification: 'config', + requiredIn: ['staging', 'production'], + description: 'Meilisearch API host.' + }, + { + key: 'MEILISEARCH_API_KEY', + category: 'search', + visibility: 'private', + classification: 'secret', + requiredIn: ['staging', 'production'], + description: 'Meilisearch API key.' + }, + { + key: 'REINDEX_SECRET', + category: 'ops', + visibility: 'private', + classification: 'secret', + requiredIn: ['ci', 'staging', 'production'], + description: 'Secret header for search reindex automation.' + }, + { + key: 'SOURCE_OPS_SECRET', + category: 'ops', + visibility: 'private', + classification: 'secret', + requiredIn: ['ci', 'staging', 'production'], + description: 'Secret header for source-ops automation.' + }, + { + key: 'LOG_LEVEL', + category: 'observability', + visibility: 'private', + classification: 'config', + requiredIn: [], + description: 'Server log verbosity.' + }, + { + key: 'ERROR_WEBHOOK_URL', + category: 'observability', + visibility: 'private', + classification: 'secret', + requiredIn: [], + description: 'Optional alert fan-out webhook.' + }, + { + key: 'MAPBOX_ACCESS_TOKEN', + category: 'maps', + visibility: 'private', + classification: 'secret', + requiredIn: [], + description: 'Preferred Mapbox API token.' + }, + { + key: 'MAPBOX_TOKEN', + category: 'maps', + visibility: 'private', + classification: 'secret', + requiredIn: [], + description: 'Legacy Mapbox API token alias.' + }, + { + key: 'OPENAI_API_KEY', + category: 'ai', + visibility: 'private', + classification: 'secret', + requiredIn: [], + description: 'Optional OpenAI API key for ingestion enrichment.' + }, + { + key: 'OPENAI_INGESTION_MODEL', + category: 'ai', + visibility: 'private', + classification: 'config', + requiredIn: [], + description: 'Optional OpenAI model override.' + }, + { + key: 'OPENAI_RESPONSES_URL', + category: 'ai', + visibility: 'private', + classification: 'config', + requiredIn: [], + description: 'Optional OpenAI API base override.' + } +] as const satisfies readonly RuntimeConfigCatalogEntry[]; + +export type RuntimeConfigKey = (typeof RUNTIME_CONFIG_CATALOG)[number]['key']; + +export type ResolvedRuntimeConfigValue = { + key: string; + value?: string; + source: RuntimeConfigSource; + error?: string; +}; + +export type RuntimeConfigSnapshot = { + environment: DeploymentEnvironment; + values: Record; + resolved: Record; +}; + +type ResolveValueOptions = { + readFile?: (path: string) => string; +}; + +function normalizeEnvironmentValue(value: string | undefined): DeploymentEnvironment | null { + if (!value) return null; + + switch (value.trim().toLowerCase()) { + case 'dev': + case 'development': + case 'local': + return 'development'; + case 'test': + return 'test'; + case 'ci': + return 'ci'; + case 'stage': + case 'staging': + case 'preview': + return 'staging'; + case 'prod': + case 'production': + return 'production'; + default: + return null; + } +} + +export function detectDeploymentEnvironment( + values: Record +): DeploymentEnvironment { + return ( + normalizeEnvironmentValue(values.KB_ENVIRONMENT) || + normalizeEnvironmentValue(values.APP_ENV) || + normalizeEnvironmentValue(values.DEPLOY_ENV) || + normalizeEnvironmentValue(values.RAILWAY_ENVIRONMENT_NAME) || + (values.NODE_ENV === 'test' ? 'test' : null) || + (values.CI === 'true' ? 'ci' : null) || + (values.NODE_ENV === 'production' ? 'production' : null) || + 'development' + ); +} + +export function isProductionLikeEnvironment(environment: DeploymentEnvironment): boolean { + return environment === 'staging' || environment === 'production'; +} + +export function resolveRuntimeConfigValue( + values: Record, + key: string, + options: ResolveValueOptions = {} +): ResolvedRuntimeConfigValue { + const directValue = values[key]?.trim(); + + if (directValue) { + return { key, value: directValue, source: 'env' }; + } + + const secretFilePath = values[`${key}_FILE`]?.trim(); + if (!secretFilePath) { + return { key, source: 'missing' }; + } + + if (!options.readFile) { + return { + key, + source: 'file_error', + error: `${key}_FILE is set but no file reader was provided.` + }; + } + + try { + const fileValue = options.readFile(secretFilePath).trim(); + if (!fileValue) { + return { + key, + source: 'file_error', + error: `${key}_FILE points to an empty file.` + }; + } + + return { key, value: fileValue, source: 'file' }; + } catch (error) { + return { + key, + source: 'file_error', + error: + error instanceof Error + ? `${key}_FILE could not be read: ${error.message}` + : `${key}_FILE could not be read.` + }; + } +} + +export function resolveRuntimeConfigSnapshot( + values: Record, + options: ResolveValueOptions = {} +): RuntimeConfigSnapshot { + const resolvedEntries = Object.fromEntries( + RUNTIME_CONFIG_CATALOG.map((entry) => { + const resolved = resolveRuntimeConfigValue(values, entry.key, options); + return [entry.key, resolved]; + }) + ); + + const resolvedValues = Object.fromEntries( + Object.entries(resolvedEntries).map(([key, resolved]) => [key, resolved.value]) + ); + + return { + environment: detectDeploymentEnvironment({ ...values, ...resolvedValues }), + values: { ...values, ...resolvedValues }, + resolved: resolvedEntries + }; +} diff --git a/src/lib/server/admin-users.ts b/src/lib/server/admin-users.ts index 4657cca..1751da0 100644 --- a/src/lib/server/admin-users.ts +++ b/src/lib/server/admin-users.ts @@ -179,10 +179,7 @@ export async function adminRevokeUserSessions(input: { headers: Headers; userId: }); } -export async function adminSendPasswordReset(input: { - email: string; - redirectTo?: string; -}) { +export async function adminSendPasswordReset(input: { email: string; redirectTo?: string }) { return callUntypedAuthApi('requestPasswordReset', { body: { email: input.email, diff --git a/src/lib/server/auth.ts b/src/lib/server/auth.ts index af154a3..7760fff 100644 --- a/src/lib/server/auth.ts +++ b/src/lib/server/auth.ts @@ -3,22 +3,23 @@ import { drizzleAdapter } from 'better-auth/adapters/drizzle'; import { admin } from 'better-auth/plugins/admin'; import { adminAc, userAc } from 'better-auth/plugins/admin/access'; import { sveltekitCookies } from 'better-auth/svelte-kit'; -import { env } from '$env/dynamic/private'; import { getRequestEvent } from '$app/server'; import { db } from '$lib/server/db'; import { sendMail } from '$lib/server/email'; import { resolveRuntimeOrigin } from '$lib/server/runtime-config'; +import { readRuntimeConfigValue } from '$lib/server/runtime-secrets'; -const googleClientId = env.GOOGLE_CLIENT_ID?.trim(); -const googleClientSecret = env.GOOGLE_CLIENT_SECRET?.trim(); +const googleClientId = readRuntimeConfigValue('GOOGLE_CLIENT_ID')?.trim(); +const googleClientSecret = readRuntimeConfigValue('GOOGLE_CLIENT_SECRET')?.trim(); const runtimeOrigin = resolveRuntimeOrigin() ?? 'http://localhost:5173'; +const authSecret = readRuntimeConfigValue('BETTER_AUTH_SECRET'); export const googleAuthEnabled = Boolean(googleClientId && googleClientSecret); export const auth = betterAuth({ baseURL: runtimeOrigin, trustedOrigins: [runtimeOrigin], - secret: env.BETTER_AUTH_SECRET, + secret: authSecret, database: drizzleAdapter(db, { provider: 'pg' }), account: { accountLinking: { diff --git a/src/lib/server/db/index.ts b/src/lib/server/db/index.ts index 5d6b6d6..d6bcef3 100644 --- a/src/lib/server/db/index.ts +++ b/src/lib/server/db/index.ts @@ -1,11 +1,12 @@ import { drizzle } from 'drizzle-orm/postgres-js'; import postgres from 'postgres'; import * as schema from './schema'; -import { env } from '$env/dynamic/private'; +import { readRuntimeConfigValue } from '$lib/server/runtime-secrets'; -if (!env.DATABASE_URL) throw new Error('DATABASE_URL is not set'); +const databaseUrl = readRuntimeConfigValue('DATABASE_URL'); +if (!databaseUrl) throw new Error('DATABASE_URL is not set'); -const client = postgres(env.DATABASE_URL); +const client = postgres(databaseUrl); export const db = drizzle(client, { schema }); diff --git a/src/lib/server/email.ts b/src/lib/server/email.ts index 728d54a..9b2cfcd 100644 --- a/src/lib/server/email.ts +++ b/src/lib/server/email.ts @@ -1,5 +1,5 @@ import nodemailer from 'nodemailer'; -import { env } from '$env/dynamic/private'; +import { readRuntimeConfigValue } from '$lib/server/runtime-secrets'; function readBoolean(value: string | undefined, fallback = false): boolean { if (!value) return fallback; @@ -20,16 +20,21 @@ function readBoolean(value: string | undefined, fallback = false): boolean { } } -const smtpPort = Number.parseInt(env.SMTP_PORT ?? '1025', 10); -const smtpSecure = readBoolean(env.SMTP_SECURE, smtpPort === 465); -const smtpRequireTls = readBoolean(env.SMTP_REQUIRE_TLS, false); +const smtpHost = readRuntimeConfigValue('SMTP_HOST') ?? 'localhost'; +const smtpPort = Number.parseInt(readRuntimeConfigValue('SMTP_PORT') ?? '1025', 10); +const smtpSecure = readBoolean(readRuntimeConfigValue('SMTP_SECURE'), smtpPort === 465); +const smtpRequireTls = readBoolean(readRuntimeConfigValue('SMTP_REQUIRE_TLS'), false); +const smtpUser = readRuntimeConfigValue('SMTP_USER'); +const smtpPass = readRuntimeConfigValue('SMTP_PASS'); +const smtpFrom = + readRuntimeConfigValue('SMTP_FROM') ?? '"Knowledge Basket" '; const transporter = nodemailer.createTransport({ - host: env.SMTP_HOST ?? 'localhost', + host: smtpHost, port: Number.isFinite(smtpPort) ? smtpPort : 1025, secure: smtpSecure, requireTLS: smtpRequireTls, - auth: env.SMTP_USER && env.SMTP_PASS ? { user: env.SMTP_USER, pass: env.SMTP_PASS } : undefined + auth: smtpUser && smtpPass ? { user: smtpUser, pass: smtpPass } : undefined }); export async function sendMail({ @@ -44,7 +49,7 @@ export async function sendMail({ text?: string; }): Promise { await transporter.sendMail({ - from: env.SMTP_FROM ?? '"Knowledge Basket" ', + from: smtpFrom, to, subject, html, diff --git a/src/lib/server/ingestion/adapters/csv-import.ts b/src/lib/server/ingestion/adapters/csv-import.ts index 36df0ef..03c68ea 100644 --- a/src/lib/server/ingestion/adapters/csv-import.ts +++ b/src/lib/server/ingestion/adapters/csv-import.ts @@ -1,5 +1,5 @@ import { parse as parseCsv } from 'csv-parse/sync'; -import * as XLSX from 'xlsx'; +import ExcelJS from 'exceljs'; import { normalizeUrl } from '../dedupe'; import { fetchText, inferDownloadUrlFromHtml, selectSourceUrl, splitTags } from '../shared'; import type { @@ -56,7 +56,7 @@ export const csvImportAdapter: IngestionAdapter = { try { const cfg = config as CsvImportConfig; const rows = rawContent.startsWith('PK') - ? parseWorkbook(rawContent, cfg.sheet_name) + ? await parseWorkbook(rawContent, cfg.sheet_name) : (parseCsv(rawContent, { columns: true, skip_empty_lines: true, @@ -154,13 +154,74 @@ export const csvImportAdapter: IngestionAdapter = { } }; -function parseWorkbook(rawContent: string, sheetName?: string) { - const workbook = XLSX.read(Buffer.from(rawContent, 'binary'), { type: 'buffer' }); - const targetSheet = sheetName && workbook.Sheets[sheetName] ? sheetName : workbook.SheetNames[0]; - if (!targetSheet) return []; - return XLSX.utils.sheet_to_json(workbook.Sheets[targetSheet]!, { defval: null }) as Array< - Record - >; +async function parseWorkbook(rawContent: string, sheetName?: string) { + const workbook = new ExcelJS.Workbook(); + const workbookSource = Buffer.from(rawContent, 'binary') as unknown as Parameters< + typeof workbook.xlsx.load + >[0]; + await workbook.xlsx.load(workbookSource); + + const worksheet = + (sheetName ? workbook.getWorksheet(sheetName) : undefined) ?? workbook.worksheets[0]; + if (!worksheet) return []; + + const headerRow = worksheet.getRow(1); + const headerValues = Array.isArray(headerRow.values) + ? (headerRow.values.slice(1) as Array) + : []; + const headers: string[] = headerValues.map( + (value, index) => readHeader(value) ?? `column_${index + 1}` + ); + + if (headers.length === 0) return []; + + const rows: Array> = []; + for (let rowNumber = 2; rowNumber <= worksheet.actualRowCount; rowNumber += 1) { + const row = worksheet.getRow(rowNumber); + const record = Object.fromEntries( + headers.map((header, index) => [header, normalizeCellValue(row.getCell(index + 1).value)]) + ); + + if (Object.values(record).some((value) => value !== null)) { + rows.push(record); + } + } + + return rows; +} + +function readHeader(value: ExcelJS.CellValue | undefined) { + const normalized = normalizeCellValue(value); + if (normalized === null || normalized === undefined) return null; + return String(normalized).trim() || null; +} + +function normalizeCellValue(value: ExcelJS.CellValue | undefined): unknown { + if (value === undefined || value === null) return null; + if (typeof value === 'string') return value.trim() || null; + if (typeof value === 'number' || typeof value === 'boolean') return value; + if (value instanceof Date) return value.toISOString(); + if (Array.isArray(value)) { + const richText = value + .map((entry) => (typeof entry === 'object' && entry && 'text' in entry ? entry.text : '')) + .join('') + .trim(); + return richText || null; + } + if (typeof value === 'object') { + if ('result' in value) return normalizeCellValue(value.result); + if ('text' in value && typeof value.text === 'string') return value.text.trim() || null; + if ('hyperlink' in value && typeof value.hyperlink === 'string') return value.hyperlink; + if ('richText' in value && Array.isArray(value.richText)) { + const richText = value.richText + .map((entry) => entry.text) + .join('') + .trim(); + return richText || null; + } + if ('error' in value && typeof value.error === 'string') return value.error; + } + return String(value).trim() || null; } function readString(value: unknown): string | null { diff --git a/src/lib/server/marketing-email.ts b/src/lib/server/marketing-email.ts index 8463cb4..f1e1b41 100644 --- a/src/lib/server/marketing-email.ts +++ b/src/lib/server/marketing-email.ts @@ -146,11 +146,14 @@ export function buildMarketingEmailTemplate(options: { options.intro, '', renderTextSections(options.sections), - options.primaryCta ? `Primary CTA: ${options.primaryCta.label} — ${options.primaryCta.href}` : null, + options.primaryCta + ? `Primary CTA: ${options.primaryCta.label} — ${options.primaryCta.href}` + : null, options.secondaryCta ? `Secondary CTA: ${options.secondaryCta.label} — ${options.secondaryCta.href}` : null, - options.footerNote ?? 'You are receiving this because of your account activity or email preferences in Knowledge Basket.', + options.footerNote ?? + 'You are receiving this because of your account activity or email preferences in Knowledge Basket.', options.managePreferencesUrl ? `Manage preferences: ${options.managePreferencesUrl}` : null, options.unsubscribeUrl ? `Unsubscribe: ${options.unsubscribeUrl}` : null ] @@ -173,7 +176,10 @@ export function buildFollowDigestEmail(options: { managePreferencesUrl?: string; unsubscribeUrl?: string; }): MarketingEmailTemplate { - const audience = options.organizations.length > 0 ? options.organizations.join(', ') : 'your followed organizations'; + const audience = + options.organizations.length > 0 + ? options.organizations.join(', ') + : 'your followed organizations'; return buildMarketingEmailTemplate({ subject: options.subject ?? 'Updates from the organizations you follow', @@ -186,7 +192,8 @@ export function buildFollowDigestEmail(options: { sections: [ { title: 'Fresh from followed organizations', - description: 'Swap these cards for events, jobs, funding, or resources tied to the user’s follow graph.', + description: + 'Swap these cards for events, jobs, funding, or resources tied to the user’s follow graph.', items: options.items } ], diff --git a/src/lib/server/meilisearch.ts b/src/lib/server/meilisearch.ts index fd67e13..49ff34a 100644 --- a/src/lib/server/meilisearch.ts +++ b/src/lib/server/meilisearch.ts @@ -2,7 +2,6 @@ * Unified Meilisearch client and index settings for public and admin search. */ import { MeiliSearch } from 'meilisearch'; -import { env } from '$env/dynamic/private'; import type { CoilKey, EventItem } from '$lib/data/kb'; import { stripHtml } from '$lib/utils/format'; import { @@ -12,6 +11,7 @@ import { type SearchIssue, type SearchReadiness } from '$lib/server/search-contracts'; +import { readRuntimeConfigValue } from '$lib/server/runtime-secrets'; const SEARCH_INDEX_VERSION = '2026-04-unified-search-v1'; const MEILISEARCH_TIMEOUT_MS = 1500; @@ -393,11 +393,11 @@ let lastHealthCheckAt = 0; let lastHealthStatus = false; function getClient(): MeiliSearch | null { - const host = env.MEILISEARCH_HOST?.trim(); + const host = readRuntimeConfigValue('MEILISEARCH_HOST')?.trim(); if (!host) return null; return new MeiliSearch({ host, - apiKey: env.MEILISEARCH_API_KEY || undefined + apiKey: readRuntimeConfigValue('MEILISEARCH_API_KEY') || undefined }); } @@ -879,11 +879,11 @@ export async function reindexAllEvents(events: (EventItem & { id: string })[]): } export function isMeilisearchConfigured(): boolean { - return !!env.MEILISEARCH_HOST?.trim(); + return !!readRuntimeConfigValue('MEILISEARCH_HOST')?.trim(); } export async function isMeilisearchAvailable(force = false): Promise { - const host = env.MEILISEARCH_HOST?.trim(); + const host = readRuntimeConfigValue('MEILISEARCH_HOST')?.trim(); if (!host) return false; const now = Date.now(); @@ -893,11 +893,10 @@ export async function isMeilisearchAvailable(force = false): Promise { try { const healthUrl = new URL('/health', host.endsWith('/') ? host : `${host}/`).toString(); + const apiKey = readRuntimeConfigValue('MEILISEARCH_API_KEY'); const response = await withTimeout( fetch(healthUrl, { - headers: env.MEILISEARCH_API_KEY - ? { Authorization: `Bearer ${env.MEILISEARCH_API_KEY}` } - : undefined + headers: apiKey ? { Authorization: `Bearer ${apiKey}` } : undefined }), 'meilisearch health' ); @@ -911,13 +910,12 @@ export async function isMeilisearchAvailable(force = false): Promise { } function getMeilisearchHeaders(): HeadersInit | undefined { - return env.MEILISEARCH_API_KEY - ? { Authorization: `Bearer ${env.MEILISEARCH_API_KEY}` } - : undefined; + const apiKey = readRuntimeConfigValue('MEILISEARCH_API_KEY'); + return apiKey ? { Authorization: `Bearer ${apiKey}` } : undefined; } async function listIndexUids(): Promise { - const host = env.MEILISEARCH_HOST?.trim(); + const host = readRuntimeConfigValue('MEILISEARCH_HOST')?.trim(); if (!host) return []; const indexesUrl = new URL('/indexes', host.endsWith('/') ? host : `${host}/`).toString(); diff --git a/src/lib/server/object-storage.ts b/src/lib/server/object-storage.ts index ee6ab91..78500f3 100644 --- a/src/lib/server/object-storage.ts +++ b/src/lib/server/object-storage.ts @@ -8,8 +8,8 @@ import { S3Client, S3ServiceException } from '@aws-sdk/client-s3'; -import { env } from '$env/dynamic/private'; import { buildPublicAssetUrl } from '$lib/config/public-assets'; +import { readRuntimeConfigValue } from '$lib/server/runtime-secrets'; const DEFAULT_REGION = 'us-east-1'; @@ -23,7 +23,7 @@ export type ObjectStorageHealth = { }; function getBucketName(): string { - const bucket = env.MINIO_BUCKET?.trim(); + const bucket = readRuntimeConfigValue('MINIO_BUCKET')?.trim(); if (!bucket) throw new Error('MINIO_BUCKET is not configured'); return bucket; } @@ -45,10 +45,10 @@ function buildPublicBucketPolicy(bucket: string): string { export function isObjectStorageConfigured(): boolean { return !!( - env.MINIO_ENDPOINT?.trim() && - env.MINIO_ACCESS_KEY?.trim() && - env.MINIO_SECRET_KEY?.trim() && - env.MINIO_BUCKET?.trim() + readRuntimeConfigValue('MINIO_ENDPOINT')?.trim() && + readRuntimeConfigValue('MINIO_ACCESS_KEY')?.trim() && + readRuntimeConfigValue('MINIO_SECRET_KEY')?.trim() && + readRuntimeConfigValue('MINIO_BUCKET')?.trim() ); } @@ -59,11 +59,11 @@ function getObjectStorageClient(): S3Client { return new S3Client({ region: DEFAULT_REGION, - endpoint: env.MINIO_ENDPOINT, + endpoint: readRuntimeConfigValue('MINIO_ENDPOINT'), forcePathStyle: true, credentials: { - accessKeyId: env.MINIO_ACCESS_KEY ?? '', - secretAccessKey: env.MINIO_SECRET_KEY ?? '' + accessKeyId: readRuntimeConfigValue('MINIO_ACCESS_KEY') ?? '', + secretAccessKey: readRuntimeConfigValue('MINIO_SECRET_KEY') ?? '' } }); } @@ -181,7 +181,7 @@ export async function getObjectStorageHealth(): Promise { return { configured: true, available: false, - bucket: env.MINIO_BUCKET?.trim() ?? null, + bucket: readRuntimeConfigValue('MINIO_BUCKET')?.trim() ?? null, error: error instanceof Error ? error.message : 'Unable to reach object storage' }; } diff --git a/src/lib/server/runtime-config.ts b/src/lib/server/runtime-config.ts index cb63b8d..d0c0fb4 100644 --- a/src/lib/server/runtime-config.ts +++ b/src/lib/server/runtime-config.ts @@ -1,276 +1,37 @@ -import { env } from '$env/dynamic/private'; -import { env as publicEnv } from '$env/dynamic/public'; +import { + inspectRuntimeConfig, + resolveRuntimeOriginFromValues, + type RuntimeConfigHealth, + type RuntimeConfigOptions +} from '$lib/config/runtime-config-core'; +import { getRuntimeConfigValues, readSecretFile } from '$lib/server/runtime-secrets'; -type RuntimeConfigIssue = { - key: string; - message: string; -}; +export type { + RuntimeConfigHealth, + RuntimeConfigInventoryEntry, + RuntimeConfigIssue, + RuntimeConfigOptions +} from '$lib/config/runtime-config-core'; -export type RuntimeConfigHealth = { - ok: boolean; - enforceProduction: boolean; - missing: RuntimeConfigIssue[]; - invalid: RuntimeConfigIssue[]; - warnings: RuntimeConfigIssue[]; -}; - -type RuntimeConfigOptions = { - enforceProduction: boolean; -}; - -function isBlank(value: string | undefined): boolean { - return !value?.trim(); -} - -function stripTrailingSlash(value: string): string { - return value.replace(/\/+$/, ''); -} - -function addMissing(target: RuntimeConfigIssue[], key: string, message: string) { - target.push({ key, message }); -} - -function addInvalid(target: RuntimeConfigIssue[], key: string, message: string) { - target.push({ key, message }); -} - -function addWarning(target: RuntimeConfigIssue[], key: string, message: string) { - target.push({ key, message }); -} - -function readBoolean(value: string | undefined, fallback = false): boolean { - if (!value) return fallback; - - switch (value.trim().toLowerCase()) { - case '1': - case 'true': - case 'yes': - case 'on': - return true; - case '0': - case 'false': - case 'no': - case 'off': - return false; - default: - return fallback; - } -} - -function isLikelyLocalhost(hostname: string): boolean { - return hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '0.0.0.0'; -} - -function resolveRuntimeOriginFromValues( - values: Record -): string | undefined { - if (!isBlank(values.ORIGIN)) { - return stripTrailingSlash(values.ORIGIN!.trim()); - } - - if (isBlank(values.RAILWAY_PUBLIC_DOMAIN)) { - return undefined; - } - - const railwayPublicDomain = stripTrailingSlash(values.RAILWAY_PUBLIC_DOMAIN!.trim()); - return /^https?:\/\//i.test(railwayPublicDomain) - ? railwayPublicDomain - : `https://${railwayPublicDomain}`; -} - -function validateOrigin(values: Record): string | null { - const origin = resolveRuntimeOriginFromValues(values); - if (isBlank(origin)) { - return 'ORIGIN is required unless Railway provides RAILWAY_PUBLIC_DOMAIN.'; - } - - try { - const parsed = new URL(origin!); - if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') { - return 'ORIGIN must use http or https.'; - } - if (isLikelyLocalhost(parsed.hostname)) { - return 'ORIGIN must not point at localhost in production.'; - } - return null; - } catch { - return 'ORIGIN must be a valid absolute URL.'; - } -} - -function validateSecret(secret: string | undefined, key: string): string | null { - if (isBlank(secret)) return `${key} is required.`; - if (secret!.trim().length < 32) return `${key} must be at least 32 characters.`; - return null; -} - -function validatePort(port: string | undefined, key: string): string | null { - if (isBlank(port)) return `${key} is required.`; - const parsed = Number.parseInt(port!, 10); - if (!Number.isInteger(parsed) || parsed <= 0 || parsed > 65535) { - return `${key} must be a valid TCP port.`; - } - return null; -} - -function validateLogLevel(logLevel: string | undefined): string | null { - if (isBlank(logLevel)) return null; - const normalized = logLevel!.trim().toLowerCase(); - return ['debug', 'info', 'warn', 'error'].includes(normalized) - ? null - : 'LOG_LEVEL must be one of debug, info, warn, or error.'; -} - -function validateAbsoluteUrl(value: string | undefined, key: string): string | null { - if (isBlank(value)) return `${key} is required.`; - - try { - const parsed = new URL(value!.trim()); - if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') { - return `${key} must use http or https.`; - } - if (parsed.pathname !== '/' && parsed.pathname.endsWith('/')) { - return `${key} must not include a trailing slash.`; - } - if (value!.trim().endsWith('/')) { - return `${key} must not include a trailing slash.`; - } - return null; - } catch { - return `${key} must be a valid absolute URL.`; - } -} - -function validateProductionRuntimeConfig( - values: Record, - options: RuntimeConfigOptions -): RuntimeConfigHealth { - const missing: RuntimeConfigIssue[] = []; - const invalid: RuntimeConfigIssue[] = []; - const warnings: RuntimeConfigIssue[] = []; - - if (options.enforceProduction) { - if (isBlank(values.DATABASE_URL)) { - addMissing(missing, 'DATABASE_URL', 'DATABASE_URL is required.'); - } - - const originIssue = validateOrigin(values); - if (originIssue) addInvalid(invalid, 'ORIGIN', originIssue); - if (isBlank(values.ORIGIN) && !isBlank(values.RAILWAY_PUBLIC_DOMAIN)) { - addWarning( - warnings, - 'ORIGIN', - 'Using Railway public domain as the runtime origin fallback. Set ORIGIN explicitly before switching to a custom domain or configuring OAuth callbacks.' - ); - } - - const authSecretIssue = validateSecret(values.BETTER_AUTH_SECRET, 'BETTER_AUTH_SECRET'); - if (authSecretIssue) addInvalid(invalid, 'BETTER_AUTH_SECRET', authSecretIssue); - - for (const key of [ - 'SMTP_HOST', - 'SMTP_FROM', - 'MEILISEARCH_HOST', - 'MEILISEARCH_API_KEY', - 'MINIO_ENDPOINT', - 'MINIO_ACCESS_KEY', - 'MINIO_SECRET_KEY', - 'MINIO_BUCKET', - 'REINDEX_SECRET', - 'SOURCE_OPS_SECRET' - ]) { - if (isBlank(values[key])) { - addMissing(missing, key, `${key} is required.`); - } - } - - if (isBlank(values.PUBLIC_ASSET_BASE_URL)) { - addMissing(missing, 'PUBLIC_ASSET_BASE_URL', 'PUBLIC_ASSET_BASE_URL is required.'); - } else { - const publicAssetBaseUrlIssue = validateAbsoluteUrl( - values.PUBLIC_ASSET_BASE_URL, - 'PUBLIC_ASSET_BASE_URL' - ); - if (publicAssetBaseUrlIssue) { - addInvalid(invalid, 'PUBLIC_ASSET_BASE_URL', publicAssetBaseUrlIssue); - } - } - - const smtpPortIssue = validatePort(values.SMTP_PORT, 'SMTP_PORT'); - if (smtpPortIssue) addInvalid(invalid, 'SMTP_PORT', smtpPortIssue); - - if (!readBoolean(values.SMTP_SECURE) && !readBoolean(values.SMTP_REQUIRE_TLS)) { - addWarning( - warnings, - 'SMTP_SECURE', - 'Production email should enable SMTPS (`SMTP_SECURE=true`) or STARTTLS (`SMTP_REQUIRE_TLS=true`).' - ); - } - } - - const logLevelIssue = validateLogLevel(values.LOG_LEVEL); - if (logLevelIssue) addInvalid(invalid, 'LOG_LEVEL', logLevelIssue); - - if (isBlank(values.SENTRY_DSN) && isBlank(values.PUBLIC_SENTRY_DSN)) { - addWarning( - warnings, - 'SENTRY_DSN', - 'Sentry is not configured; production error tracking will rely on stdout and webhooks only.' - ); - } - - const googleClientIdConfigured = !isBlank(values.GOOGLE_CLIENT_ID); - const googleClientSecretConfigured = !isBlank(values.GOOGLE_CLIENT_SECRET); - - if (googleClientIdConfigured !== googleClientSecretConfigured) { - addInvalid( - invalid, - googleClientIdConfigured ? 'GOOGLE_CLIENT_SECRET' : 'GOOGLE_CLIENT_ID', - 'Google sign-in requires both GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET.' - ); - } else if (!googleClientIdConfigured) { - addWarning( - warnings, - 'GOOGLE_CLIENT_ID', - 'Google sign-in is not configured; authentication will be limited to email and password.' - ); - } - - if (isBlank(values.PUBLIC_POSTHOG_KEY)) { - addWarning( - warnings, - 'PUBLIC_POSTHOG_KEY', - 'PostHog is not configured; product analytics and session replay are disabled.' - ); - } - - if (isBlank(values.ERROR_WEBHOOK_URL)) { - addWarning( - warnings, - 'ERROR_WEBHOOK_URL', - 'ERROR_WEBHOOK_URL is not configured; structured server errors will not fan out to an alerting endpoint.' - ); - } - - return { - ok: missing.length === 0 && invalid.length === 0, - enforceProduction: options.enforceProduction, - missing, - invalid, - warnings - }; -} - -export function getRuntimeConfigHealth(options: RuntimeConfigOptions): RuntimeConfigHealth { - return validateProductionRuntimeConfig({ ...env, ...publicEnv }, options); +export function getRuntimeConfigHealth(options: RuntimeConfigOptions = {}): RuntimeConfigHealth { + return inspectRuntimeConfig(getRuntimeConfigValues(), { ...options, readFile: readSecretFile }); } export function resolveRuntimeOrigin(): string | undefined { - return resolveRuntimeOriginFromValues({ ...env, ...process.env }); + const values = getRuntimeConfigValues(); + + return resolveRuntimeOriginFromValues( + { + ...values, + ORIGIN: process.env.ORIGIN ?? values.ORIGIN, + RAILWAY_PUBLIC_DOMAIN: process.env.RAILWAY_PUBLIC_DOMAIN ?? values.RAILWAY_PUBLIC_DOMAIN + }, + { readFile: readSecretFile } + ); } export function assertProductionRuntimeConfig(): void { - if (readBoolean(process.env.SKIP_PRODUCTION_RUNTIME_CONFIG_ASSERTION)) { + if (process.env.SKIP_PRODUCTION_RUNTIME_CONFIG_ASSERTION === '1') { return; } @@ -288,7 +49,7 @@ export function assertProductionRuntimeConfig(): void { export function inspectRuntimeConfigForTests( values: Record, - options: RuntimeConfigOptions + options: RuntimeConfigOptions = {} ): RuntimeConfigHealth { - return validateProductionRuntimeConfig(values, options); + return inspectRuntimeConfig(values, options); } diff --git a/src/lib/server/runtime-secrets.ts b/src/lib/server/runtime-secrets.ts new file mode 100644 index 0000000..5dc15c4 --- /dev/null +++ b/src/lib/server/runtime-secrets.ts @@ -0,0 +1,29 @@ +import { readFileSync } from 'node:fs'; +import { env } from '$env/dynamic/private'; +import { env as publicEnv } from '$env/dynamic/public'; +import { + resolveRuntimeConfigSnapshot, + resolveRuntimeConfigValue, + type RuntimeConfigKey +} from '$lib/config/runtime-secrets'; + +export function readSecretFile(path: string): string { + return readFileSync(path, 'utf8'); +} + +export function getRuntimeConfigValues(): Record { + return { ...process.env, ...env, ...publicEnv }; +} + +export function getResolvedRuntimeConfigSnapshot( + values: Record = getRuntimeConfigValues() +) { + return resolveRuntimeConfigSnapshot(values, { readFile: readSecretFile }); +} + +export function readRuntimeConfigValue( + key: RuntimeConfigKey, + values: Record = getRuntimeConfigValues() +): string | undefined { + return resolveRuntimeConfigValue(values, key, { readFile: readSecretFile }).value; +} diff --git a/src/lib/server/search-service.ts b/src/lib/server/search-service.ts index a3940b7..31d9ac2 100644 --- a/src/lib/server/search-service.ts +++ b/src/lib/server/search-service.ts @@ -374,7 +374,9 @@ function buildExperience( function normalizeSearchDoc(doc: SearchDoc): SearchDoc { const imageUrl = - typeof doc.imageUrl === 'string' ? (resolveAbsoluteUrl(doc.imageUrl) ?? doc.imageUrl) : undefined; + typeof doc.imageUrl === 'string' + ? (resolveAbsoluteUrl(doc.imageUrl) ?? doc.imageUrl) + : undefined; if (imageUrl === doc.imageUrl) return doc; diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index e40f18a..5501f63 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -151,7 +151,9 @@ {#snippet statsSnippet()}
{data.counts.events} Events - {data.counts.funding} Funding + {data.counts.funding} Funding Opportunities {data.counts.jobs} Jobs {data.counts.redpages} Red Page Businesses { const posthogConfigured = !!publicEnv.PUBLIC_POSTHOG_KEY?.trim(); const posthogHost = publicEnv.PUBLIC_POSTHOG_HOST?.trim() || POSTHOG_DEFAULT_HOST; const smtpConfigured = !!(env.SMTP_HOST?.trim() && env.SMTP_FROM?.trim()); - const smtpTransportSecurity = env.SMTP_SECURE?.trim() === 'true' - ? 'SMTPS' - : env.SMTP_REQUIRE_TLS?.trim() === 'true' - ? 'STARTTLS' - : 'Not enforced'; + const smtpTransportSecurity = + env.SMTP_SECURE?.trim() === 'true' + ? 'SMTPS' + : env.SMTP_REQUIRE_TLS?.trim() === 'true' + ? 'STARTTLS' + : 'Not enforced'; return { mapboxConfigured, diff --git a/src/routes/admin/settings/integrations/+page.svelte b/src/routes/admin/settings/integrations/+page.svelte index 0e0251e..8dc46f3 100644 --- a/src/routes/admin/settings/integrations/+page.svelte +++ b/src/routes/admin/settings/integrations/+page.svelte @@ -59,8 +59,8 @@ PostHog - Explicit product analytics and masked session replay, only after visitors opt into - analytics cookies. + Explicit product analytics and masked session replay, only after visitors opt into analytics + cookies. diff --git a/src/routes/admin/users/+page.svelte b/src/routes/admin/users/+page.svelte index 34ad9e0..0ef9735 100644 --- a/src/routes/admin/users/+page.svelte +++ b/src/routes/admin/users/+page.svelte @@ -5,7 +5,15 @@ import * as Alert from '$lib/components/ui/alert/index.js'; import { Badge, type BadgeVariant } from '$lib/components/ui/badge/index.js'; import { Input } from '$lib/components/ui/input/index.js'; - import { ShieldAlert, ShieldCheck, Search, KeyRound, Ban, RefreshCcw, Users } from '@lucide/svelte'; + import { + ShieldAlert, + ShieldCheck, + Search, + KeyRound, + Ban, + RefreshCcw, + Users + } from '@lucide/svelte'; let { data, form } = $props(); @@ -63,7 +71,9 @@
-

Directory

+

+ Directory +

{data.total}

@@ -89,7 +99,9 @@
-
+
@@ -112,20 +124,19 @@
- - +
{#if data.users.length === 0} -
+

No users matched that search.

Try a different email fragment, a first name, or clear the current filter. @@ -135,8 +146,12 @@

{#each data.users as managedUser} {@const isCurrentAdmin = managedUser.id === data.currentAdminId} -
-
+
+

@@ -167,14 +182,19 @@

{#if managedUser.banned && managedUser.banReason}

- Reason: {managedUser.banReason} + Reason: + {managedUser.banReason}

{/if}
-
+

Permissions

@@ -225,12 +245,7 @@

Force re-authentication across every active session for this account.

- @@ -253,10 +268,7 @@ {:else}
- + - @@ -280,7 +297,9 @@ {/if} {#if data.totalPages > 1} -
+

Page {data.page} of {data.totalPages}

diff --git a/src/routes/api/reindex/+server.ts b/src/routes/api/reindex/+server.ts index f46954e..0531e22 100644 --- a/src/routes/api/reindex/+server.ts +++ b/src/routes/api/reindex/+server.ts @@ -1,4 +1,3 @@ -import { env } from '$env/dynamic/private'; import { error, json } from '@sveltejs/kit'; import type { RequestHandler } from './$types'; import { requirePrivilegedApiUser } from '$lib/server/access-control'; @@ -10,6 +9,7 @@ import { import { captureServerError, logServerEvent } from '$lib/server/observability'; import { reindexAllPublishedContent, reindexSearchScope } from '$lib/server/search-ops'; import { SEARCH_INDEX_SCOPES, type SearchIndexScope } from '$lib/server/search-contracts'; +import { readRuntimeConfigValue } from '$lib/server/runtime-secrets'; /** * Reindex all events from DB into Meilisearch. Call after db:seed or when search is out of sync. @@ -29,7 +29,7 @@ export const POST: RequestHandler = async ({ locals, request, getClientAddress, ); } - const reindexSecret = env.REINDEX_SECRET?.trim(); + const reindexSecret = readRuntimeConfigValue('REINDEX_SECRET')?.trim(); const headerSecret = request.headers.get('x-reindex-secret')?.trim(); const hasValidSecret = !!reindexSecret && headerSecret === reindexSecret; diff --git a/src/routes/api/source-ops/run-due/+server.ts b/src/routes/api/source-ops/run-due/+server.ts index 05cef89..1ad0627 100644 --- a/src/routes/api/source-ops/run-due/+server.ts +++ b/src/routes/api/source-ops/run-due/+server.ts @@ -1,4 +1,3 @@ -import { env } from '$env/dynamic/private'; import { json } from '@sveltejs/kit'; import { runDueSources } from '$lib/server/ingestion/scheduler'; import type { RequestHandler } from './$types'; @@ -9,6 +8,7 @@ import { consumeRateLimit } from '$lib/server/rate-limit'; import { captureServerError, logServerEvent } from '$lib/server/observability'; +import { readRuntimeConfigValue } from '$lib/server/runtime-secrets'; export const POST: RequestHandler = async ({ locals, request, getClientAddress, url }) => { const rateLimit = consumeRateLimit( @@ -24,7 +24,7 @@ export const POST: RequestHandler = async ({ locals, request, getClientAddress, ); } - const sourceOpsSecret = env.SOURCE_OPS_SECRET?.trim(); + const sourceOpsSecret = readRuntimeConfigValue('SOURCE_OPS_SECRET')?.trim(); const headerSecret = request.headers.get('x-source-ops-secret')?.trim(); const hasValidSecret = !!sourceOpsSecret && headerSecret === sourceOpsSecret; diff --git a/src/routes/cookies/+page.svelte b/src/routes/cookies/+page.svelte index cb98b9a..d9fbcf5 100644 --- a/src/routes/cookies/+page.svelte +++ b/src/routes/cookies/+page.svelte @@ -53,9 +53,7 @@ Knowledge Basket uses PostHog for explicit product analytics and masked session replay after analytics consent is granted. -
  • - The app does not use broad click autocapture or automatic pageview capture for PostHog. -
  • +
  • The app does not use broad click autocapture or automatic pageview capture for PostHog.
  • For signed-in users who opt in, PostHog may receive account-level properties such as email, role, and email-delivery preferences so the team can understand opted-in cohorts and prepare diff --git a/src/routes/o/+page.svelte b/src/routes/o/+page.svelte index 5b88cd9..37f0181 100644 --- a/src/routes/o/+page.svelte +++ b/src/routes/o/+page.svelte @@ -123,11 +123,7 @@ {#if mapboxToken && mapPoints.length}
    - +
    {/if} @@ -208,9 +204,7 @@

    {/if} {#if location || org.region} -
    +
    {location || org.region}
    diff --git a/src/routes/o/[slug]/+page.svelte b/src/routes/o/[slug]/+page.svelte index 0acbbed..c7eda75 100644 --- a/src/routes/o/[slug]/+page.svelte +++ b/src/routes/o/[slug]/+page.svelte @@ -182,88 +182,88 @@
    - {#snippet meta()} - {#if shortLocation} - - - {shortLocation} - - {/if} - {/snippet} - {#snippet actions()} - {#if organization.phone} - - - {#snippet child({ props })} - - {/snippet} - - Call - - {/if} - {#if organization.email} - - - {#snippet child({ props })} - - {/snippet} - - Email - - {/if} - {/snippet} - {#snippet primary()} - {#if organization.website} - - {/if} - {/snippet} + {#snippet meta()} + {#if shortLocation} + + + {shortLocation} + + {/if} + {/snippet} + {#snippet actions()} + {#if organization.phone} + + + {#snippet child({ props })} + + {/snippet} + + Call + + {/if} + {#if organization.email} + + + {#snippet child({ props })} + + {/snippet} + + Email + + {/if} + {/snippet} + {#snippet primary()} + {#if organization.website} + + {/if} + {/snippet}
    diff --git a/src/routes/red-pages/[slug]/+page.svelte b/src/routes/red-pages/[slug]/+page.svelte index f7cc6c4..9ffb9a2 100644 --- a/src/routes/red-pages/[slug]/+page.svelte +++ b/src/routes/red-pages/[slug]/+page.svelte @@ -210,80 +210,80 @@ accent="var(--red)" hasLogoOverhang > - {#snippet actions()} - {#if item.phone} - - - {#snippet child({ props })} - - {/snippet} - - Call - - {/if} - {#if item.email} - - - {#snippet child({ props })} - - {/snippet} - - Email - - {/if} - {/snippet} - {#snippet primary()} - {#if item.website} - - {/if} - {/snippet} + {#snippet actions()} + {#if item.phone} + + + {#snippet child({ props })} + + {/snippet} + + Call + + {/if} + {#if item.email} + + + {#snippet child({ props })} + + {/snippet} + + Email + + {/if} + {/snippet} + {#snippet primary()} + {#if item.website} + + {/if} + {/snippet}
    diff --git a/src/routes/toolbox/+page.svelte b/src/routes/toolbox/+page.svelte index f32646e..6f8ac22 100644 --- a/src/routes/toolbox/+page.svelte +++ b/src/routes/toolbox/+page.svelte @@ -505,7 +505,9 @@
    -
    +

    90 || lng < -180 || lng > 180) continue; - const city = - h.get('x-vercel-ip-city') ?? h.get('cf-ipcity') ?? h.get('x-nf-geo-city') ?? null; + const city = h.get('x-vercel-ip-city') ?? h.get('cf-ipcity') ?? h.get('x-nf-geo-city') ?? null; const region = h.get('x-vercel-ip-country-region') ?? h.get('cf-region') ?? h.get('x-nf-geo-subdivision-code') ?? null; const country = - h.get('x-vercel-ip-country') ?? - h.get('cf-ipcountry') ?? - h.get('x-nf-geo-country') ?? - null; + h.get('x-vercel-ip-country') ?? h.get('cf-ipcountry') ?? h.get('x-nf-geo-country') ?? null; const place = [decodeOrNull(city), decodeOrNull(region) ?? decodeOrNull(country)] @@ -124,8 +120,7 @@ export async function load({ url, request }) { logoUrl: null, badge: p.venueType, verified: false, - location: - [p.city, p.state].filter(Boolean).join(', ') || p.address || null, + location: [p.city, p.state].filter(Boolean).join(', ') || p.address || null, description: p.description })), pagination: { diff --git a/src/routes/v/+page.svelte b/src/routes/v/+page.svelte index 3f5aa3c..44221f8 100644 --- a/src/routes/v/+page.svelte +++ b/src/routes/v/+page.svelte @@ -49,9 +49,7 @@ return mapPoints.filter((p) => { // Bounds straddling the antimeridian flip west > east. const inLng = - b.west <= b.east - ? p.lng >= b.west && p.lng <= b.east - : p.lng >= b.west || p.lng <= b.east; + b.west <= b.east ? p.lng >= b.west && p.lng <= b.east : p.lng >= b.west || p.lng <= b.east; return inLng && p.lat >= b.south && p.lat <= b.north; }); }); @@ -89,8 +87,7 @@ name: v.name, description: v.description ?? null, venueType: v.venueType ?? null, - locationLabel: - [v.city, v.state].filter(Boolean).join(', ') || v.address || null + locationLabel: [v.city, v.state].filter(Boolean).join(', ') || v.address || null })); }); @@ -335,9 +332,7 @@

    {/if} {#if venue.locationLabel} -
    +
    {venue.locationLabel}
    @@ -352,9 +347,7 @@