From f7c0d77e9cb2737c5ba2e661fb8b366d9f4682a1 Mon Sep 17 00:00:00 2001 From: MartinBraquet Date: Tue, 17 Feb 2026 12:10:17 +0100 Subject: [PATCH] Add local supabase for DB isolation --- .github/workflows/ci-e2e.yml | 58 + .github/workflows/ci.yml | 26 +- .gitignore | 1 - .junie/guidelines.md | 1315 +++++++++++++++++ README.md | 10 + backend/shared/src/supabase/init.ts | 46 +- backend/supabase/extensions.sql | 28 + backend/supabase/functions.sql | 11 - backend/supabase/migration.sql | 25 +- backend/supabase/migrations/.keep | 0 .../20251110_add_languages_to_profiles.sql | 2 +- .../private_user_message_channel_members.sql | 12 + backend/supabase/profiles.sql | 11 +- common/src/supabase/utils.ts | 5 +- docs/TESTING.md | 16 + package.json | 34 +- scripts/combine-migrations.sh | 85 ++ scripts/docker-compose.test.yml | 19 + scripts/e2e.sh | 125 +- scripts/run_local_emulated.sh | 62 - scripts/run_local_isolated.sh | 86 ++ scripts/seed-test-data.ts | 56 + scripts/test_db_migration.sh | 41 + supabase/.gitignore | 9 + supabase/config.toml | 295 ++++ supabase/seed.sql | 2 + web/lib/supabase/db.ts | 22 +- web/package.json | 4 +- yarn.lock | 471 ++++-- 29 files changed, 2590 insertions(+), 287 deletions(-) create mode 100644 .github/workflows/ci-e2e.yml create mode 100644 .junie/guidelines.md create mode 100644 backend/supabase/extensions.sql delete mode 100644 backend/supabase/migrations/.keep create mode 100755 scripts/combine-migrations.sh create mode 100644 scripts/docker-compose.test.yml delete mode 100755 scripts/run_local_emulated.sh create mode 100755 scripts/run_local_isolated.sh create mode 100644 scripts/seed-test-data.ts create mode 100755 scripts/test_db_migration.sh create mode 100644 supabase/.gitignore create mode 100644 supabase/config.toml create mode 100644 supabase/seed.sql 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. + +![Vercel](https://deploy-badge.vercel.app/vercel/compass) +[![CD](https://github.com/CompassConnections/Compass/actions/workflows/cd.yml/badge.svg)](https://github.com/CompassConnections/Compass/actions/workflows/cd.yml) +[![CD API](https://github.com/CompassConnections/Compass/actions/workflows/cd-api.yml/badge.svg)](https://github.com/CompassConnections/Compass/actions/workflows/cd-api.yml) +[![CI](https://github.com/CompassConnections/Compass/actions/workflows/ci.yml/badge.svg)](https://github.com/CompassConnections/Compass/actions/workflows/ci.yml) +[![codecov](https://codecov.io/gh/CompassConnections/Compass/branch/main/graph/badge.svg)](https://codecov.io/gh/CompassConnections/Compass) +[![Users](https://img.shields.io/badge/Users-500%2B-blue?logo=myspace)](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! + +![Demo](https://raw.githubusercontent.com/CompassConnections/assets/refs/heads/main/assets/demo-2x.gif) + +## 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"