diff --git a/.github/workflows/ci-e2e.yml b/.github/workflows/ci-e2e.yml
new file mode 100644
index 0000000..036f48e
--- /dev/null
+++ b/.github/workflows/ci-e2e.yml
@@ -0,0 +1,58 @@
+name: E2E Tests
+
+on:
+ push:
+ branches: [ main ]
+ pull_request:
+ branches: [ main ]
+
+jobs:
+ e2e:
+ name: E2E Tests
+ runs-on: ubuntu-latest
+ timeout-minutes: 15
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: '22'
+ cache: 'yarn'
+
+ - name: Install Java (for Firebase emulators)
+ uses: actions/setup-java@v4
+ with:
+ distribution: 'temurin'
+ java-version: '11'
+
+ - name: Install dependencies
+ run: yarn install --frozen-lockfile
+
+ - name: Install Playwright browsers
+ run: npx playwright install chromium --with-deps
+
+ - name: Run E2E tests
+ env:
+ SKIP_DB_CLEANUP: true # Don't try to stop Docker in CI
+ run: |
+ # chmod +x scripts/e2e.sh
+ yarn test:e2e
+
+ - name: Upload Playwright report
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: playwright-report
+ path: playwright-report/
+ retention-days: 7
+
+ - name: Upload test results
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: test-results
+ path: test-results/
+ retention-days: 7
\ No newline at end of file
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index ba03ab6..ef72b6c 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,17 +1,16 @@
-name: CI
+name: Jest Tests
on:
push:
- branches:
- - main
+ branches: [ main ]
pull_request:
- branches:
- - main
+ branches: [ main ]
jobs:
ci:
- name: Tests
+ name: Jest Tests
runs-on: ubuntu-latest
+ timeout-minutes: 15
steps:
- name: Checkout repository
@@ -21,9 +20,10 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: '22'
+ cache: 'yarn'
- name: Install dependencies
- run: yarn install
+ run: yarn install --frozen-lockfile
- name: Type check
run: echo skipping #npx tsc --noEmit
@@ -46,18 +46,6 @@ jobs:
# "web/coverage/lcov.info" \
# > coverage/lcov.info
- # Optional: Playwright E2E tests
- - name: Install Playwright deps
- run: |
- npx playwright install chromium
-# npx playwright install --with-deps
-# npm install @playwright/test
-
- - name: Run E2E tests
- run: |
- chmod +x scripts/e2e.sh
- yarn test:e2e
-
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v5
with:
diff --git a/.gitignore b/.gitignore
index 8323d66..5a951e9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -39,7 +39,6 @@ yarn-error.log*
.env.local
.env.*
.envrc
-supabase/*
# vercel
.vercel
diff --git a/.junie/guidelines.md b/.junie/guidelines.md
new file mode 100644
index 0000000..5a37d7c
--- /dev/null
+++ b/.junie/guidelines.md
@@ -0,0 +1,1315 @@
+---
+trigger: always_on
+description:
+globs:
+---
+
+## Project Structure
+
+- next.js react tailwind frontend `/web`
+ - broken down into pages, components, hooks, lib
+- express node api server `/backend/api`
+- one off scripts, like migrations `/backend/scripts`
+- supabase postgres. schema in `/backend/supabase`
+ - supabase-generated types in `/backend/supabase/schema.ts`
+- files shared between backend directories `/backend/shared`
+ - anything in `/backend` can import from `shared`, but not vice versa
+- files shared between the frontend and backend in `/common`
+ - `/common` has lots of type definitions for our data structures, like User. It also contains many useful utility
+ functions. We try not to add package dependencies to common. `/web` and `/backend` are allowed to import from
+ `/common`, but not vice versa.
+
+## Deployment
+
+- The project has both dev and prod environments.
+- Backend is on GCP (Google Cloud Platform). Deployment handled by terraform.
+- Project ID is `compass-130ba`.
+
+## Code Guidelines
+
+---
+
+Here's an example component from web in our style:
+
+```tsx
+import clsx from 'clsx'
+import Link from 'next/link'
+
+import {isAdminId, isModId} from 'common/envs/constants'
+import {type Headline} from 'common/news'
+import {EditNewsButton} from 'web/components/news/edit-news-button'
+import {Carousel} from 'web/components/widgets/carousel'
+import {useUser} from 'web/hooks/use-user'
+import {track} from 'web/lib/service/analytics'
+import {DashboardEndpoints} from 'web/components/dashboard/dashboard-page'
+import {removeEmojis} from 'common/util/string'
+
+export function HeadlineTabs(props: {
+ headlines: Headline[]
+ currentSlug: string
+ endpoint: DashboardEndpoints
+ hideEmoji?: boolean
+ notSticky?: boolean
+ className?: string
+}) {
+ const {headlines, endpoint, currentSlug, hideEmoji, notSticky, className} =
+ props
+ const user = useUser()
+
+ return (
+
+
+ {headlines.map(({id, slug, title}) => (
+
+ ))}
+ {user && }
+ {user && (isAdminId(user.id) || isModId(user.id)) && (
+
+ )}
+
+
+ )
+}
+```
+
+---
+
+We prefer to have many smaller components that each represent one logical unit, rather than one very large component
+that does everything. Then we compose and reuse the components.
+
+It's best to export the main component at the top of the file. We also try to name the component the same as the file
+name (headline-tabs.tsx) so that it's easy to find.
+
+Here's another example in `home.tsx` that calls our api. We have an endpoint called 'headlines', which is being cached
+by NextJS:
+
+```ts
+import {api} from 'web/lib/api/api'
+
+// More imports...
+
+export async function getStaticProps() {
+ try {
+ const headlines = await api('headlines', {})
+ return {
+ props: {
+ headlines,
+ revalidate: 30 * 60, // 30 minutes
+ },
+ }
+ } catch (err) {
+ return {props: {headlines: []}, revalidate: 60}
+ }
+}
+
+export default function Home(props: { headlines: Headline[] }) { ...
+}
+```
+
+---
+
+If we are calling the API on the client, prefer using the `useAPIGetter` hook:
+
+```ts
+export const YourTopicsSection = (props: {
+ user: User
+ className?: string
+}) => {
+ const {user, className} = props
+ const {data, refresh} = useAPIGetter('get-followed-groups', {
+ userId: user.id,
+ })
+ const followedGroups = data?.groups ?? []
+...
+```
+
+This stores the result in memory, and allows you to call refresh() to get an updated version.
+
+---
+
+We frequently use `usePersistentInMemoryState` or `usePersistentLocalState` as an alternative to `useState`. These cache
+data. Most of the time you want in-memory caching so that navigating back to a page will preserve the same state and
+appear to load instantly.
+
+Here's the definition of usePersistentInMemoryState:
+
+```ts
+export const usePersistentInMemoryState = (initialValue: T, key: string) => {
+ const [state, setState] = useStateCheckEquality(
+ safeJsonParse(store[key]) ?? initialValue
+ )
+
+ useEffect(() => {
+ const storedValue = safeJsonParse(store[key]) ?? initialValue
+ setState(storedValue as T)
+ }, [key])
+
+ const saveState = useEvent((newState: T | ((prevState: T) => T)) => {
+ setState((prevState) => {
+ const updatedState = isFunction(newState) ? newState(prevState) : newState
+ store[key] = JSON.stringify(updatedState)
+ return updatedState
+ })
+ })
+
+ return [state, saveState] as const
+}
+```
+
+---
+
+For live updates, we use websockets. In `use-api-subscription.ts`, we have this hook:
+
+```ts
+export function useApiSubscription(opts: SubscriptionOptions) {
+ useEffect(() => {
+ const ws = client
+ if (ws != null) {
+ if (opts.enabled ?? true) {
+ ws.subscribe(opts.topics, opts.onBroadcast).catch(opts.onError)
+ return () => {
+ ws.unsubscribe(opts.topics, opts.onBroadcast).catch(opts.onError)
+ }
+ }
+ }
+ }, [opts.enabled, JSON.stringify(opts.topics)])
+}
+```
+
+In `use-bets`, we have this hook to get live updates with useApiSubscription:
+
+```ts
+export const useContractBets = (
+ contractId: string,
+ opts?: APIParams<'bets'> & { enabled?: boolean }
+) => {
+ const {enabled = true, ...apiOptions} = {
+ contractId,
+ ...opts,
+ }
+ const optionsKey = JSON.stringify(apiOptions)
+
+ const [newBets, setNewBets] = usePersistentInMemoryState(
+ [],
+ `${optionsKey}-bets`
+ )
+
+ const addBets = (bets: Bet[]) => {
+ setNewBets((currentBets) => {
+ const uniqueBets = sortBy(
+ uniqBy([...currentBets, ...bets], 'id'),
+ 'createdTime'
+ )
+ return uniqueBets.filter((b) => !betShouldBeFiltered(b, apiOptions))
+ })
+ }
+
+ const isPageVisible = useIsPageVisible()
+
+ useEffect(() => {
+ if (isPageVisible && enabled) {
+ api('bets', apiOptions).then(addBets)
+ }
+ }, [optionsKey, enabled, isPageVisible])
+
+ useApiSubscription({
+ topics: [`contract/${contractId}/new-bet`],
+ onBroadcast: (msg) => {
+ addBets(msg.data.bets as Bet[])
+ },
+ enabled,
+ })
+
+ return newBets
+}
+```
+
+---
+
+Here are all the topics we broadcast, from `backend/shared/src/websockets/helpers.ts`
+
+```ts
+export function broadcastUpdatedPrivateUser(userId: string) {
+ // don't send private user info because it's private and anyone can listen
+ broadcast(`private-user/${userId}`, {})
+}
+
+export function broadcastUpdatedUser(user: Partial & { id: string }) {
+ broadcast(`user/${user.id}`, {user})
+}
+
+export function broadcastUpdatedComment(comment: Comment) {
+ broadcast(`user/${comment.onUserId}/comment`, {comment})
+}
+```
+
+---
+
+We have our scripts in the directory `/backend/scripts`.
+
+To write a script, run it inside the helper function called `runScript` that automatically fetches any secret keys and
+loads them into process.env.
+
+Example from `/backend/scripts/manicode.ts`
+
+```ts
+import {runScript} from 'run-script'
+
+runScript(async ({pg}) => {
+ const userPrompt = process.argv[2]
+ await pg.none(...)
+})
+```
+
+Generally scripts should be run by me, especially if they modify backend state or schema.
+But if you need to run a script, you can use `bun`. For example:
+
+```sh
+bun run manicode.ts "Generate a page called cowp, which has cows that make noises!"
+```
+
+if that doesn't work, try
+
+```sh
+bun x ts-node manicode.ts "Generate a page called cowp, which has cows that make noises!"
+```
+
+---
+
+Our backend is mostly a set of endpoints. We create new endpoints by adding to the schema in
+`/common/src/api/schema.ts`.
+
+E.g. Here is a hypothetical bet schema:
+
+```ts
+ bet: {
+ method: 'POST',
+ authed
+:
+ true,
+ returns
+:
+ {
+ }
+ as
+ CandidateBet & {betId: string},
+ props
+:
+ z
+ .object({
+ contractId: z.string(),
+ amount: z.number().gte(1),
+ replyToCommentId: z.string().optional(),
+ limitProb: z.number().gte(0.01).lte(0.99).optional(),
+ expiresAt: z.number().optional(),
+ // Used for binary and new multiple choice contracts (cpmm-multi-1).
+ outcome: z.enum(['YES', 'NO']).default('YES'),
+ //Multi
+ answerId: z.string().optional(),
+ dryRun: z.boolean().optional(),
+ })
+ .strict(),
+}
+```
+
+Then, we define the bet endpoint in `backend/api/src/place-bet.ts`
+
+```ts
+export const placeBet: APIHandler<'bet'> = async (props, auth) => {
+ const isApi = auth.creds.kind === 'key'
+ return await betsQueue.enqueueFn(
+ () => placeBetMain(props, auth.uid, isApi),
+ [props.contractId, auth.uid]
+ )
+}
+```
+
+And finally, you need to register the handler in `backend/api/src/routes.ts`
+
+```ts
+import {placeBet} from './place-bet'
+
+...
+
+const handlers = {
+ bet: placeBet,
+ ...
+}
+```
+
+---
+
+We have two ways to access our postgres database.
+
+```ts
+import {db} from 'web/lib/supabase/db'
+
+db.from('profiles').select('*').eq('user_id', userId)
+```
+
+and
+
+```ts
+import {createSupabaseDirectClient} from 'shared/supabase/init'
+
+const pg = createSupabaseDirectClient()
+pg.oneOrNone>('select * from profiles where user_id = $1', [userId])
+```
+
+The supabase client just uses the supabase client library, which is a wrapper around postgREST. It allows us to query
+and update the database directly from the frontend.
+
+`createSupabaseDirectClient` is used on the backend. it lets us specify sql strings to run directly on our database,
+using the pg-promise library. The client (code in web) does not have permission to do this.
+
+Another example using the direct client:
+
+```ts
+export const getUniqueBettorIds = async (
+ contractId: string,
+ pg: SupabaseDirectClient
+) => {
+ const res = await pg.manyOrNone(
+ 'select distinct user_id from contract_bets where contract_id = $1',
+ [contractId]
+ )
+ return res.map((r) => r.user_id as string)
+}
+```
+
+(you may notice we write sql in lowercase)
+
+We have a few helper functions for updating and inserting data into the database.
+
+```ts
+import {
+ buikInsert,
+ bulkUpdate,
+ bulkUpdateData,
+ bulkUpsert,
+ insert,
+ update,
+ updateData,
+} from 'shared/supabase/utils'
+
+...
+
+const pg = createSupabaseDirectClient()
+
+// you are encouraged to use tryCatch for these
+const {data, error} = await tryCatch(
+ insert(pg, 'profiles', {user_id: auth.uid, ...body})
+)
+
+if (error) throw APIError(500, 'Error creating profile: ' + error.message)
+
+await update(pg, 'profiles', 'user_id', {user_id: auth.uid, age: 99})
+
+await updateData(pg, 'private_users', {id: userId, notifications: {...}})
+```
+
+The sqlBuilder from `shared/supabase/sql-builder.ts` can be used to construct SQL queries with re-useable parts. All it
+does is sanitize and output sql query strings. It has several helper functions including:
+
+- `select`: Specifies the columns to select
+- `from`: Specifies the table to query
+- `where`: Adds WHERE clauses
+- `orderBy`: Specifies the order of results
+- `limit`: Limits the number of results
+- `renderSql`: Combines all parts into a final SQL string
+
+Example usage:
+
+```typescript
+const query = renderSql(
+ select('distinct user_id'),
+ from('contract_bets'),
+ where('contract_id = ${id}', {id}),
+ orderBy('created_time desc'),
+ limitValue != null && limit(limitValue)
+)
+
+const res = await pg.manyOrNone(query)
+```
+
+Use these functions instead of string concatenation.
+
+# Documentation for development
+
+> [!WARNING]
+> TODO: This document is a work in progress. Please help us improve it!
+
+See those other useful documents as well:
+
+- [knowledge.md](knowledge.md) for high-level architecture and design decisions.
+- [README.md](../backend/api/README.md) for the backend API
+- [README.md](../backend/email/README.md) for the email routines and how to set up a local server for quick email
+ rendering
+- [README.md](../web/README.md) for the frontend / web server
+- [TESTING.md](TESTING.md) for testing guidance and direction
+
+### Adding a new profile field
+
+A profile field is any variable associated with a user profile, such as age, politics, diet, etc. You may want to add a
+new profile field if it helps people find better matches.
+
+To do so, you can add code in a similar way as
+in [this commit](https://github.com/CompassConnections/Compass/commit/940c1f5692f63bf72ddccd4ec3b00b1443801682) for the
+`religion` field. If you also want people to filter by that profile field, you'll also need to add it to the search
+filters, as done
+in [this commit](https://github.com/CompassConnections/Compass/commit/a4bb184e95553184a4c8773d7896e4b570508fe5) (for the
+`religion` field as well).
+
+Note that you will also need to add a column to the `profiles` table in the dev database before running the code; you
+can do so via this SQL command (change the type if not `TEXT`):
+
+```sql
+ALTER TABLE profiles
+ ADD COLUMN profile_field TEXT;
+```
+
+Store it in `add_profile_field.sql` in the [migrations](../backend/supabase/migrations) folder and
+run [migrate.sh](../scripts/migrate.sh) from the root folder:
+
+```bash
+./scripts/migrate.sh backend/supabase/migrations/add_profile_field.sql
+```
+
+Then sync the database types from supabase to the local files (which assist Typescript in typing):
+
+```bash
+yarn regen-types dev
+```
+
+That's it!
+
+### Adding a new language
+
+Adding a new language is very easy, especially with translating tools like large language models (ChatGPT, etc.) which
+you can use as first draft.
+
+- Add the language to the LOCALES dictionary in [constants.ts](../common/src/constants.ts) (the key is the locale code,
+ the value is the original language name (not in English)).
+- Duplicate [fr.json](../web/messages/fr.json) and rename it to the locale code (e.g., `de.json` for German). Translate
+ all the strings in the new file (keep the keys identical). LLMs like ChatGPT may not be able to translate the whole
+ file in one go; try to copy-paste by batch of 300 lines and ask the LLM to
+ `translate the values of the json above to (keep the keys unchanged)`. In order to fit the bottom
+ navigation bar on mobile, make sure the values for those keys are less than 10 characters: "nav.home", "
+ nav.messages", "nav.more", "nav.notifs", "nav.people".
+- Duplicate the [fr](../web/public/md/fr) folder and rename it to the locale code (e.g., `de` for German). Translate all
+ the markdown files in the new folder. To do so, you can copy-paste each file into an LLM and ask it to
+ `translate the markdown above to `.
+
+That's all, no code needed!
+
+# Testing
+
+### Why we test
+
+Testing exists to give us fast, reliable feedback about real behavior so we can ship with confidence.
+
+- Prevent regressions: Lock in correct behavior so future changes don’t silently break working features.
+- Enable safe refactoring: A trustworthy suite lets us improve design without fear.
+- Document intent: Tests act as living examples of how modules and components are expected to work.
+- Catch edge cases early: Exercise unhappy paths, timeouts, and integration boundaries before production.
+- Increase release confidence: Unit/integration tests plus a few critical E2E flows gate deployments.
+
+What testing is not
+
+- Not a replacement for monitoring, logging, or manual exploratory testing.
+- Not a quest for 100% coverage—optimize for meaningful scenarios over raw numbers.
+
+How we apply it here
+
+- Unit and integration tests live in each package and run with Jest (see `jest.config.js`).
+- Critical user journeys are covered by Playwright E2E tests under `tests/e2e` (see `playwright.config.ts`).
+
+### Test types at a glance
+
+This project uses three complementary test types. Use the right level for the job:
+
+- Unit tests
+ - Purpose: Verify a single function/module in isolation; fast, deterministic.
+ - Where: Each package under `tests/unit` (e.g., `backend/api/tests/unit`, `web/tests/unit`, `common/tests/unit`,
+ etc.).
+ - Runner: Jest (configured via root `jest.config.js`).
+ - Naming: `*.unit.test.ts` (or `.tsx` for React in `web`).
+ - When to use: Pure logic, utilities, hooks, reducers, small components with mocked dependencies.
+
+- Integration tests
+ - Purpose: Verify multiple units working together (e.g., function + DB/client, component + context/provider) without
+ spinning up the full app.
+ - Where: Each package under `tests/integration` (e.g., `backend/shared/tests/integration`, `web/tests/integration`).
+ - Runner: Jest (configured via root `jest.config.js`).
+ - Naming: `*.integration.test.ts` (or `.tsx` for React in `web`).
+ - When to use: Boundaries between modules, real serialization/parsing, API handlers with mocked network/DB,
+ component trees with providers.
+
+- End-to-End (E2E) tests
+ - Purpose: Validate real user flows across the full stack.
+ - Where: Top-level `tests/e2e` with separate areas for `web` and `backend`.
+ - Runner: Playwright (see root `playwright.config.ts`, `testDir: ./tests/e2e`).
+ - Naming: `*.e2e.spec.ts`.
+ - When to use: Critical journeys (signup, login, checkout), cross-service interactions, smoke tests for deployments.
+
+Quick commands
+
+```bash
+# Jest (unit + integration)
+yarn test
+
+# Playwright (E2E)
+yarn test:e2e
+```
+
+### Where to put test files
+
+```filetree
+# Config
+jest.config.js (for unit and integration tests)
+playwright.config.ts (for e2e tests)
+
+# Top-level End-to-End (Playwright)
+tests/
+├── e2e/
+│ ├── web/
+│ │ ├── pages/
+│ │ └── specs/
+│ │ └── example.e2e.spec.ts
+│ └── backend/
+│ └── specs/
+│ └── api.e2e.spec.ts
+└── reports/
+ └── playwright-report/
+
+# Package-level Unit & Integration (Jest)
+backend/
+├── api/
+│ ├── src/
+│ └── tests/
+│ ├── unit/
+│ │ └── example.unit.test.ts
+│ └── integration/
+│ └── example.integration.test.ts
+├── email/
+│ └── tests/
+│ ├── unit/
+│ └── integration/
+└── shared/
+ └── tests/
+ ├── unit/
+ └── integration/
+
+common/
+└── tests/
+ ├── unit/
+ │ └── example.unit.test.ts
+ └── integration/
+ └── example.integration.test.ts
+
+web/
+└── tests/
+ ├── unit/
+ │ └── example.unit.test.tsx
+ └── integration/
+ └── example.integration.test.tsx
+```
+
+- End-to-End tests live under `tests/e2e` and are executed by Playwright. The root `playwright.config.ts` sets `testDir`
+ to `./tests/e2e`.
+- Unit and integration tests live in each package’s `tests` folder and are executed by Jest via the root
+ `jest.config.js` projects array.
+- Naming:
+ - Unit: `*.unit.test.ts` (or `.tsx` for React in `web`)
+ - Integration: `*.integration.test.ts`
+ - E2E (Playwright): `*.e2e.spec.ts`
+
+### Best Practices
+
+* Test Behavior, Not Implementation. Don’t test internal state or function calls unless you’re testing utilities or very
+ critical behavior.
+* Use msw to Mock APIs. Don't manually mock fetch—use msw to simulate realistic behavior, including network delays and
+ errors.
+* Don’t Overuse Snapshots. Snapshots are fragile and often meaningless unless used sparingly (e.g., for JSON response
+ schemas).
+* Prefer userEvent Over fireEvent. It simulates real user interactions more accurately.
+* Avoid Testing Next.js Internals . You don’t need to test getStaticProps, getServerSideProps themselves-test what they
+ render.
+* Don't test just for coverage. Test to prevent regressions, document intent, and handle edge cases.
+* Don't write end-to-end tests for features that change frequently unless absolutely necessary.
+
+### Jest Unit Testing Guide
+
+This guide provides guidelines and best practices for writing unit tests using Jest in this project. Following these
+standards ensures consistency, maintainability, and comprehensive test coverage.
+
+#### Best Practices
+
+1. Isolate a function route - Each test should focus on one thing that can affect the function outcome
+2. Keep tests independent - Tests should not rely on the execution order
+3. Use meaningful assertions - Assert that functions are called, what they are called with and the results
+4. Avoid testing implementation details - Focus on behavior and outputs
+5. Mock external dependencies - Isolate the unit being tested
+
+#### Running Tests
+
+```bash
+# Run all tests
+yarn test
+
+# Run specific test file
+yarn test path/to/test.unit.test.ts
+```
+
+#### Test Standards
+
+- Test file names should convey what to expect
+ - Follow the pattern: `.[unit,integration].test.ts`. Examples:
+ - filename.unit.test.ts
+ - filename.integration.test.ts
+- Group related tests using describe blocks
+- Use descriptive test names that explain the expected behavior.
+ - Follow the pattern: "should `expected behavior` [relevant modifier]". Examples:
+ - should `ban user` [with matching user id]
+ - should `ban user` [with matching user name]
+
+#### Mocking
+
+Mocking means replacing a real dependency (like a module, function, API client, timer, or browser API) with a
+controllable test double so your test can run quickly and deterministically, without calling the real thing. In unit
+tests we use mocks to isolate the unit under test; in integration tests we selectively mock only the expensive or
+unstable edges (e.g., network, filesystem) while exercising real collaborations.
+
+What to mock vs not to mock
+
+- Mock: network/HTTP calls, databases/ORM clients, email/SMS providers, time and randomness (`Date`, timers,
+ `Math.random`), browser APIs that are hard to reproduce in Node (e.g., `localStorage`, `IntersectionObserver`).
+- Prefer real: pure functions, small utilities, reducers/selectors, simple components; let their real logic run so tests
+ actually verify behavior.
+- Don’t over-mock: If you mock everything, you only test your mocks. Keep integration tests that hit real boundaries
+ inside the process.
+
+Common test doubles
+
+- Stub: a function that returns a fixed value (no assertions on how it was used).
+- Spy: records how a function was called (calls count/args); may optionally change behavior.
+- Mock: a spy with expectations about how it must be called; in Jest, `jest.fn()` and `jest.spyOn()` produce mock
+ functions you can assert on.
+- Fake: a lightweight in-memory implementation (e.g., an in-memory repo) used instead of the real service.
+
+Jest quick reference
+
+- Module mock: `jest.mock('path/to/module')` to replace all exports with mock functions. Control behavior with
+ `(exportedFn as jest.Mock).mockReturnValue(...)` or `.mockResolvedValue(...)` for async.
+- Function mock: `const fn = jest.fn()`; set behavior with `.mockReturnValue`, `.mockImplementation`.
+- Spy on existing method: `const spy = jest.spyOn(obj, 'method')` and optionally `spy.mockImplementation(...)`.
+- Timers/time: `jest.useFakeTimers(); jest.setSystemTime(new Date('2024-01-01'));` and advance with
+ `jest.advanceTimersByTime(ms)`; finally `jest.useRealTimers()`.
+- Clearing: `jest.clearAllMocks()` (between tests) vs `jest.resetAllMocks()` (reset implementations) vs
+ `jest.restoreAllMocks()` (restore spied originals).
+
+When writing mocks, assert both outcome and interaction:
+
+- Outcome: what your function returned or what side-effect occurred.
+- Interaction: that dependencies were called the expected number of times and with the right arguments.
+
+Why mocking is important?
+
+- *Isolation* - Test your code independently of databases, APIs, and external systems. Tests only fail when your code
+ breaks, not when a server is down.
+- *Speed* - Mocked tests run in milliseconds vs. seconds for real network/database calls. Run your suite constantly
+ without waiting.
+- *Control* - Easily simulate edge cases like API errors, timeouts, or rare conditions that are difficult to reproduce
+ with real systems.
+- *Reliability* - Eliminate unpredictable failures from network issues, rate limits, or changing external data. Same
+ inputs = same results, every time.
+- *Focus* - Verify your function's logic and how it uses its dependencies, without requiring those dependencies to
+ actually work yet.
+
+###### Use `jest.mock()`
+
+Jest automatically hoists all `jest.mock()` calls to the top of the file before imports are evaluated. To maintain
+clarity and align with best practices, explicitly place `jest.mock()` calls at the very top of the file.
+
+Modules mocked this way automatically return `undefined`, which is useful for simplifying tests. If a module or
+function’s return value isn’t used, there’s no need to mock it further.
+
+```tsx
+//Function and module mocks
+jest.mock('path/to/module');
+
+//Function and module imports
+import {functionUnderTest} from "path/to/function"
+import {module} from "path/to/module"
+
+describe('functionUnderTest', () => {
+ //Setup
+ beforeEach(() => {
+ //Run before each test
+ jest.resetAllMocks(); // Resets any mocks from previous tests
+ });
+ afterEach(() => {
+ //Run after each test
+ jest.restoreAllMocks(); // Cleans up between tests
+ });
+
+ describe('when given valid input', () => {
+ it('should describe what is being tested', async () => {
+ //Arrange: Setup test data
+ const mockData = 'test';
+
+ //Act: Execute the function under test
+ const result = myFunction(mockData);
+
+ //Assert: Verify the result
+ expect(result).toBe('expected');
+ });
+ });
+
+ describe('when an error occurs', () => {
+ //Test cases for errors
+ });
+});
+```
+
+###### Modules
+
+When mocking modules it's important to verify what was returned if applicable, the amount of times said module was
+called and what it was called with.
+
+```tsx
+//functionFile.ts
+import {module as mockedDep} from "path/to/module"
+
+export const functionUnderTest = async (param) => {
+ return await mockedDep(param);
+};
+```
+
+```tsx
+//testFile.unit.test.ts
+import {functionUnderTest} from "path/to/function";
+import {module as mockedDep} from "path/to/module";
+
+jest.mock('path/to/module');
+
+/**
+ * Inside the test case
+ * We create a mock for any information passed into the function that is being tested
+ * and if the function returns a result we create a mock to test the result
+ */
+const mockParam = "mockParam";
+const mockReturnValue = "mockModuleValue";
+
+/**
+ * use .mockResolvedValue when handling async/await modules that return values
+ * use .mockReturnValue when handling non async/await modules that return values
+ */
+describe('functionUnderTest', () => {
+ it('returns mocked module value and calls dependency correctly', async () => {
+ (mockedDep as jest.Mock).mockResolvedValue(mockReturnValue);
+
+ const result = await functionUnderTest(mockParam);
+
+ expect(result).toBe(mockReturnValue);
+ expect(mockedDep).toHaveBeenCalledTimes(1);
+ expect(mockedDep).toHaveBeenCalledWith(mockParam);
+ });
+});
+```
+
+Use namespace imports when you want to import everything a module exports under a single name.
+
+```tsx
+//moduleFile.ts
+export const module = async (param) => {
+ const value = "module"
+ return value
+};
+
+export const moduleTwo = async (param) => {
+ const value = "moduleTwo"
+ return value
+};
+```
+
+```tsx
+//functionFile.ts
+import {module, moduleTwo} from "path/to/module"
+
+export const functionUnderTest = async (param) => {
+ const mockValue = await moduleTwo(param)
+ const returnValue = await module(mockValue)
+ return returnValue;
+};
+```
+
+```tsx
+//testFile.unit.test.ts
+jest.mock('path/to/module');
+
+/**
+ * This creates an object containing all named exports from ./path/to/module
+ */
+import * as mockModule from "path/to/module"
+
+(mockModule.module as jest.Mock).mockResolvedValue(mockReturnValue);
+```
+
+When mocking modules, you can use `jest.spyOn()` instead of `jest.mock()`.
+
+- `jest.mock()` mocks the entire module, which is ideal for external dependencies like Axios or database clients.
+- `jest.spyOn()` mocks specific methods while keeping the real implementation for others. It can also be used to observe
+ how a real method is called without changing its behavior.
+ - also replaces the need to have `jest.mock()` at the top of the file.
+
+```tsx
+//testFile.unit.test.ts
+import * as mockModule from "path/to/module"
+
+//Mocking the return value of the module
+jest.spyOn(mockModule, 'module').mockResolvedValue(mockReturnValue);
+
+//Spying on the module to check functionality
+jest.spyOn(mockModule, 'module');
+
+//You can assert the module functionality with both of the above exactly like you would if you used jest.mock()
+expect(mockModule.module).toBeCalledTimes(1);
+expect(mockModule.module).toBeCalledWith(mockParam);
+```
+
+###### Dependencies
+
+Mocking dependencies allows you to test `your code’s` logic in isolation, without relying on third-party services or
+external functionality.
+
+```tsx
+//functionFile.ts
+import {dependency} from "path/to/dependency"
+
+export const functionUnderTest = async (param) => {
+ const depen = await dependency();
+ const value = depen.module();
+
+ return value;
+};
+```
+
+```tsx
+//testFile.unit.test.ts
+jest.mock('path/to/dependency');
+
+import {dependency} from "path/to/dependency"
+
+describe('functionUnderTest', () => {
+ /**
+ * Because the dependency has modules that are used we need to
+ * create a variable outside of scope that can be asserted on
+ */
+ let mockDependency = {} as any;
+ beforeEach(() => {
+ mockDependency = {
+ module: jest.fn(),
+ };
+ jest.resetAllMocks(); // Resets any mocks from previous tests
+ });
+ afterEach(() => {
+ //Run after each test
+ jest.restoreAllMocks(); // Cleans up between tests
+ });
+
+ //Inside the test case
+ (mockDependency.module as jest.Mock).mockResolvedValue(mockReturnValue);
+
+ expect(mockDependency.module).toBeCalledTimes(1);
+ expect(mockDependency.module).toBeCalledWith(mockParam);
+});
+```
+
+###### Error checking
+
+```tsx
+//function.ts
+const result = await functionName(param);
+
+if (!result) {
+ throw new Error(403, 'Error text', error);
+}
+;
+```
+
+```tsx
+//testFile.unit.test.ts
+const mockParam = {} as any;
+
+//This will check only the error message
+expect(functionName(mockParam))
+ .rejects
+ .toThrowError('Error text');
+
+//This will check the complete error
+try {
+ await functionName(mockParam);
+ fail('Should have thrown');
+} catch (error) {
+ const functionError = error as Error;
+ expect(functionError.code).toBe(403);
+ expect(functionError.message).toBe('Error text');
+ expect(functionError.details).toBe(mockParam);
+ expect(functionError.name).toBe('Error');
+}
+```
+
+```tsx
+//For console.error types
+console.error('Error message', error);
+
+//Use spyOn to mock
+const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {
+});
+
+expect(errorSpy).toHaveBeenCalledWith(
+ 'Error message',
+ expect.objectContaining({name: 'Error'}) //The error 'name' refers to the error type
+);
+
+```
+
+###### Mocking array return value
+
+```tsx
+//arrayFile.ts
+const exampleArray = [1, 2, 3, 4, 5];
+
+const arrayResult = exampleArray.includes(2);
+```
+
+```tsx
+//testFile.unit.test.ts
+
+//This will mock 'includes' for all arrays and force the return value to be true
+jest.spyOn(Array.prototype, 'includes').mockReturnValue(true);
+
+// ---
+//This will specify which 'includes' array to mock based on the args passed into the .includes()
+jest.spyOn(Array.prototype, 'includes').mockImplementation(function (value) {
+ if (value === 2) {
+ return true;
+ }
+ return false;
+});
+```
+
+### Playwright (E2E) Testing Guide
+
+##### Usage
+
+```shell
+# Run all tests
+yarn test:e2e
+
+# Run with UI
+yarn test:e2e:ui
+
+# Run specific test file
+yarn test:e2e tests/e2e/auth.spec.ts
+
+# Reset test database
+yarn test:db:reset
+```
+
+##### Component Selection Hierarchy
+
+Use this priority order for selecting elements in Playwright tests:
+
+1. Prefer `getByRole()` — use semantic roles that reflect how users interact
+ ```typescript
+ await page.getByRole('button', { name: 'Submit' }).click();
+ ```
+ If a meaningful ARIA role is not available, fall back to accessible text selectors (next point).
+
+2. Use accessible text selectors — when roles don't apply, target user-facing text
+ ```typescript
+ await page.getByLabel('Email').fill('user@example.com');
+ await page.getByPlaceholder('Enter your name').fill('John');
+ await page.getByText('Welcome back').isVisible();
+ ```
+
+3. Only use `data-testid` — when elements have no stable user-facing text
+ ```typescript
+ // For icons, toggles, or dynamic content without text
+ await page.getByTestId('menu-toggle').click();
+ await page.getByTestId('loading-spinner').isVisible();
+ ```
+
+This hierarchy mirrors how users actually interact with your application, making tests more reliable and meaningful.
+
+
+[](https://github.com/CompassConnections/Compass/actions/workflows/cd.yml)
+[](https://github.com/CompassConnections/Compass/actions/workflows/cd-api.yml)
+[](https://github.com/CompassConnections/Compass/actions/workflows/ci.yml)
+[](https://codecov.io/gh/CompassConnections/Compass)
+[](https://www.compassmeet.com/stats)
+
+# Compass
+
+This repository contains the source code for [Compass](https://compassmeet.com) — a transparent platform for forming
+deep, authentic 1-on-1 connections with clarity and efficiency.
+
+## Features
+
+- Extremely detailed profiles for deep connections
+- Radically transparent: user base fully searchable
+- Free, ad-free, not for profit (supported by donations)
+- Created, hosted, maintained, and moderated by volunteers
+- Open source
+- Democratically governed
+
+You can find a lot of interesting info in the [About page](https://www.compassmeet.com/about) and
+the [FAQ](https://www.compassmeet.com/faq) as well.
+A detailed description of the early vision is also available in
+this [blog post](https://martinbraquet.com/meeting-rational) (you can disregard the parts about rationality, as Compass
+shifted to a more general audience).
+
+**We can’t do this alone.** Whatever your skills—coding, design, writing, moderation, marketing, or even small
+donations—you can make a real difference. [Contribute](https://www.compassmeet.com/support) in any way you can and help
+our community thrive!
+
+
+
+## To Do
+
+No contribution is too small—whether it’s changing a color, resizing a button, tweaking a font, or improving wording.
+Bigger contributions like adding new profile fields, building modules, or improving onboarding are equally welcome. The
+goal is to make the platform better step by step, and every improvement counts. If you see something that could be
+clearer, smoother, or more engaging, **please jump in**!
+
+The complete, official list of tasks is
+available [here on ClickUp](https://sharing.clickup.com/90181043445/l/h/6-901810339879-1/bbfd32f4f4bf64b). If you are
+working on one task, just assign it to yourself and move its status to "in progress". If there is also a GitHub issue
+for that task, assign it to yourself as well.
+
+To have edit access to the ClickUp workspace, you need an admin to manually give you permission (one time thing). To do
+so, use your preferred option:
+
+- Ask or DM an admin on [Discord](https://discord.gg/8Vd7jzqjun)
+- Email hello@compassmeet.com
+- Raise an issue on GitHub
+
+If you want to add tasks without creating an account, you can simply email
+
+```
+a.t.901810339879.u-276866260.b847aba1-2709-4f17-b4dc-565a6967c234@tasks.clickup.com
+```
+
+Put the task title in the email subject and the task description in the email content.
+
+Here is a tailored selection of things that would be very useful. If you want to help but don’t know where to start,
+just ask us on [Discord](https://discord.gg/8Vd7jzqjun).
+
+- [x] Authentication (user/password and Google Sign In)
+- [x] Set up PostgreSQL in Production with supabase
+- [x] Set up web hosting (vercel)
+- [x] Set up backend hosting (google cloud)
+- [x] Ask for detailed info upon registration (location, desired type of connection, prompt answers, gender, etc.)
+- [x] Set up page listing all the profiles
+- [x] Search through most profile variables
+- [x] Set up chat / direct messaging
+- [x] Set up domain name (compassmeet.com)
+- [ ] Cover more than 90% with tests (unit, integration, e2e)
+- [x] Add Android mobile app
+- [ ] Add iOS mobile app
+- [x] Add better onboarding (tooltips, modals, etc.)
+- [ ] Add modules to learn more about each other (personality test, conflict style, love languages, etc.)
+- [ ] Add modules to improve interpersonal skills (active listening, nonviolent communication, etc.)
+- [ ] Add calendar integration and scheduling
+- [ ] Add events (group calls, in-person meetups, etc.)
+
+#### Secondary To Do
+
+Everything is open to anyone for collaboration, but the following ones are particularly easy to do for first-time
+contributors.
+
+- [x] Clean up learn more page
+- [x] Add dark theme
+- [x] Add profile fields (intellectual interests, cause areas, personality type, etc.)
+- [ ] Add profile fields: conflict style
+- [ ] Add profile fields: timezone
+- [ ] Add translations: Italian, Dutch, Hindi, Chinese, etc.
+- [x] Add filters to search through remaining profile fields (politics, religion, education level, etc.)
+- [ ] Make the app more user-friendly and appealing (UI/UX)
+- [ ] Clean up terms and conditions (convert to Markdown)
+- [ ] Clean up privacy notice (convert to Markdown)
+- [ ] Add other authentication methods (GitHub, Facebook, Apple, phone, etc.)
+- [x] Add email verification
+- [x] Add password reset
+- [x] Add automated welcome email
+- [ ] Security audit and penetration testing
+- [x] Make `deploy-api.sh` run automatically on push to `main` branch
+- [x] Create settings page (change email, password, delete account, etc.)
+- [ ] Improve [financials](web/public/md/financials.md) page (donor / acknowledgments, etc.)
+- [x] Improve loading sign (e.g., animation of a compass moving around)
+- [x] Show compatibility score in profile page
+
+## Implementation
+
+The web app is coded in Typescript using React as front-end. It includes:
+
+- [Supabase](https://supabase.com/) for the PostgreSQL database
+- [Google Cloud](https://console.cloud.google.com) for hosting the backend API
+- [Firebase](https://firebase.google.com/) for authentication and media storage
+- [Vercel](https://vercel.com/) for hosting the front-end
+
+## Development
+
+Below are the steps to contribute. If you have any trouble or questions, please don't hesitate to open an issue or
+contact us on [Discord](https://discord.gg/8Vd7jzqjun)! We're responsive and happy to help.
+
+### Installation
+
+Fork the [repo](https://github.com/CompassConnections/Compass) on GitHub (button in top right). Then, clone your repo
+and navigating into it:
+
+```bash
+git clone https://github.com//Compass.git
+cd Compass
+```
+
+Install `yarn` (if not already installed):
+
+```bash
+npm install --global yarn
+```
+
+Then, install the dependencies for this project:
+
+```bash
+yarn install
+```
+
+### Tests
+
+Make sure the tests pass:
+
+```bash
+yarn test
+```
+
+If they don't and you can't find out why, simply raise an issue! Sometimes it's something on our end that we overlooked.
+
+### Running the Development Server
+
+Start the development server:
+
+```bash
+yarn dev
+```
+
+Once the server is running, visit http://localhost:3000 to start using the app. You can sign up and visit the profiles;
+you should see a few synthetic profiles.
+
+Note: it's normal if page loading locally is much slower than the deployed version. It can take up to 10 seconds, it
+would be great to improve that though!
+
+##### Full isolation
+
+`yarn dev` runs the app locally but uses the data from a shared remote database (Supabase) and authentication (
+Firebase).
+If you want to avoid any conflict / break or simply have it run faster, run the app in full isolation locally:
+
+```bash
+yarn test:db:reset # reset your local supabase
+yarn isolated
+```
+
+### Contributing
+
+Now you can start contributing by making changes and submitting pull requests!
+
+We recommend using a good code editor (VSCode, WebStorm, Cursor, etc.) with Typescript support and a good AI assistant (
+GitHub Copilot, etc.) to make your life easier. To debug, you can use the browser developer tools (F12), specifically:
+
+- Components tab to see the React component tree and props (you need to install
+ the [React Developer Tools](https://react.dev/learn/react-developer-tools) extension)
+- Console tab for errors and logs
+- Network tab to see the requests and responses
+- Storage tab to see cookies and local storage
+
+You can also add `console.log()` statements in the code.
+
+If you are new to Typescript or the open-source space, you could start with small changes, such as tweaking some web
+components or improving wording in some pages. You can find those files in `web/public/md/`.
+
+##### Resources
+
+There is a lof of documentation in the [docs](docs) folder and across the repo, namely:
+
+- [Next.js.md](docs/Next.js.md) for core fundamentals about our web / page-rendering framework.
+- [knowledge.md](docs/knowledge.md) for general information about the project structure.
+- [development.md](docs/development.md) for additional instructions, such as adding new profile fields or languages.
+- [TESTING.md](docs/TESTING.md) for how to write tests.
+- [web](web) for the web.
+- [backend/api](backend/api) for the backend API.
+- [android](android) for the Android app.
+
+There are a lot of useful scripts you can use in the [scripts](scripts) folder.
+
+### Submission
+
+Add the original repo as upstream for syncing:
+
+```bash
+git remote add upstream https://github.com/CompassConnections/Compass.git
+```
+
+Create a new branch for your changes:
+
+```bash
+git checkout -b
+```
+
+Make changes, then stage and commit:
+
+```bash
+git add .
+git commit -m "Describe your changes"
+```
+
+Push branch to your fork:
+
+```bash
+git push origin
+```
+
+Finally, open a Pull Request on GitHub from your `fork/` → `CompassConnections/Compass` main branch.
+
+### Environment Variables
+
+Almost all the features will work out of the box, so you can skip this step and come back later if you need to test the
+following services: email, geolocation.
+
+We can't make the following information public, for security and privacy reasons:
+
+- Database, otherwise anyone could access all the user data (including private messages)
+- Firebase, otherwise anyone could remove users or modify the media files
+- Email, analytics, and location services, otherwise anyone could use the service plans Compass paid for and run up the
+ bill.
+
+That's why we separate all those services between production and development environments, so that you can code freely
+without impacting the functioning of the deployed platform.
+Contributors should use the default keys for local development. Production uses a separate environment with stricter
+rules and private keys that are not shared.
+
+If you do need one of the few remaining services, you need to set them up and store your own secrets as environment
+variables. To do so, simply open `.env` and fill in the variables according to the instructions in the file.
diff --git a/README.md b/README.md
index 9b64002..091e7de 100644
--- a/README.md
+++ b/README.md
@@ -138,6 +138,16 @@ Once the server is running, visit http://localhost:3000 to start using the app.
Note: it's normal if page loading locally is much slower than the deployed version. It can take up to 10 seconds, it would be great to improve that though!
+##### Full isolation
+
+`yarn dev` runs the app locally but uses the data from a shared remote database (Supabase) and authentication (
+Firebase).
+If you want to avoid any conflict / break or simply have it run faster, run the app in full isolation locally:
+
+```bash
+yarn isolated
+```
+
### Contributing
Now you can start contributing by making changes and submitting pull requests!
diff --git a/backend/shared/src/supabase/init.ts b/backend/shared/src/supabase/init.ts
index c5d2d20..394019a 100644
--- a/backend/shared/src/supabase/init.ts
+++ b/backend/shared/src/supabase/init.ts
@@ -51,19 +51,36 @@ const newClient = (
) => {
const {instanceId, password, ...settings} = props
- const config = {
- // This host is IPV4 compatible, for the google cloud VM
- host: 'aws-1-us-west-1.pooler.supabase.com',
- port: 5432,
- user: `postgres.${instanceId}`,
- password: password,
- database: 'postgres',
- pool_mode: 'session',
- ssl: {rejectUnauthorized: false},
- family: 4, // <- forces IPv4
- ...settings,
+ // If DATABASE_URL is provided (e.g., for E2E tests), prefer connecting directly to that Postgres instance.
+ const databaseUrl = process.env.DATABASE_URL
+ if (process.env.DATABASE_URL) {
+ console.log('Creating direct Postgres client (DATABASE_URL)')
+ } else {
+ console.log('Creating Supabase direct client')
}
+ const config: any = databaseUrl
+ ? {
+ // Use connection string for local/dev Postgres
+ connectionString: databaseUrl,
+ // Local Postgres typically doesn't need SSL
+ ssl: false,
+ ...settings,
+ }
+ : {
+ // Default: connect to Supabase's pooled Postgres
+ // This host is IPV4 compatible, for the google cloud VM
+ host: 'aws-1-us-west-1.pooler.supabase.com',
+ port: 5432,
+ user: `postgres.${instanceId}`,
+ password: password,
+ database: 'postgres',
+ pool_mode: 'session',
+ ssl: {rejectUnauthorized: false},
+ family: 4, // <- forces IPv4
+ ...settings,
+ }
+
// console.debug(config)
return pgp(config)
@@ -77,15 +94,16 @@ export function createSupabaseDirectClient(
password?: string
) {
if (pgpDirect) return pgpDirect
- console.log('Creating Supabase direct client')
+ const hasDatabaseUrl = !!process.env.DATABASE_URL
+ // Only enforce Supabase credentials when not using DATABASE_URL
instanceId = instanceId ?? getInstanceId()
- if (!instanceId) {
+ if (!hasDatabaseUrl && !instanceId) {
throw new Error(
"Can't connect to Supabase; no process.env.SUPABASE_INSTANCE_ID and no instance ID in config."
)
}
password = password ?? getSupabasePwd()
- if (!password) {
+ if (!hasDatabaseUrl && !password) {
throw new Error(
"Can't connect to Supabase; no process.env.SUPABASE_DB_PASSWORD."
)
diff --git a/backend/supabase/extensions.sql b/backend/supabase/extensions.sql
new file mode 100644
index 0000000..04ccd51
--- /dev/null
+++ b/backend/supabase/extensions.sql
@@ -0,0 +1,28 @@
+-- Required PostgreSQL extensions
+CREATE EXTENSION IF NOT EXISTS pg_trgm; -- Trigram matching for text search
+CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; -- UUID generation (if needed)
+CREATE EXTENSION IF NOT EXISTS btree_gin;
+-- Additional GIN operators (if needed)
+
+-- Supabase roles
+DO
+$$
+ BEGIN
+ IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'anon') THEN
+ CREATE ROLE anon NOLOGIN;
+ END IF;
+
+ IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'authenticated') THEN
+ CREATE ROLE authenticated NOLOGIN;
+ END IF;
+
+ IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'service_role') THEN
+ CREATE ROLE service_role NOLOGIN;
+ END IF;
+ END
+$$;
+
+-- Grant roles to test_user so it can grant permissions
+-- GRANT anon TO test_user;
+-- GRANT authenticated TO test_user;
+-- GRANT service_role TO test_user;
\ No newline at end of file
diff --git a/backend/supabase/functions.sql b/backend/supabase/functions.sql
index abb2405..7b07b2e 100644
--- a/backend/supabase/functions.sql
+++ b/backend/supabase/functions.sql
@@ -55,17 +55,6 @@ BEGIN
END;
$function$;
-create
-or replace function public.can_access_private_messages (channel_id bigint, user_id text) returns boolean language sql parallel SAFE as $function$
-select exists (
- select 1 from private_user_message_channel_members
- where private_user_message_channel_members.channel_id = $1
- and private_user_message_channel_members.user_id = $2
-)
-$function$;
-
-
-
create
or replace function public.get_average_rating (user_id text) returns numeric language plpgsql as $function$
DECLARE
diff --git a/backend/supabase/migration.sql b/backend/supabase/migration.sql
index 46e8917..de8c548 100644
--- a/backend/supabase/migration.sql
+++ b/backend/supabase/migration.sql
@@ -1,9 +1,10 @@
BEGIN;
+\i backend/supabase/extensions.sql
\i backend/supabase/rebuild_profile_search.sql
\i backend/supabase/functions.sql
\i backend/supabase/firebase.sql
-\i backend/supabase/profiles.sql
\i backend/supabase/users.sql
+\i backend/supabase/profiles.sql
\i backend/supabase/private_user_message_channels.sql
\i backend/supabase/private_user_message_channel_members.sql
\i backend/supabase/private_users.sql
@@ -23,4 +24,26 @@ BEGIN;
\i backend/supabase/functions_others.sql
\i backend/supabase/reports.sql
\i backend/supabase/bookmarked_searches.sql
+\i backend/supabase/causes.sql
+\i backend/supabase/causes_translations.sql
+\i backend/supabase/contact.sql
+\i backend/supabase/hidden_profiles.sql
+\i backend/supabase/interests.sql
+\i backend/supabase/interests_translations.sql
+\i backend/supabase/push_subscriptions.sql
+\i backend/supabase/push_subscriptions_mobile.sql
+\i backend/supabase/temp_users.sql
+\i backend/supabase/user_activity.sql
+\i backend/supabase/votes.sql
+\i backend/supabase/vote_results.sql
+\i backend/supabase/work.sql
+\i backend/supabase/work_translations.sql
+\i backend/supabase/profile_causes.sql
+\i backend/supabase/profile_interests.sql
+\i backend/supabase/profile_work.sql
+\i backend/supabase/compatibility_prompts_translations.sql
+\i backend/supabase/migrations/20251106_add_message_actions.sql
+\i backend/supabase/migrations/20251110_add_languages_to_profiles.sql
+\i backend/supabase/migrations/20251112_add_mbti_to_profiles.sql
+\i backend/supabase/migrations/20260213_add_big_5_to_profiles.sql
COMMIT;
diff --git a/backend/supabase/migrations/.keep b/backend/supabase/migrations/.keep
deleted file mode 100644
index e69de29..0000000
diff --git a/backend/supabase/migrations/20251110_add_languages_to_profiles.sql b/backend/supabase/migrations/20251110_add_languages_to_profiles.sql
index bae3d8f..7d2074e 100644
--- a/backend/supabase/migrations/20251110_add_languages_to_profiles.sql
+++ b/backend/supabase/migrations/20251110_add_languages_to_profiles.sql
@@ -3,4 +3,4 @@ ALTER TABLE profiles
ADD COLUMN IF NOT EXISTS languages TEXT[] null;
-- Create GIN index for array operations
-CREATE INDEX IF NOT EXISTS idx_profiles_languages ON profiles USING GIN (languages);
+CREATE INDEX IF NOT EXISTS idx_profiles_languages ON profiles USING GIN (languages);
\ No newline at end of file
diff --git a/backend/supabase/private_user_message_channel_members.sql b/backend/supabase/private_user_message_channel_members.sql
index 1825c52..c686b84 100644
--- a/backend/supabase/private_user_message_channel_members.sql
+++ b/backend/supabase/private_user_message_channel_members.sql
@@ -43,3 +43,15 @@ CREATE INDEX IF NOT EXISTS pumcm_members_idx
CREATE UNIQUE INDEX IF NOT EXISTS unique_user_channel
ON public.private_user_message_channel_members
USING btree (channel_id, user_id);
+
+-- Functions
+create
+ or replace function public.can_access_private_messages(channel_id bigint, user_id text) returns boolean
+ language sql
+ parallel SAFE as
+$function$
+select exists (select 1
+ from private_user_message_channel_members
+ where private_user_message_channel_members.channel_id = $1
+ and private_user_message_channel_members.user_id = $2)
+$function$;
diff --git a/backend/supabase/profiles.sql b/backend/supabase/profiles.sql
index 4cc4f82..2e7cc19 100644
--- a/backend/supabase/profiles.sql
+++ b/backend/supabase/profiles.sql
@@ -10,7 +10,7 @@ CREATE TABLE IF NOT EXISTS profiles (
bio JSONB,
bio_length integer null,
born_in_location TEXT,
- city TEXT NOT NULL,
+ city TEXT,
city_latitude NUMERIC(9, 6),
city_longitude NUMERIC(9, 6),
comments_enabled BOOLEAN DEFAULT TRUE NOT NULL,
@@ -22,7 +22,7 @@ CREATE TABLE IF NOT EXISTS profiles (
drinks_per_month INTEGER,
education_level TEXT,
ethnicity TEXT[],
- gender TEXT NOT NULL,
+ gender TEXT,
geodb_city_id TEXT,
has_kids INTEGER,
height_in_inches float4,
@@ -40,8 +40,8 @@ CREATE TABLE IF NOT EXISTS profiles (
political_details TEXT,
pref_age_max INTEGER NULL,
pref_age_min INTEGER NULL,
- pref_gender TEXT[] NOT NULL,
- pref_relation_styles TEXT[] NOT NULL,
+ pref_gender TEXT[],
+ pref_relation_styles TEXT[],
pref_romantic_styles TEXT[],
referred_by_username TEXT,
region_code TEXT,
@@ -53,7 +53,7 @@ CREATE TABLE IF NOT EXISTS profiles (
university TEXT,
user_id TEXT NOT NULL,
visibility profile_visibility DEFAULT 'member'::profile_visibility NOT NULL,
- wants_kids_strength INTEGER DEFAULT 0 NOT NULL,
+ wants_kids_strength INTEGER DEFAULT 0,
website TEXT,
CONSTRAINT profiles_pkey PRIMARY KEY (id)
);
@@ -102,7 +102,6 @@ CREATE INDEX profiles_pref_romantic_styles_gin ON profiles USING GIN (pref_roman
CREATE INDEX profiles_diet_gin ON profiles USING GIN (diet);
CREATE INDEX profiles_political_beliefs_gin ON profiles USING GIN (political_beliefs);
CREATE INDEX profiles_relationship_status_gin ON profiles USING GIN (relationship_status);
-CREATE INDEX profiles_languages_gin ON profiles USING GIN (languages);
CREATE INDEX profiles_religion_gin ON profiles USING GIN (religion);
CREATE INDEX profiles_ethnicity_gin ON profiles USING GIN (ethnicity);
diff --git a/common/src/supabase/utils.ts b/common/src/supabase/utils.ts
index 70fab2d..15eeb2d 100644
--- a/common/src/supabase/utils.ts
+++ b/common/src/supabase/utils.ts
@@ -25,11 +25,12 @@ export type Column = keyof Row & string
export type SupabaseClient = SupabaseClientGeneric
export function createClient(
- instanceId: string,
+ instanceIdOrUrl: string,
key: string,
opts?: SupabaseClientOptionsGeneric<'public'>
) {
- const url = `https://${instanceId}.supabase.co`
+ // Allow passing a full Supabase URL directly (e.g., http://localhost:54321)
+ const url = /:\/\//.test(instanceIdOrUrl) ? instanceIdOrUrl : `https://${instanceIdOrUrl}.supabase.co`
// console.debug('createClient', instanceId, key, opts)
return createClientGeneric(
url,
diff --git a/docs/TESTING.md b/docs/TESTING.md
index 7483fb9..28dfbae 100644
--- a/docs/TESTING.md
+++ b/docs/TESTING.md
@@ -478,6 +478,22 @@ jest.spyOn(Array.prototype, 'includes').mockImplementation(function(value) {
### Playwright (E2E) Testing Guide
+##### Usage
+
+```shell
+# Run all tests
+yarn test:e2e
+
+# Run with UI
+yarn test:e2e:ui
+
+# Run specific test file
+yarn test:e2e tests/e2e/auth.spec.ts
+
+# Reset test database
+yarn test:db:reset
+```
+
##### Component Selection Hierarchy
Use this priority order for selecting elements in Playwright tests:
diff --git a/package.json b/package.json
index c900d10..728f2eb 100644
--- a/package.json
+++ b/package.json
@@ -15,6 +15,7 @@
"lint": "yarn --cwd=web lint-fix; eslint common --fix ; eslint backend/api --fix ; eslint backend/shared --fix",
"dev": "./scripts/run_local.sh dev",
"prod": "./scripts/run_local.sh prod",
+ "isolated": "./scripts/run_local_isolated.sh",
"clean-install": "./scripts/install.sh",
"build-web-view": "./scripts/build_web_view.sh",
"build-sync-android": "./scripts/build_sync_android.sh",
@@ -26,6 +27,11 @@
"test:watch": "yarn workspaces run test --watch",
"test:update": "yarn workspaces run test --updateSnapshot",
"test:e2e": "./scripts/e2e.sh",
+ "test:e2e:ui": "./scripts/e2e.sh --ui",
+ "test:e2e:debug": "./scripts/e2e.sh --debug",
+ "test:db:reset": "supabase start && ./scripts/combine-migrations.sh && supabase db reset",
+ "test:db:reset-postgres": "docker compose -f scripts/docker-compose.test.yml down -v && docker compose -f scripts/docker-compose.test.yml up -d",
+ "test:db:migrate": "./scripts/test_db_migration.sh",
"playwright": "playwright test",
"playwright:ui": "playwright test --ui",
"playwright:debug": "playwright test --debug",
@@ -41,38 +47,40 @@
"@capacitor/status-bar": "7.0.3",
"@capawesome/capacitor-live-update": "7.2.2",
"@capgo/capacitor-social-login": "7.14.9",
- "@playwright/test": "^1.54.2",
- "colorette": "^2.0.20",
- "prismjs": "^1.30.0",
+ "@playwright/test": "1.55.0",
+ "colorette": "2.0.20",
+ "prismjs": "1.30.0",
"react": "18.2.0",
"react-dom": "18.2.0",
- "react-markdown": "*"
+ "react-markdown": "10.1.0",
+ "supabase": "2.76.9",
+ "wait-on": "9.0.4"
},
"devDependencies": {
"@capacitor/android": "7.4.4",
"@capacitor/assets": "3.0.5",
"@capacitor/cli": "7.4.4",
"@faker-js/faker": "10.1.0",
- "@testing-library/dom": "^10.0.0",
- "@testing-library/jest-dom": "^6.6.4",
- "@testing-library/react": "^16.3.0",
- "@testing-library/user-event": "^14.6.1",
+ "@testing-library/dom": "10.4.1",
+ "@testing-library/jest-dom": "6.8.0",
+ "@testing-library/react": "16.3.0",
+ "@testing-library/user-event": "14.6.1",
"@types/jest": "29.2.4",
"@types/node": "20.12.11",
"@typescript-eslint/eslint-plugin": "7.4.0",
"@typescript-eslint/parser": "7.4.0",
"chalk": "5.6.2",
"concurrently": "8.2.2",
- "dotenv-cli": "^10.0.0",
- "eslint": "8.57.0",
- "eslint-plugin-lodash": "^7.4.0",
+ "dotenv-cli": "10.0.0",
+ "eslint": "10.0.0",
+ "eslint-plugin-lodash": "7.4.0",
"eslint-plugin-unused-imports": "4.1.4",
- "firebase-tools": "^14.26.0",
+ "firebase-tools": "14.27.0",
"jest": "29.3.1",
"nodemon": "2.0.20",
"prettier": "3.6.2",
"prettier-plugin-sql": "0.19.2",
- "prettier-plugin-tailwindcss": "^0.2.1",
+ "prettier-plugin-tailwindcss": "0.2.8",
"ts-jest": "29.0.3",
"ts-node": "10.9.1",
"tsc-alias": "1.8.2",
diff --git a/scripts/combine-migrations.sh b/scripts/combine-migrations.sh
new file mode 100755
index 0000000..0f43c23
--- /dev/null
+++ b/scripts/combine-migrations.sh
@@ -0,0 +1,85 @@
+#!/bin/bash
+
+set -euo pipefail
+
+# Change to project root
+cd "$(dirname "$0")"/..
+
+echo "📦 Copying migrations from backend/supabase/ to supabase/migrations/"
+echo ""
+
+# Create migrations directory if it doesn't exist
+mkdir -p supabase/migrations
+
+# Read migration.sql and extract all \i commands
+if [ ! -f "backend/supabase/migration.sql" ]; then
+ echo "❌ Error: backend/supabase/migration.sql not found"
+ exit 1
+fi
+
+# Extract file paths from \i commands
+FILES=$(grep '\\i ' backend/supabase/migration.sql | sed 's/\\i //' | sed 's/;//' | tr -d '\r')
+
+# Starting timestamp (you can adjust this)
+TIMESTAMP=20260101000000
+COUNTER=0
+
+echo "Files to copy:"
+echo "----------------------------------------"
+
+# Copy each file with timestamp
+while IFS= read -r file; do
+ # Remove leading/trailing whitespace
+ file=$(echo "$file" | xargs)
+
+ if [ -z "$file" ]; then
+ continue
+ fi
+
+ if [ ! -f "$file" ]; then
+ echo "⚠️ Warning: $file not found, skipping..."
+ continue
+ fi
+
+ # Calculate timestamp (increment by 1 minute for each file)
+ CURRENT_TIMESTAMP=$((TIMESTAMP + COUNTER))
+
+ # Get filename without path
+ BASENAME=$(basename "$file")
+
+ # Create descriptive name from path
+ # backend/supabase/users.sql -> users
+ # backend/supabase/migrations/20251106_add_message_actions.sql -> add_message_actions
+ if [[ "$file" == *"/migrations/"* ]]; then
+ # Already has a migration name
+ NAME=$(echo "$BASENAME" | sed 's/^[0-9_]*//;s/\.sql$//')
+ else
+ NAME=$(echo "$BASENAME" | sed 's/\.sql$//')
+ fi
+
+ # Output filename
+ OUTPUT="supabase/migrations/${CURRENT_TIMESTAMP}_${NAME}.sql"
+
+ # Add header comment to track source
+ {
+ echo "-- Migration: $NAME"
+ echo "-- Source: $file"
+ echo "-- Timestamp: $(date)"
+ echo ""
+ cat "$file"
+ } > "$OUTPUT"
+
+ echo "✅ $file -> $OUTPUT"
+
+ COUNTER=$((COUNTER + 100))
+done <<< "$FILES"
+
+echo ""
+echo "----------------------------------------"
+echo "✅ Migration files copied to supabase/migrations/"
+echo ""
+echo "To apply migrations:"
+echo " supabase db reset"
+echo ""
+echo "To view in Studio:"
+echo " open http://127.0.0.1:54323"
\ No newline at end of file
diff --git a/scripts/docker-compose.test.yml b/scripts/docker-compose.test.yml
new file mode 100644
index 0000000..896a03a
--- /dev/null
+++ b/scripts/docker-compose.test.yml
@@ -0,0 +1,19 @@
+services:
+ postgres-test:
+ image: postgres:15-alpine
+ environment:
+ POSTGRES_DB: test_db
+ POSTGRES_USER: test_user
+ POSTGRES_PASSWORD: test_password
+ ports:
+ - "5433:5432"
+ volumes:
+ - postgres-test-data:/var/lib/postgresql/data
+ healthcheck:
+ test: [ "CMD-SHELL", "pg_isready -U test_user -d test_db" ]
+ interval: 5s
+ timeout: 5s
+ retries: 5
+
+volumes:
+ postgres-test-data:
\ No newline at end of file
diff --git a/scripts/e2e.sh b/scripts/e2e.sh
index 66182b0..98e12f7 100755
--- a/scripts/e2e.sh
+++ b/scripts/e2e.sh
@@ -2,41 +2,132 @@
set -euo pipefail
+# Colors for output
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+NC='\033[0m' # No Color
+
+# Function to print colored output
+print_status() {
+ echo -e "${GREEN}[E2E]${NC} $1"
+}
+
+print_error() {
+ echo -e "${RED}[ERROR]${NC} $1"
+}
+
+print_warning() {
+ echo -e "${YELLOW}[WARN]${NC} $1"
+}
+
+# Array to track background process PIDs
+PIDS=()
+
# Function to clean up background processes
cleanup() {
- echo "Stopping background processes..."
+ print_status "Cleaning up..."
+
+ # Kill all background processes
for pid in "${PIDS[@]:-}"; do
if kill -0 "$pid" 2>/dev/null; then
- kill "$pid" || true
+ kill "$pid" 2>/dev/null || true
wait "$pid" 2>/dev/null || true
- echo "Killed PID $pid"
fi
done
+
+ # Stop Docker containers
+ if [ "${SKIP_DB_CLEANUP:-}" != "true" ]; then
+ print_status "Stopping test database..."
+ docker compose -f scripts/docker-compose.test.yml down -v
+ fi
+
+ print_status "Cleanup complete"
}
# Trap EXIT, INT, TERM to run cleanup automatically
trap cleanup EXIT INT TERM
+# Change to project root
cd "$(dirname "$0")"/..
-npx playwright install chromium
+# Load test environment variables
+if [ -f .env.test ]; then
+ export $(cat .env.test | grep -v '^#' | xargs)
+fi
-export NEXT_PUBLIC_API_URL=localhost:8088
-export NEXT_PUBLIC_FIREBASE_ENV=DEV
-export NEXT_PUBLIC_FIREBASE_EMULATOR=true
-export FIREBASE_AUTH_EMULATOR_HOST=127.0.0.1:9099
-export FIREBASE_STORAGE_EMULATOR_HOST=127.0.0.1:9199
+# Start Supabase (includes Postgres, Auth, Storage, etc.)
+supabase start
-# Start servers in background and store their PIDs
-PIDS=()
-npx nyc --reporter=lcov yarn --cwd=web serve & PIDS+=($!)
-npx nyc --reporter=lcov yarn --cwd=backend/api dev & PIDS+=($!)
+# Apply migrations (if using Supabase migrations)
+./scripts/combine-migrations.sh
+supabase db reset
+
+# Get connection details
+export NEXT_PUBLIC_SUPABASE_URL=$(supabase status --output json | jq -r '.API_URL')
+export NEXT_PUBLIC_SUPABASE_ANON_KEY=$(supabase status --output json | jq -r '.ANON_KEY')
+export DATABASE_URL=$(supabase status --output json | jq -r '.DB_URL')
+
+echo $NEXT_PUBLIC_SUPABASE_URL
+echo $NEXT_PUBLIC_SUPABASE_ANON_KEY
+echo $DATABASE_URL
+
+print_status "Supabase started at: $DATABASE_URL"
+
+# Install Playwright browsers
+print_status "Installing Playwright browsers..."
+npx playwright install chromium # --with-deps
+
+# Start Firebase emulators
+print_status "Starting Firebase emulators..."
yarn emulate & PIDS+=($!)
-npx wait-on http://localhost:3000
+# Wait for emulators to be ready
+print_status "Waiting for Firebase emulators..."
+npx wait-on \
+ http-get://127.0.0.1:9099 \
+ --timeout 30000
-npx tsx scripts/setup-auth.ts
+# Build backend (required?)
+./scripts/build_api.sh
-npx playwright test tests/e2e
+# Seed test data if script exists
+if [ -f "scripts/seed-test-data.ts" ]; then
+ print_status "Seeding test data..."
+ npx tsx scripts/seed-test-data.ts
+fi
-exit ${TEST_FAILED:-0}
+# Start backend API
+print_status "Starting backend API..."
+yarn --cwd=backend/api dev & PIDS+=($!)
+
+# Wait for API to be ready
+print_status "Waiting for API..."
+npx wait-on http://localhost:8088/health --timeout 30000 || {
+ print_error "API failed to start"
+ exit 1
+}
+
+# Start Next.js app
+print_status "Starting Next.js app..."
+yarn --cwd=web dev & PIDS+=($!)
+
+# Wait for Next.js to be ready
+print_status "Waiting for Next.js..."
+npx wait-on http://localhost:3000 --timeout 60000 || {
+ print_error "Next.js failed to start"
+ exit 1
+}
+
+# Run Playwright tests
+print_status "Running Playwright tests..."
+TEST_FAILED=0
+npx playwright test tests/e2e "$@" || TEST_FAILED=$?
+
+if [ $TEST_FAILED -eq 0 ]; then
+ print_status "${GREEN}All tests passed!${NC}"
+else
+ print_error "Some tests failed (exit code: $TEST_FAILED)"
+fi
+
+exit $TEST_FAILED
\ No newline at end of file
diff --git a/scripts/run_local_emulated.sh b/scripts/run_local_emulated.sh
deleted file mode 100755
index 68e2188..0000000
--- a/scripts/run_local_emulated.sh
+++ /dev/null
@@ -1,62 +0,0 @@
-#!/bin/bash
-
-# What runs on each port?
-# - 4000: Firebase emulator
-# - 3000: Front end
-# - 8088: Back end
-
-# How to view users? Each user is stored in two locations for two different purposes:
-# In the auth system (firebase emulator) to see the auth info (email, provider, etc.): http://127.0.0.1:4000/auth
-# In the database (dev supabase project, users and private_users table) to see the user info specific to compass (username, notif preferences, etc.): use DBeaver to connect to the dev supabase db
-
-# Clean ghost processes
-kill_ghosts() {
- for p in 3000 4000 4400 4500 8088; do
- pids=$(lsof -ti :$p 2>/dev/null)
- if [ -n "$pids" ]; then
- kill $pids || true
- fi
- done
-}
-kill_ghosts
-
-set -euo pipefail
-
-# Function to clean up background processes
-cleanup() {
- echo "Stopping background processes..."
- for pid in "${PIDS[@]:-}"; do
- if kill -0 "$pid" 2>/dev/null; then
- kill "$pid" || true
- wait "$pid" 2>/dev/null || true
- echo "Killed PID $pid"
- fi
- done
- kill_ghosts
-}
-
-# Trap EXIT, INT, TERM to run cleanup automatically
-trap cleanup EXIT INT TERM
-
-cd "$(dirname "$0")"/..
-
-export NEXT_PUBLIC_API_URL=localhost:8088
-export NEXT_PUBLIC_FIREBASE_ENV=DEV
-export NEXT_PUBLIC_FIREBASE_EMULATOR=true
-export FIREBASE_AUTH_EMULATOR_HOST=127.0.0.1:9099
-export FIREBASE_STORAGE_EMULATOR_HOST=127.0.0.1:9199
-
-# Start servers in background and store their PIDs
-PIDS=()
-npx nyc --reporter=lcov yarn --cwd=web serve & PIDS+=($!)
-npx nyc --reporter=lcov yarn --cwd=backend/api dev & PIDS+=($!)
-yarn emulate & PIDS+=($!)
-
-npx wait-on http://localhost:3000
-
-# This creates a new user in firebase auth only (not in the db, hence it won't show in the list of profiles)
-npx tsx scripts/setup-auth.ts
-
-read -p "Press enter to exit..." < /dev/tty
-
-exit ${TEST_FAILED:-0}
diff --git a/scripts/run_local_isolated.sh b/scripts/run_local_isolated.sh
new file mode 100755
index 0000000..78db8b2
--- /dev/null
+++ b/scripts/run_local_isolated.sh
@@ -0,0 +1,86 @@
+#!/bin/bash
+
+# Run the web app locally in full isolation (database, storage and authentication all stored locally)
+# What runs on each port?
+# - 4000: Firebase emulator UI
+# - 9099: Firebase emulator authentication
+# - 9199: Firebase emulator storage
+# - 54323: Supabase emulator UI
+# - 54322: Supabase emulator Database (direct client)
+# - 54321: Supabase emulator Database (font-end client)
+# - 3000: Front end
+# - 8088: Back end
+
+# How to view users? Each user is stored in two locations for two different purposes:
+# In the auth system (firebase emulator) to see the auth info (email, provider, etc.): http://127.0.0.1:4000/auth
+# In the database (users and private_users table) to see the user info specific to compass (username, notif preferences, etc.): http://127.0.0.1:54323
+
+# Clean ghost processes
+kill_ghosts() {
+ for p in 3000 4000 4400 4500 8088; do
+ pids=$(lsof -ti :$p 2>/dev/null)
+ if [ -n "$pids" ]; then
+ kill $pids || true
+ fi
+ done
+}
+kill_ghosts
+
+set -euo pipefail
+
+# Function to clean up background processes
+cleanup() {
+ echo "Stopping background processes..."
+ for pid in "${PIDS[@]:-}"; do
+ if kill -0 "$pid" 2>/dev/null; then
+ kill "$pid" || true
+ wait "$pid" 2>/dev/null || true
+ echo "Killed PID $pid"
+ fi
+ done
+ kill_ghosts
+}
+
+# Trap EXIT, INT, TERM to run cleanup automatically
+trap cleanup EXIT INT TERM
+
+cd "$(dirname "$0")"/..
+
+export $(cat .env.test | grep -v '^#' | xargs)
+
+# Ensure Supabase local stack is running; if not, reset/start it
+STATUS_JSON=$(supabase status --output json 2>/dev/null || echo '')
+API_URL=$(echo "$STATUS_JSON" | jq -r '.API_URL // empty')
+
+if [ -z "$API_URL" ]; then
+ echo "Supabase is not running. Bootstrapping local stack with: yarn test:db:reset"
+ yarn test:db:reset
+ STATUS_JSON=$(supabase status --output json)
+fi
+
+export NEXT_PUBLIC_SUPABASE_URL=$(echo "$STATUS_JSON" | jq -r '.API_URL')
+export NEXT_PUBLIC_SUPABASE_ANON_KEY=$(echo "$STATUS_JSON" | jq -r '.ANON_KEY')
+export DATABASE_URL=$(echo "$STATUS_JSON" | jq -r '.DB_URL')
+
+echo $NEXT_PUBLIC_SUPABASE_URL
+echo $NEXT_PUBLIC_SUPABASE_ANON_KEY
+echo $DATABASE_URL
+
+# Start servers in background and store their PIDs
+PIDS=()
+npx nyc --reporter=lcov yarn --cwd=web serve & PIDS+=($!)
+npx nyc --reporter=lcov yarn --cwd=backend/api dev & PIDS+=($!)
+yarn emulate & PIDS+=($!)
+
+npx wait-on http://localhost:3000
+
+echo ""
+echo "✅ Isolated web app fully running and ready!"
+echo " Useful links:"
+echo " - Front end: http://127.0.0.1:3000"
+echo " - Supabase UI: http://127.0.0.1:54323"
+echo " - Firebase UI: http://127.0.0.1:4000"
+echo ""
+read -p "Press enter to exit..." < /dev/tty
+
+exit ${TEST_FAILED:-0}
diff --git a/scripts/seed-test-data.ts b/scripts/seed-test-data.ts
new file mode 100644
index 0000000..c5fb121
--- /dev/null
+++ b/scripts/seed-test-data.ts
@@ -0,0 +1,56 @@
+// TODO: add test data to firebase emulator as well (see example below, but user IDs from supabase and firebase need to the same)
+
+import {createSupabaseDirectClient} from "shared/lib/supabase/init";
+import UserAccountInformation from "../tests/e2e/backend/utils/userInformation";
+import {seedDatabase} from "../tests/e2e/utils/seedDatabase";
+
+import axios from 'axios';
+import {config} from '../tests/e2e/web/SPEC_CONFIG.js';
+
+async function createAuth(email: string, password: string) {
+ const base = 'http://localhost:9099/identitytoolkit.googleapis.com/v1';
+
+ await axios.post(`${base}/accounts:signUp?key=fake-api-key`, {
+ email: email,
+ password: password,
+ returnSecureToken: true
+ });
+
+ console.log('Auth created', config.USERS.DEV_1.EMAIL)
+
+ // TODY: retrieve real user ID from response
+ const userId = Date.now().toString()
+
+ return userId
+}
+
+// Can remove this later once we update tests/e2e/web/fixtures/signInFixture.ts
+createAuth(config.USERS.DEV_1.EMAIL, config.USERS.DEV_1.PASSWORD)
+
+
+type ProfileType = 'basic' | 'medium' | 'full'
+
+(async () => {
+ const pg = createSupabaseDirectClient()
+
+ //Edit the count seedConfig to specify the amount of each profiles to create
+ const seedConfig = [
+ {count: 1, profileType: 'basic' as ProfileType},
+ {count: 1, profileType: 'medium' as ProfileType},
+ {count: 1, profileType: 'full' as ProfileType},
+ ]
+
+ for (const {count, profileType} of seedConfig) {
+ for (let i = 0; i < count; i++) {
+ const userInfo = new UserAccountInformation()
+ userInfo.user_id = await createAuth(userInfo.email, userInfo.password)
+ if (i == 0) {
+ // Seed the first profile with deterministic data for the e2e tests
+ userInfo.name = 'Franklin Buckridge'
+ }
+ console.log('Seeded user:', userInfo)
+ await seedDatabase(pg, userInfo, profileType)
+ }
+ }
+ process.exit(0)
+})()
\ No newline at end of file
diff --git a/scripts/test_db_migration.sh b/scripts/test_db_migration.sh
new file mode 100755
index 0000000..575c6a1
--- /dev/null
+++ b/scripts/test_db_migration.sh
@@ -0,0 +1,41 @@
+#!/bin/bash
+
+set -euo pipefail
+
+cd "$(dirname "$0")/.."
+
+# Test database config (hardcoded - no .env needed)
+export DB_HOST=localhost
+export DB_PORT=5433
+export DB_USER=test_user
+export DB_NAME=test_db
+export PGPASSWORD=test_password
+
+# Build connection URL
+export DATABASE_URL="postgresql://$DB_USER:$PGPASSWORD@$DB_HOST:$DB_PORT/$DB_NAME"
+
+echo "Migrating test database: $DATABASE_URL"
+
+# Check if psql is available
+if ! command -v psql &> /dev/null; then
+ echo "Error: psql not found. Use docker exec instead."
+ echo "Running: docker exec scripts-postgres-test-1 psql ..."
+
+ # Use docker exec if psql not installed
+ docker exec -i scripts-postgres-test-1 psql -U $DB_USER -d $DB_NAME <.s3-.amazonaws.com
+s3_host = "env(S3_HOST)"
+# Configures S3 bucket region, eg. us-east-1
+s3_region = "env(S3_REGION)"
+# Configures AWS_ACCESS_KEY_ID for S3 bucket
+s3_access_key = "env(S3_ACCESS_KEY)"
+# Configures AWS_SECRET_ACCESS_KEY for S3 bucket
+s3_secret_key = "env(S3_SECRET_KEY)"
diff --git a/supabase/seed.sql b/supabase/seed.sql
new file mode 100644
index 0000000..60d05a8
--- /dev/null
+++ b/supabase/seed.sql
@@ -0,0 +1,2 @@
+-- You can seed basic stuff in the Supabase SQL database here, but most of the seeding is done in seed-test-data.ts
+-- to populate both the Firebase auth section and Supabase SQL database (as the user IDs must match between the two)
\ No newline at end of file
diff --git a/web/lib/supabase/db.ts b/web/lib/supabase/db.ts
index 0b09aef..b58a9bd 100644
--- a/web/lib/supabase/db.ts
+++ b/web/lib/supabase/db.ts
@@ -1,10 +1,26 @@
-import { createClient } from 'common/supabase/utils'
-import { ENV_CONFIG } from 'common/envs/constants'
+import {createClient} from 'common/supabase/utils'
+import {ENV_CONFIG} from 'common/envs/constants'
let currentToken: string | undefined
export function initSupabaseClient() {
- // console.debug('Initializing supabase client', ENV_CONFIG.supabaseInstanceId, ENV_CONFIG.supabaseAnonKey)
+ // Prefer explicit env overrides when available (useful for local Supabase via Docker)
+ const urlOverride = process.env.NEXT_PUBLIC_SUPABASE_URL || process.env.SUPABASE_URL
+ const anonKeyOverride = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || process.env.SUPABASE_ANON_KEY
+
+ if (urlOverride && anonKeyOverride) {
+ console.log('Initializing Supabase client (env URL override)')
+ return createClient(urlOverride, anonKeyOverride)
+ }
+ if (urlOverride || anonKeyOverride) {
+ console.warn(
+ 'Supabase env override is partially set. Both URL and ANON_KEY are required. Falling back to ENV_CONFIG.'
+ )
+ }
+
+ // Default: use instanceId and anon key from ENV_CONFIG
+ // Note: createClient accepts either instanceId or full URL
+ // console.debug('Initializing Supabase client', ENV_CONFIG.supabaseInstanceId)
return createClient(ENV_CONFIG.supabaseInstanceId, ENV_CONFIG.supabaseAnonKey)
}
diff --git a/web/package.json b/web/package.json
index a93db55..d2941a4 100644
--- a/web/package.json
+++ b/web/package.json
@@ -73,7 +73,7 @@
"@svgr/webpack": "8.1.0",
"@tailwindcss/container-queries": "0.1.1",
"@tailwindcss/forms": "0.4.0",
- "@tailwindcss/typography": "^0.5.1",
+ "@tailwindcss/typography": "^0.5.13",
"@types/d3": "7.4.0",
"@types/lodash": "4.14.178",
"@types/react": "18.3.5",
@@ -83,7 +83,7 @@
"cross-env": "7.0.3",
"eslint-config-next": "14.0.3",
"eslint-config-prettier": "9.0.0",
- "next-sitemap": "^2.5.14",
+ "next-sitemap": "2.5.28",
"postcss": "8.4.31",
"tailwindcss": "3.3.3",
"tsc-files": "1.1.3"
diff --git a/yarn.lock b/yarn.lock
index 26b070f..373b92a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1410,37 +1410,65 @@
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.25.11.tgz#5ecda6f3fe138b7e456f4e429edde33c823f392f"
integrity sha512-D7Hpz6A2L4hzsRpPaCYkQnGOotdUpDzSGRIv9I+1ITdHROSFUWW95ZPZWQmGka1Fg7W3zFJowyn9WGwMJ0+KPA==
-"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0":
+"@eslint-community/eslint-utils@^4.4.0":
version "4.4.0"
resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59"
integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==
dependencies:
eslint-visitor-keys "^3.3.0"
-"@eslint-community/regexpp@^4.5.1", "@eslint-community/regexpp@^4.6.1":
+"@eslint-community/eslint-utils@^4.8.0":
+ version "4.9.1"
+ resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz#4e90af67bc51ddee6cdef5284edf572ec376b595"
+ integrity sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==
+ dependencies:
+ eslint-visitor-keys "^3.4.3"
+
+"@eslint-community/regexpp@^4.12.2":
+ version "4.12.2"
+ resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.2.tgz#bccdf615bcf7b6e8db830ec0b8d21c9a25de597b"
+ integrity sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==
+
+"@eslint-community/regexpp@^4.5.1":
version "4.10.0"
resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.10.0.tgz#548f6de556857c8bb73bbee70c35dc82a2e74d63"
integrity sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==
-"@eslint/eslintrc@^2.1.4":
- version "2.1.4"
- resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad"
- integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==
+"@eslint/config-array@^0.23.0":
+ version "0.23.1"
+ resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.23.1.tgz#908223da7b9148f1af5bfb3144b77a9387a89446"
+ integrity sha512-uVSdg/V4dfQmTjJzR0szNczjOH/J+FyUMMjYtr07xFRXR7EDf9i1qdxrD0VusZH9knj1/ecxzCQQxyic5NzAiA==
dependencies:
- ajv "^6.12.4"
- debug "^4.3.2"
- espree "^9.6.0"
- globals "^13.19.0"
- ignore "^5.2.0"
- import-fresh "^3.2.1"
- js-yaml "^4.1.0"
- minimatch "^3.1.2"
- strip-json-comments "^3.1.1"
+ "@eslint/object-schema" "^3.0.1"
+ debug "^4.3.1"
+ minimatch "^10.1.1"
-"@eslint/js@8.57.0":
- version "8.57.0"
- resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.0.tgz#a5417ae8427873f1dd08b70b3574b453e67b5f7f"
- integrity sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==
+"@eslint/config-helpers@^0.5.2":
+ version "0.5.2"
+ resolved "https://registry.yarnpkg.com/@eslint/config-helpers/-/config-helpers-0.5.2.tgz#314c7b03d02a371ad8c0a7f6821d5a8a8437ba9d"
+ integrity sha512-a5MxrdDXEvqnIq+LisyCX6tQMPF/dSJpCfBgBauY+pNZ28yCtSsTvyTYrMhaI+LK26bVyCJfJkT0u8KIj2i1dQ==
+ dependencies:
+ "@eslint/core" "^1.1.0"
+
+"@eslint/core@^1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@eslint/core/-/core-1.1.0.tgz#51f5cd970e216fbdae6721ac84491f57f965836d"
+ integrity sha512-/nr9K9wkr3P1EzFTdFdMoLuo1PmIxjmwvPozwoSodjNBdefGujXQUF93u1DDZpEaTuDvMsIQddsd35BwtrW9Xw==
+ dependencies:
+ "@types/json-schema" "^7.0.15"
+
+"@eslint/object-schema@^3.0.1":
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-3.0.1.tgz#9a1dc9af00d790dc79a9bf57a756e3cb2740ddb9"
+ integrity sha512-P9cq2dpr+LU8j3qbLygLcSZrl2/ds/pUpfnHNNuk5HW7mnngHs+6WSq5C9mO3rqRX8A1poxqLTC9cu0KOyJlBg==
+
+"@eslint/plugin-kit@^0.6.0":
+ version "0.6.0"
+ resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.6.0.tgz#e0cb12ec66719cb2211ad36499fb516f2a63899d"
+ integrity sha512-bIZEUzOI1jkhviX2cp5vNyXQc6olzb2ohewQubuYlMXZ2Q/XjBO0x0XhGPvc9fjSIiUN0vw+0hq53BJ4eQSJKQ==
+ dependencies:
+ "@eslint/core" "^1.1.0"
+ levn "^0.4.1"
"@faker-js/faker@10.1.0":
version "10.1.0"
@@ -2136,6 +2164,40 @@
protobufjs "^7.5.3"
yargs "^17.7.2"
+"@hapi/address@^5.1.1":
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/@hapi/address/-/address-5.1.1.tgz#e9925fc1b65f5cc3fbea821f2b980e4652e84cb6"
+ integrity sha512-A+po2d/dVoY7cYajycYI43ZbYMXukuopIsqCjh5QzsBCipDtdofHntljDlpccMjIfTy6UOkg+5KPriwYch2bXA==
+ dependencies:
+ "@hapi/hoek" "^11.0.2"
+
+"@hapi/formula@^3.0.2":
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/@hapi/formula/-/formula-3.0.2.tgz#81b538060ee079481c906f599906d163c4badeaf"
+ integrity sha512-hY5YPNXzw1He7s0iqkRQi+uMGh383CGdyyIGYtB+W5N3KHPXoqychklvHhKCC9M3Xtv0OCs/IHw+r4dcHtBYWw==
+
+"@hapi/hoek@^11.0.2", "@hapi/hoek@^11.0.7":
+ version "11.0.7"
+ resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-11.0.7.tgz#56a920793e0a42d10e530da9a64cc0d3919c4002"
+ integrity sha512-HV5undWkKzcB4RZUusqOpcgxOaq6VOAH7zhhIr2g3G8NF/MlFO75SjOr2NfuSx0Mh40+1FqCkagKLJRykUWoFQ==
+
+"@hapi/pinpoint@^2.0.1":
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/@hapi/pinpoint/-/pinpoint-2.0.1.tgz#32077e715655fc00ab8df74b6b416114287d6513"
+ integrity sha512-EKQmr16tM8s16vTT3cA5L0kZZcTMU5DUOZTuvpnY738m+jyP3JIUj+Mm1xc1rsLkGBQ/gVnfKYPwOmPg1tUR4Q==
+
+"@hapi/tlds@^1.1.1":
+ version "1.1.5"
+ resolved "https://registry.yarnpkg.com/@hapi/tlds/-/tlds-1.1.5.tgz#3eff5d8a3bd20833a2d779e0f839d25177fee722"
+ integrity sha512-Vq/1gnIIsvFUpKlDdfrPd/ssHDpAyBP/baVukh3u2KSG2xoNjsnRNjQiPmuyPPGqsn1cqVWWhtZHfOBaLizFRQ==
+
+"@hapi/topo@^6.0.2":
+ version "6.0.2"
+ resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-6.0.2.tgz#f219c1c60da8430228af4c1f2e40c32a0d84bbb4"
+ integrity sha512-KR3rD5inZbGMrHmgPxsJ9dbi6zEK+C3ZwUwTa+eMwWLz7oijWUTWD2pMSNNYJAU6Qq+65NkxXjqHr/7LM2Xkqg==
+ dependencies:
+ "@hapi/hoek" "^11.0.2"
+
"@headlessui/react@1.7.11":
version "1.7.11"
resolved "https://registry.yarnpkg.com/@headlessui/react/-/react-1.7.11.tgz#1cc5750226abe5af2c94f72e975c0c8d2f5cc5a6"
@@ -2148,24 +2210,28 @@
resolved "https://registry.yarnpkg.com/@heroicons/react/-/react-1.0.6.tgz#35dd26987228b39ef2316db3b1245c42eb19e324"
integrity sha512-JJCXydOFWMDpCP4q13iEplA503MQO3xLoZiKum+955ZCtHINWnx26CUxVxxFQu/uLb4LW3ge15ZpzIkXKkJ8oQ==
-"@humanwhocodes/config-array@^0.11.14":
- version "0.11.14"
- resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b"
- integrity sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==
+"@humanfs/core@^0.19.1":
+ version "0.19.1"
+ resolved "https://registry.yarnpkg.com/@humanfs/core/-/core-0.19.1.tgz#17c55ca7d426733fe3c561906b8173c336b40a77"
+ integrity sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==
+
+"@humanfs/node@^0.16.6":
+ version "0.16.7"
+ resolved "https://registry.yarnpkg.com/@humanfs/node/-/node-0.16.7.tgz#822cb7b3a12c5a240a24f621b5a2413e27a45f26"
+ integrity sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==
dependencies:
- "@humanwhocodes/object-schema" "^2.0.2"
- debug "^4.3.1"
- minimatch "^3.0.5"
+ "@humanfs/core" "^0.19.1"
+ "@humanwhocodes/retry" "^0.4.0"
"@humanwhocodes/module-importer@^1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c"
integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==
-"@humanwhocodes/object-schema@^2.0.2":
- version "2.0.3"
- resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3"
- integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==
+"@humanwhocodes/retry@^0.4.0", "@humanwhocodes/retry@^0.4.2":
+ version "0.4.3"
+ resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.4.3.tgz#c2b9d2e374ee62c586d3adbea87199b1d7a7a6ba"
+ integrity sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==
"@hutson/parse-repository-url@^3.0.0":
version "3.0.2"
@@ -2486,6 +2552,11 @@
wrap-ansi "^8.1.0"
wrap-ansi-cjs "npm:wrap-ansi@^7.0.0"
+"@isaacs/cliui@^9.0.0":
+ version "9.0.0"
+ resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-9.0.0.tgz#4d0a3f127058043bf2e7ee169eaf30ed901302f3"
+ integrity sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==
+
"@isaacs/fs-minipass@^4.0.0":
version "4.0.1"
resolved "https://registry.yarnpkg.com/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz#2d59ae3ab4b38fb4270bfa23d30f8e2e86c7fe32"
@@ -2853,7 +2924,7 @@
resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b"
integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
-"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8":
+"@nodelib/fs.walk@^1.2.3":
version "1.2.8"
resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a"
integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==
@@ -2925,7 +2996,7 @@
resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33"
integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==
-"@playwright/test@^1.54.2":
+"@playwright/test@1.55.0":
version "1.55.0"
resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.55.0.tgz#080fa6d9ee6d749ff523b1c18259572d0268b963"
integrity sha512-04IXzPwHrW69XusN/SIdDdKZBzMfOT9UNT/YiJit/xpy2VuAoB8NHc8Aplb96zsWDddLnbkPL3TsmrS04ZU2xQ==
@@ -3595,17 +3666,14 @@
dependencies:
mini-svg-data-uri "^1.2.3"
-"@tailwindcss/typography@^0.5.1":
- version "0.5.13"
- resolved "https://registry.yarnpkg.com/@tailwindcss/typography/-/typography-0.5.13.tgz#cd788a4fa4d0ca2506e242d512f377b22c1f7932"
- integrity sha512-ADGcJ8dX21dVVHIwTRgzrcunY6YY9uSlAHHGVKvkA+vLc5qLwEszvKts40lx7z0qc4clpjclwLeK5rVCV2P/uw==
+"@tailwindcss/typography@^0.5.13":
+ version "0.5.19"
+ resolved "https://registry.yarnpkg.com/@tailwindcss/typography/-/typography-0.5.19.tgz#ecb734af2569681eb40932f09f60c2848b909456"
+ integrity sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==
dependencies:
- lodash.castarray "^4.4.0"
- lodash.isplainobject "^4.0.6"
- lodash.merge "^4.6.2"
postcss-selector-parser "6.0.10"
-"@testing-library/dom@^10.0.0":
+"@testing-library/dom@10.4.1":
version "10.4.1"
resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-10.4.1.tgz#d444f8a889e9a46e9a3b4f3b88e0fcb3efb6cf95"
integrity sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==
@@ -3619,7 +3687,7 @@
picocolors "1.1.1"
pretty-format "^27.0.2"
-"@testing-library/jest-dom@^6.6.4":
+"@testing-library/jest-dom@6.8.0":
version "6.8.0"
resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-6.8.0.tgz#697db9424f0d21d8216f1958fa0b1b69b5f43923"
integrity sha512-WgXcWzVM6idy5JaftTVC8Vs83NKRmGJz4Hqs4oyOuO2J4r/y79vvKZsb+CaGyCSEbUPI6OsewfPd0G1A0/TUZQ==
@@ -3631,14 +3699,14 @@
picocolors "^1.1.1"
redent "^3.0.0"
-"@testing-library/react@^16.3.0":
+"@testing-library/react@16.3.0":
version "16.3.0"
resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-16.3.0.tgz#3a85bb9bdebf180cd76dba16454e242564d598a6"
integrity sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw==
dependencies:
"@babel/runtime" "^7.12.5"
-"@testing-library/user-event@^14.6.1":
+"@testing-library/user-event@14.6.1":
version "14.6.1"
resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-14.6.1.tgz#13e09a32d7a8b7060fe38304788ebf4197cd2149"
integrity sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==
@@ -4225,6 +4293,11 @@
dependencies:
"@types/ms" "*"
+"@types/esrecurse@^4.3.1":
+ version "4.3.1"
+ resolved "https://registry.yarnpkg.com/@types/esrecurse/-/esrecurse-4.3.1.tgz#6f636af962fbe6191b830bd676ba5986926bccec"
+ integrity sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==
+
"@types/estree-jsx@^1.0.0":
version "1.0.5"
resolved "https://registry.yarnpkg.com/@types/estree-jsx/-/estree-jsx-1.0.5.tgz#858a88ea20f34fe65111f005a689fa1ebf70dc18"
@@ -4232,7 +4305,7 @@
dependencies:
"@types/estree" "*"
-"@types/estree@*", "@types/estree@^1.0.0":
+"@types/estree@*", "@types/estree@^1.0.0", "@types/estree@^1.0.6", "@types/estree@^1.0.8":
version "1.0.8"
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e"
integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==
@@ -4347,7 +4420,7 @@
expect "^29.0.0"
pretty-format "^29.0.0"
-"@types/json-schema@^7.0.12", "@types/json-schema@^7.0.6":
+"@types/json-schema@^7.0.12", "@types/json-schema@^7.0.15", "@types/json-schema@^7.0.6":
version "7.0.15"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841"
integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==
@@ -4735,11 +4808,6 @@
resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8"
integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==
-"@ungap/structured-clone@^1.2.0":
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406"
- integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==
-
"@vercel/og@0.5.20":
version "0.5.20"
resolved "https://registry.yarnpkg.com/@vercel/og/-/og-0.5.20.tgz#dedd4b433bc3c1fec67d70a577b5ce8569a67838"
@@ -4822,6 +4890,11 @@ acorn-walk@^8.1.1:
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.2.tgz#7703af9415f1b6db9315d6895503862e231d34aa"
integrity sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==
+acorn@^8.15.0:
+ version "8.15.0"
+ resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816"
+ integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==
+
acorn@^8.4.1, acorn@^8.9.0:
version "8.11.3"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a"
@@ -5255,6 +5328,15 @@ axe-core@=4.7.0:
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.7.0.tgz#34ba5a48a8b564f67e103f0aa5768d76e15bbbbf"
integrity sha512-M0JtH+hlOL5pLQwHOLNYZaXuhqmvS8oExsqB1SBYgA4Dk7u/xx+YdGHXaK5pyUfed5mYXdlYiphWq3G8cRi5JQ==
+axios@^1.13.5:
+ version "1.13.5"
+ resolved "https://registry.yarnpkg.com/axios/-/axios-1.13.5.tgz#5e464688fa127e11a660a2c49441c009f6567a43"
+ integrity sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==
+ dependencies:
+ follow-redirects "^1.15.11"
+ form-data "^4.0.5"
+ proxy-from-env "^1.1.0"
+
axios@^1.7.4:
version "1.8.4"
resolved "https://registry.yarnpkg.com/axios/-/axios-1.8.4.tgz#78990bb4bc63d2cae072952d374835950a82f447"
@@ -5370,6 +5452,13 @@ balanced-match@^1.0.0:
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
+balanced-match@^4.0.2:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-4.0.2.tgz#241591ea634702bef9c482696f2469406e16d233"
+ integrity sha512-x0K50QvKQ97fdEz2kPehIerj+YTeptKF9hyYkKf6egnwmMWAkADiO0QCzSp0R5xN8FTZgYaBfSaue46Ej62nMg==
+ dependencies:
+ jackspeak "^4.2.3"
+
bare-events@^2.5.4, bare-events@^2.7.0:
version "2.8.1"
resolved "https://registry.yarnpkg.com/bare-events/-/bare-events-2.8.1.tgz#121afaeee9e9a8eb92e71d125bc85753d39913d0"
@@ -5462,6 +5551,17 @@ bin-links@^5.0.0:
read-cmd-shim "^5.0.0"
write-file-atomic "^6.0.0"
+bin-links@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/bin-links/-/bin-links-6.0.0.tgz#0245114374463a694e161a1e65417e7939ab2eba"
+ integrity sha512-X4CiKlcV2GjnCMwnKAfbVWpHa++65th9TuzAEYtZoATiOE2DQKhSp4CJlyLoTqdhBKlXjpXjCTYPNNFS33Fi6w==
+ dependencies:
+ cmd-shim "^8.0.0"
+ npm-normalize-package-bin "^5.0.0"
+ proc-log "^6.0.0"
+ read-cmd-shim "^6.0.0"
+ write-file-atomic "^7.0.0"
+
binary-extensions@^2.0.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522"
@@ -5594,6 +5694,13 @@ brace-expansion@^2.0.1:
dependencies:
balanced-match "^1.0.0"
+brace-expansion@^5.0.2:
+ version "5.0.2"
+ resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-5.0.2.tgz#b6c16d0791087af6c2bc463f52a8142046c06b6f"
+ integrity sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==
+ dependencies:
+ balanced-match "^4.0.2"
+
braces@^3.0.2, braces@~3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
@@ -6021,6 +6128,11 @@ cmd-shim@^7.0.0:
resolved "https://registry.yarnpkg.com/cmd-shim/-/cmd-shim-7.0.0.tgz#23bcbf69fff52172f7e7c02374e18fb215826d95"
integrity sha512-rtpaCbr164TPPh+zFdkWpCyZuKkjpAzODfaZCf/SVJZzJN+4bHQb/LP3Jzq5/+84um3XXY8r548XiWKSborwVw==
+cmd-shim@^8.0.0:
+ version "8.0.0"
+ resolved "https://registry.yarnpkg.com/cmd-shim/-/cmd-shim-8.0.0.tgz#5be238f22f40faf3f7e8c92edc3f5d354f7657b2"
+ integrity sha512-Jk/BK6NCapZ58BKUxlSI+ouKRbjH1NLZCgJkYoab+vEHUY3f6OzpNBN9u7HFSv9J6TRDGs4PLOHezoKGaFRSCA==
+
co@^4.6.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
@@ -6098,16 +6210,16 @@ color@^5.0.2:
color-convert "^3.1.3"
color-string "^2.1.3"
+colorette@2.0.20, colorette@^2.0.19, colorette@^2.0.20:
+ version "2.0.20"
+ resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a"
+ integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==
+
colorette@^1.2.2:
version "1.4.0"
resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.4.0.tgz#5190fbb87276259a86ad700bff2c6d6faa3fca40"
integrity sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==
-colorette@^2.0.19, colorette@^2.0.20:
- version "2.0.20"
- resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a"
- integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==
-
colors@1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78"
@@ -6545,7 +6657,7 @@ cross-fetch@3.1.5:
dependencies:
node-fetch "2.6.7"
-cross-spawn@^7.0.0, cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.3:
+cross-spawn@^7.0.0, cross-spawn@^7.0.1, cross-spawn@^7.0.3:
version "7.0.3"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
@@ -7061,7 +7173,7 @@ dlv@^1.1.3:
resolved "https://registry.yarnpkg.com/dlv/-/dlv-1.1.3.tgz#5c198a8a11453596e751494d49874bc7732f2e79"
integrity sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==
-doctrine@3.0.0, doctrine@^3.0.0:
+doctrine@3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961"
integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==
@@ -7155,7 +7267,7 @@ dot-prop@^5.1.0, dot-prop@^5.2.0:
dependencies:
is-obj "^2.0.0"
-dotenv-cli@^10.0.0:
+dotenv-cli@10.0.0:
version "10.0.0"
resolved "https://registry.yarnpkg.com/dotenv-cli/-/dotenv-cli-10.0.0.tgz#b8edf89a2d24eb344d9d9b96be09de1b4b7882ac"
integrity sha512-lnOnttzfrzkRx2echxJHQRB6vOAMSCzzZg79IxpC00tU42wZPuZkQxNNrrwVAxaQZIIh001l4PxVlCrBxngBzA==
@@ -7673,7 +7785,7 @@ eslint-plugin-jsx-a11y@^6.7.1:
object.entries "^1.1.7"
object.fromentries "^2.0.7"
-eslint-plugin-lodash@^7.4.0:
+eslint-plugin-lodash@7.4.0:
version "7.4.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-lodash/-/eslint-plugin-lodash-7.4.0.tgz#14a761547f126c92ff56789662a20a44f8bb6290"
integrity sha512-Tl83UwVXqe1OVeBRKUeWcfg6/pCW1GTRObbdnbEJgYwjxp5Q92MEWQaH9+dmzbRt6kvYU1Mp893E79nJiCSM8A==
@@ -7714,11 +7826,13 @@ eslint-plugin-unused-imports@4.1.4:
resolved "https://registry.yarnpkg.com/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-4.1.4.tgz#62ddc7446ccbf9aa7b6f1f0b00a980423cda2738"
integrity sha512-YptD6IzQjDardkl0POxnnRBhU1OEePMV0nd6siHaRBbd+lyh6NAhFEobiznKU7kTsSsDeSD62Pe7kAM1b7dAZQ==
-eslint-scope@^7.2.2:
- version "7.2.2"
- resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f"
- integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==
+eslint-scope@^9.1.0:
+ version "9.1.0"
+ resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-9.1.0.tgz#dfcb41d6c0d73df6b977a50cf3e91c41ddb4154e"
+ integrity sha512-CkWE42hOJsNj9FJRaoMX9waUFYhqY4jmyLFdAdzZr6VaCg3ynLYx4WnOdkaIifGfH4gsUcBTn4OZbHXkpLD0FQ==
dependencies:
+ "@types/esrecurse" "^4.3.1"
+ "@types/estree" "^1.0.8"
esrecurse "^4.3.0"
estraverse "^5.2.0"
@@ -7727,51 +7841,57 @@ eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800"
integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==
-eslint@8.57.0:
- version "8.57.0"
- resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.0.tgz#c786a6fd0e0b68941aaf624596fb987089195668"
- integrity sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==
+eslint-visitor-keys@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-5.0.0.tgz#b9aa1a74aa48c44b3ae46c1597ce7171246a94a9"
+ integrity sha512-A0XeIi7CXU7nPlfHS9loMYEKxUaONu/hTEzHTGba9Huu94Cq1hPivf+DE5erJozZOky0LfvXAyrV/tcswpLI0Q==
+
+eslint@10.0.0:
+ version "10.0.0"
+ resolved "https://registry.yarnpkg.com/eslint/-/eslint-10.0.0.tgz#c93c36a96d91621d0fbb680db848ea11af56ab1e"
+ integrity sha512-O0piBKY36YSJhlFSG8p9VUdPV/SxxS4FYDWVpr/9GJuMaepzwlf4J8I4ov1b+ySQfDTPhc3DtLaxcT1fN0yqCg==
dependencies:
- "@eslint-community/eslint-utils" "^4.2.0"
- "@eslint-community/regexpp" "^4.6.1"
- "@eslint/eslintrc" "^2.1.4"
- "@eslint/js" "8.57.0"
- "@humanwhocodes/config-array" "^0.11.14"
+ "@eslint-community/eslint-utils" "^4.8.0"
+ "@eslint-community/regexpp" "^4.12.2"
+ "@eslint/config-array" "^0.23.0"
+ "@eslint/config-helpers" "^0.5.2"
+ "@eslint/core" "^1.1.0"
+ "@eslint/plugin-kit" "^0.6.0"
+ "@humanfs/node" "^0.16.6"
"@humanwhocodes/module-importer" "^1.0.1"
- "@nodelib/fs.walk" "^1.2.8"
- "@ungap/structured-clone" "^1.2.0"
+ "@humanwhocodes/retry" "^0.4.2"
+ "@types/estree" "^1.0.6"
ajv "^6.12.4"
- chalk "^4.0.0"
- cross-spawn "^7.0.2"
+ cross-spawn "^7.0.6"
debug "^4.3.2"
- doctrine "^3.0.0"
escape-string-regexp "^4.0.0"
- eslint-scope "^7.2.2"
- eslint-visitor-keys "^3.4.3"
- espree "^9.6.1"
- esquery "^1.4.2"
+ eslint-scope "^9.1.0"
+ eslint-visitor-keys "^5.0.0"
+ espree "^11.1.0"
+ esquery "^1.7.0"
esutils "^2.0.2"
fast-deep-equal "^3.1.3"
- file-entry-cache "^6.0.1"
+ file-entry-cache "^8.0.0"
find-up "^5.0.0"
glob-parent "^6.0.2"
- globals "^13.19.0"
- graphemer "^1.4.0"
ignore "^5.2.0"
imurmurhash "^0.1.4"
is-glob "^4.0.0"
- is-path-inside "^3.0.3"
- js-yaml "^4.1.0"
json-stable-stringify-without-jsonify "^1.0.1"
- levn "^0.4.1"
- lodash.merge "^4.6.2"
- minimatch "^3.1.2"
+ minimatch "^10.1.1"
natural-compare "^1.4.0"
optionator "^0.9.3"
- strip-ansi "^6.0.1"
- text-table "^0.2.0"
-espree@^9.0.0, espree@^9.6.0, espree@^9.6.1:
+espree@^11.1.0:
+ version "11.1.0"
+ resolved "https://registry.yarnpkg.com/espree/-/espree-11.1.0.tgz#7d0c82a69f8df670728dba256264b383fbf73e8f"
+ integrity sha512-WFWYhO1fV4iYkqOOvq8FbqIhr2pYfoDY0kCotMkDeNtGpiGGkZ1iov2u8ydjtgM8yF8rzK7oaTbw2NAzbAbehw==
+ dependencies:
+ acorn "^8.15.0"
+ acorn-jsx "^5.3.2"
+ eslint-visitor-keys "^5.0.0"
+
+espree@^9.0.0:
version "9.6.1"
resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f"
integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==
@@ -7785,10 +7905,10 @@ esprima@^4.0.0, esprima@^4.0.1:
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
-esquery@^1.4.2:
- version "1.5.0"
- resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b"
- integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==
+esquery@^1.7.0:
+ version "1.7.0"
+ resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.7.0.tgz#08d048f261f0ddedb5bae95f46809463d9c9496d"
+ integrity sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==
dependencies:
estraverse "^5.1.0"
@@ -8171,12 +8291,12 @@ fflate@^0.7.3:
resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.7.4.tgz#61587e5d958fdabb5a9368a302c25363f4f69f50"
integrity sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==
-file-entry-cache@^6.0.1:
- version "6.0.1"
- resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027"
- integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==
+file-entry-cache@^8.0.0:
+ version "8.0.0"
+ resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz#7787bddcf1131bffb92636c69457bbc0edd6d81f"
+ integrity sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==
dependencies:
- flat-cache "^3.0.4"
+ flat-cache "^4.0.0"
filesize@^6.1.0:
version "6.4.0"
@@ -8283,7 +8403,7 @@ firebase-admin@13.5.0:
"@google-cloud/firestore" "^7.11.0"
"@google-cloud/storage" "^7.14.0"
-firebase-tools@^14.26.0:
+firebase-tools@14.27.0:
version "14.27.0"
resolved "https://registry.yarnpkg.com/firebase-tools/-/firebase-tools-14.27.0.tgz#762a2de407f9df188952cd942ef67a144776a053"
integrity sha512-HrucHJ69mLM9pQhZFO1rb0N/QMpZD4iznoOtKd2lctEELPtbSMN5JHgdgzLlf+EXn5aQy87u5zlPd/0xwwyYTQ==
@@ -8399,14 +8519,13 @@ firebase@11.1.0:
"@firebase/util" "1.10.2"
"@firebase/vertexai" "1.0.2"
-flat-cache@^3.0.4:
- version "3.2.0"
- resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.2.0.tgz#2c0c2d5040c99b1632771a9d105725c0115363ee"
- integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==
+flat-cache@^4.0.0:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-4.0.1.tgz#0ece39fcb14ee012f4b0410bd33dd9c1f011127c"
+ integrity sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==
dependencies:
flatted "^3.2.9"
- keyv "^4.5.3"
- rimraf "^3.0.2"
+ keyv "^4.5.4"
flatted@^3.2.9:
version "3.3.1"
@@ -8418,6 +8537,11 @@ fn.name@1.x.x:
resolved "https://registry.yarnpkg.com/fn.name/-/fn.name-1.1.0.tgz#26cad8017967aea8731bc42961d04a3d5988accc"
integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==
+follow-redirects@^1.15.11:
+ version "1.15.11"
+ resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.11.tgz#777d73d72a92f8ec4d2e410eb47352a56b8e8340"
+ integrity sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==
+
follow-redirects@^1.15.6:
version "1.15.6"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b"
@@ -8464,7 +8588,7 @@ form-data@^4.0.0:
combined-stream "^1.0.8"
mime-types "^2.1.12"
-form-data@^4.0.1:
+form-data@^4.0.1, form-data@^4.0.5:
version "4.0.5"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.5.tgz#b49e48858045ff4cbf6b03e1805cebcad3679053"
integrity sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==
@@ -8950,13 +9074,6 @@ globals@^11.1.0:
resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==
-globals@^13.19.0:
- version "13.24.0"
- resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171"
- integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==
- dependencies:
- type-fest "^0.20.2"
-
globalthis@^1.0.3:
version "1.0.4"
resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.4.tgz#7430ed3a975d97bfb59bcce41f5cabbafa651236"
@@ -9510,7 +9627,7 @@ immer@^10.0.3, immer@^10.1.1:
resolved "https://registry.yarnpkg.com/immer/-/immer-10.1.3.tgz#e38a0b97db59949d31d9b381b04c2e441b1c3747"
integrity sha512-tmjF/k8QDKydUlm3mZU+tjM6zeq9/fFpPqH9SzWmBnVVKsPBg/V66qsMwb3/Bo90cgUN+ghdVBess+hPsxUyRw==
-import-fresh@^3.2.1, import-fresh@^3.3.0:
+import-fresh@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b"
integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==
@@ -9831,7 +9948,7 @@ is-path-cwd@^2.2.0:
resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-2.2.0.tgz#67d43b82664a7b5191fd9119127eb300048a9fdb"
integrity sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==
-is-path-inside@^3.0.2, is-path-inside@^3.0.3:
+is-path-inside@^3.0.2:
version "3.0.3"
resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283"
integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==
@@ -10092,6 +10209,13 @@ jackspeak@^4.1.1:
dependencies:
"@isaacs/cliui" "^8.0.2"
+jackspeak@^4.2.3:
+ version "4.2.3"
+ resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-4.2.3.tgz#27ef80f33b93412037c3bea4f8eddf80e1931483"
+ integrity sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==
+ dependencies:
+ "@isaacs/cliui" "^9.0.0"
+
jest-changed-files@^29.7.0:
version "29.7.0"
resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.7.0.tgz#1c06d07e77c78e1585d020424dedc10d6e17ac3a"
@@ -10460,6 +10584,19 @@ jju@^1.1.0:
resolved "https://registry.yarnpkg.com/jju/-/jju-1.4.0.tgz#a3abe2718af241a2b2904f84a625970f389ae32a"
integrity sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==
+joi@^18.0.2:
+ version "18.0.2"
+ resolved "https://registry.yarnpkg.com/joi/-/joi-18.0.2.tgz#30ced6aed00a7848cc11f92859515258301dc3a4"
+ integrity sha512-RuCOQMIt78LWnktPoeBL0GErkNaJPTBGcYuyaBvUOQSpcpcLfWrHPPihYdOGbV5pam9VTWbeoF7TsGiHugcjGA==
+ dependencies:
+ "@hapi/address" "^5.1.1"
+ "@hapi/formula" "^3.0.2"
+ "@hapi/hoek" "^11.0.7"
+ "@hapi/pinpoint" "^2.0.1"
+ "@hapi/tlds" "^1.1.1"
+ "@hapi/topo" "^6.0.2"
+ "@standard-schema/spec" "^1.0.0"
+
join-path@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/join-path/-/join-path-1.1.1.tgz#10535a126d24cbd65f7ffcdf15ef2e631076b505"
@@ -10728,7 +10865,7 @@ jws@^4.0.0:
jwa "^2.0.0"
safe-buffer "^5.0.1"
-keyv@^4.5.3:
+keyv@^4.5.4:
version "4.5.4"
resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93"
integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==
@@ -10918,11 +11055,6 @@ lodash.camelcase@^4.3.0:
resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
integrity sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==
-lodash.castarray@^4.4.0:
- version "4.4.0"
- resolved "https://registry.yarnpkg.com/lodash.castarray/-/lodash.castarray-4.4.0.tgz#c02513515e309daddd4c24c60cfddcf5976d9115"
- integrity sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==
-
lodash.clonedeep@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
@@ -11020,6 +11152,11 @@ lodash@4.17.21, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.15, lodash@^4.17.
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
+lodash@^4.17.23:
+ version "4.17.23"
+ resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.23.tgz#f113b0378386103be4f6893388c73d0bde7f2c5a"
+ integrity sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==
+
log-symbols@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503"
@@ -11704,7 +11841,14 @@ minimatch@^10.0.3:
dependencies:
"@isaacs/brace-expansion" "^5.0.0"
-minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
+minimatch@^10.1.1:
+ version "10.2.0"
+ resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.2.0.tgz#e710473e66e3e1aaf376d0aa82438375cac86e9e"
+ integrity sha512-ugkC31VaVg9cF0DFVoADH12k6061zNZkZON+aX8AWsR9GhPcErkcMBceb6znR8wLERM2AkkOxy2nWRLpT9Jq5w==
+ dependencies:
+ brace-expansion "^5.0.2"
+
+minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
@@ -11755,7 +11899,7 @@ minimist-options@4.1.0:
is-plain-obj "^1.1.0"
kind-of "^6.0.3"
-minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5, minimist@^1.2.6:
+minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5, minimist@^1.2.6, minimist@^1.2.8:
version "1.2.8"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
@@ -11996,7 +12140,7 @@ netmask@^2.0.2:
resolved "https://registry.yarnpkg.com/netmask/-/netmask-2.0.2.tgz#8b01a07644065d536383835823bc52004ebac5e7"
integrity sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==
-next-sitemap@^2.5.14:
+next-sitemap@2.5.28:
version "2.5.28"
resolved "https://registry.yarnpkg.com/next-sitemap/-/next-sitemap-2.5.28.tgz#c061914edcc74268e43254710dec0a0febae5fa4"
integrity sha512-rDMTa7nLE0ikYnu8JDCrwQufIRm4b4Sg8BK11DGdDAmYpXmcmw4Qvm+oVBR8gUL97E6i2ihTM+cRx8mgENDACA==
@@ -12216,6 +12360,11 @@ npm-normalize-package-bin@^4.0.0:
resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-4.0.0.tgz#df79e70cd0a113b77c02d1fe243c96b8e618acb1"
integrity sha512-TZKxPvItzai9kN9H/TkmCtx/ZN/hvr3vUycjlfmH0ootY9yFBzNOpiXAdIn1Iteqsvk4lQn6B5PTrt+n6h8k/w==
+npm-normalize-package-bin@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-5.0.0.tgz#2b207ff260f2e525ddce93356614e2f736728f89"
+ integrity sha512-CJi3OS4JLsNMmr2u07OJlhcrPxCeOeP/4xq67aWNai6TNWWbTrlNDgl8NcFKVlcBKp18GPj+EzbNIgrBfZhsag==
+
npm-package-arg@^11.0.0:
version "11.0.3"
resolved "https://registry.yarnpkg.com/npm-package-arg/-/npm-package-arg-11.0.3.tgz#dae0c21199a99feca39ee4bfb074df3adac87e2d"
@@ -13076,7 +13225,7 @@ prettier-plugin-sql@0.19.2:
sql-formatter "^15.6.5"
tslib "^2.8.1"
-prettier-plugin-tailwindcss@^0.2.1:
+prettier-plugin-tailwindcss@0.2.8:
version "0.2.8"
resolved "https://registry.yarnpkg.com/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.2.8.tgz#e9c0356680331f909a86fefe8fc2b247c21e23a2"
integrity sha512-KgPcEnJeIijlMjsA6WwYgRs5rh3/q76oInqtMXBA/EMcamrcYJpyhtRhyX1ayT9hnHlHTuO8sIifHF10WuSDKg==
@@ -13119,7 +13268,7 @@ prismjs@1.29.0:
resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.29.0.tgz#f113555a8fa9b57c35e637bba27509dcf802dd12"
integrity sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==
-prismjs@^1.30.0:
+prismjs@1.30.0:
version "1.30.0"
resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.30.0.tgz#d9709969d9d4e16403f6f348c63553b19f0975a9"
integrity sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==
@@ -13134,6 +13283,11 @@ proc-log@^5.0.0:
resolved "https://registry.yarnpkg.com/proc-log/-/proc-log-5.0.0.tgz#e6c93cf37aef33f835c53485f314f50ea906a9d8"
integrity sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==
+proc-log@^6.0.0:
+ version "6.1.0"
+ resolved "https://registry.yarnpkg.com/proc-log/-/proc-log-6.1.0.tgz#18519482a37d5198e231133a70144a50f21f0215"
+ integrity sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==
+
process-nextick-args@~2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
@@ -13688,7 +13842,7 @@ react-is@^18.0.0:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e"
integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==
-react-markdown@*:
+react-markdown@10.1.0:
version "10.1.0"
resolved "https://registry.yarnpkg.com/react-markdown/-/react-markdown-10.1.0.tgz#e22bc20faddbc07605c15284255653c0f3bad5ca"
integrity sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==
@@ -13759,6 +13913,11 @@ read-cmd-shim@^5.0.0:
resolved "https://registry.yarnpkg.com/read-cmd-shim/-/read-cmd-shim-5.0.0.tgz#6e5450492187a0749f6c80dcbef0debc1117acca"
integrity sha512-SEbJV7tohp3DAAILbEMPXavBjAnMN0tVnh4+9G8ihV4Pq3HYF9h8QNez9zkJ1ILkv9G2BjdzwctznGZXgu/HGw==
+read-cmd-shim@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/read-cmd-shim/-/read-cmd-shim-6.0.0.tgz#98f5c8566e535829f1f8afb1595aaf05fd0f3970"
+ integrity sha512-1zM5HuOfagXCBWMN83fuFI/x+T/UhZ7k+KIzhrHXcQoeX5+7gmaDYjELQHmmzIodumBHeByBJT4QYS7ufAgs7A==
+
read-pkg-up@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-3.0.0.tgz#3ed496685dba0f8fe118d0691dc51f4a1ff96f07"
@@ -14183,6 +14342,13 @@ rxjs@^7.5.2, rxjs@^7.8.1:
dependencies:
tslib "^2.1.0"
+rxjs@^7.8.2:
+ version "7.8.2"
+ resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.2.tgz#955bc473ed8af11a002a2be52071bf475638607b"
+ integrity sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==
+ dependencies:
+ tslib "^2.1.0"
+
safe-array-concat@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.2.tgz#81d77ee0c4e8b863635227c721278dd524c20edb"
@@ -14996,6 +15162,16 @@ supabase@2.15.8:
node-fetch "^3.3.2"
tar "7.4.3"
+supabase@2.76.9:
+ version "2.76.9"
+ resolved "https://registry.yarnpkg.com/supabase/-/supabase-2.76.9.tgz#79b1d1f78e80b54f71ddce67078f89b577573860"
+ integrity sha512-BHQ9kSetU7QUm9eLUzGtzlIvkktVnQZ8ypar8Bck9PQZdRE/ugXLLHBg/qqlWTxYRS7QV7Vn/rGLVhWclVMm+g==
+ dependencies:
+ bin-links "^6.0.0"
+ https-proxy-agent "^7.0.2"
+ node-fetch "^3.3.2"
+ tar "7.5.7"
+
superstatic@^10.0.0:
version "10.0.0"
resolved "https://registry.yarnpkg.com/superstatic/-/superstatic-10.0.0.tgz#64d07e969b387a2567754205d10305a390dddfa4"
@@ -15197,6 +15373,17 @@ tar@7.4.3:
mkdirp "^3.0.1"
yallist "^5.0.0"
+tar@7.5.7:
+ version "7.5.7"
+ resolved "https://registry.yarnpkg.com/tar/-/tar-7.5.7.tgz#adf99774008ba1c89819f15dbd6019c630539405"
+ integrity sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==
+ dependencies:
+ "@isaacs/fs-minipass" "^4.0.0"
+ chownr "^3.0.0"
+ minipass "^7.1.2"
+ minizlib "^3.1.0"
+ yallist "^5.0.0"
+
tar@^6.1.11:
version "6.2.1"
resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.1.tgz#717549c541bc3c2af15751bea94b1dd068d4b03a"
@@ -15291,11 +15478,6 @@ text-hex@1.0.x:
resolved "https://registry.yarnpkg.com/text-hex/-/text-hex-1.0.0.tgz#69dc9c1b17446ee79a92bf5b884bb4b9127506f5"
integrity sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==
-text-table@^0.2.0:
- version "0.2.0"
- resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
- integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==
-
thenify-all@^1.0.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726"
@@ -16054,6 +16236,17 @@ w3c-keyname@^2.2.0:
resolved "https://registry.yarnpkg.com/w3c-keyname/-/w3c-keyname-2.2.8.tgz#7b17c8c6883d4e8b86ac8aba79d39e880f8869c5"
integrity sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==
+wait-on@9.0.4:
+ version "9.0.4"
+ resolved "https://registry.yarnpkg.com/wait-on/-/wait-on-9.0.4.tgz#ddf3a44ebd18f380621855d52973069ff2cb5b54"
+ integrity sha512-k8qrgfwrPVJXTeFY8tl6BxVHiclK11u72DVKhpybHfUL/K6KM4bdyK9EhIVYGytB5MJe/3lq4Tf0hrjM+pvJZQ==
+ dependencies:
+ axios "^1.13.5"
+ joi "^18.0.2"
+ lodash "^4.17.23"
+ minimist "^1.2.8"
+ rxjs "^7.8.2"
+
walker@^1.0.8:
version "1.0.8"
resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f"
@@ -16307,6 +16500,14 @@ write-file-atomic@^6.0.0:
imurmurhash "^0.1.4"
signal-exit "^4.0.1"
+write-file-atomic@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-7.0.0.tgz#f89def4f223e9bf8b06cc6fdb12bda3a917505c7"
+ integrity sha512-YnlPC6JqnZl6aO4uRc+dx5PHguiR9S6WeoLtpxNT9wIG+BDya7ZNE1q7KOjVgaA73hKhKLpVPgJ5QA9THQ5BRg==
+ dependencies:
+ imurmurhash "^0.1.4"
+ signal-exit "^4.0.1"
+
ws@8.17.1:
version "8.17.1"
resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b"