mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-09-08 12:16:12 -05:00
npm->pnpm (#2929)
This commit is contained in:
@@ -18,7 +18,7 @@ description: Run, debug, and manage Playwright e2e tests. Use when running e2e t
|
||||
|
||||
Before running tests, check for these common issues:
|
||||
|
||||
1. **Stale worker databases** — Files matching `db-test-e2e-*.sqlite3` in the project root can cause "table already exists" migration errors if the schema has changed since they were created. Run `npm run test:e2e:generate-seeds` to regenerate these from the seed databases.
|
||||
1. **Stale worker databases** — Files matching `db-test-e2e-*.sqlite3` in the project root can cause "table already exists" migration errors if the schema has changed since they were created. Run `pnpm run test:e2e:generate-seeds` to regenerate these from the seed databases.
|
||||
|
||||
2. **Port conflicts** — Check if anything is already listening on the e2e ports (base port through base+3):
|
||||
```
|
||||
@@ -26,7 +26,7 @@ Before running tests, check for these common issues:
|
||||
```
|
||||
If ports are occupied by leftover e2e servers, kill them. If occupied by something else, warn the user.
|
||||
|
||||
3. **Seed databases exist** — Verify `e2e/seeds/` contains the expected seed files. If missing, run `npm run test:e2e:generate-seeds`.
|
||||
3. **Seed databases exist** — Verify `e2e/seeds/` contains the expected seed files. If missing, run `pnpm run test:e2e:generate-seeds`.
|
||||
|
||||
4. **Docker running** — MinIO requires Docker. Check with `docker info` if there are storage-related failures.
|
||||
|
||||
@@ -34,22 +34,22 @@ Before running tests, check for these common issues:
|
||||
|
||||
### Run all tests
|
||||
```bash
|
||||
npm run test:e2e
|
||||
pnpm run test:e2e
|
||||
```
|
||||
|
||||
### Run a specific test file
|
||||
```bash
|
||||
npx playwright test e2e/<name>.spec.ts
|
||||
pnpm exec playwright test e2e/<name>.spec.ts
|
||||
```
|
||||
|
||||
### Flaky detection (repeats each test 10 times, stops on first failure)
|
||||
```bash
|
||||
npm run test:e2e:flaky-detect
|
||||
pnpm run test:e2e:flaky-detect
|
||||
```
|
||||
|
||||
### Regenerate seed databases (after schema/migration changes)
|
||||
```bash
|
||||
npm run test:e2e:generate-seeds
|
||||
pnpm run test:e2e:generate-seeds
|
||||
```
|
||||
|
||||
## Debugging failures
|
||||
@@ -65,19 +65,19 @@ Common infrastructure errors and fixes:
|
||||
- **"table already exists"** → Stale worker DBs. Run `rm -f db-test-e2e-*.sqlite3`
|
||||
- **"Server on port X did not start within timeout"** → Port conflict or app build error. Check ports with `lsof -i :<port>` and check for build errors
|
||||
- **"MinIO failed to start"** → Docker not running or compose issue. Check `docker info`
|
||||
- **Seed-related errors** → Run `npm run test:e2e:generate-seeds`
|
||||
- **Seed-related errors** → Run `pnpm run test:e2e:generate-seeds`
|
||||
|
||||
### Step 3: Reduce to single debug worker
|
||||
If the error is unclear, re-run with debug output and a single worker to see server logs:
|
||||
```bash
|
||||
E2E_DEBUG=true E2E_WORKERS=1 npx playwright test e2e/<failing-test>.spec.ts
|
||||
E2E_DEBUG=true E2E_WORKERS=1 pnpm exec playwright test e2e/<failing-test>.spec.ts
|
||||
```
|
||||
This shows stdout/stderr from the test server, which is hidden by default.
|
||||
|
||||
### Step 4: Examine trace artifacts
|
||||
Playwright is configured with `trace: "retain-on-failure"`. After a failure, view the trace:
|
||||
```bash
|
||||
npx playwright show-trace test-results/<test-folder>/trace.zip
|
||||
pnpm exec playwright show-trace test-results/<test-folder>/trace.zip
|
||||
```
|
||||
|
||||
## Test pattern reference
|
||||
|
||||
2
.github/dependabot.yml
vendored
2
.github/dependabot.yml
vendored
@@ -4,6 +4,8 @@ updates:
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
cooldown:
|
||||
default-days: 8
|
||||
groups:
|
||||
minor-and-patch:
|
||||
update-types:
|
||||
|
||||
10
.github/workflows/e2e-tests.yml
vendored
10
.github/workflows/e2e-tests.yml
vendored
@@ -38,19 +38,21 @@ jobs:
|
||||
echo "MinIO failed to start"
|
||||
exit 1
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'npm'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Install Playwright browsers
|
||||
run: npx playwright install --with-deps
|
||||
run: pnpm exec playwright install --with-deps
|
||||
|
||||
- name: Run E2E tests
|
||||
run: npm run test:e2e
|
||||
run: pnpm run test:e2e
|
||||
|
||||
- name: Stop MinIO
|
||||
if: always()
|
||||
|
||||
30
.github/workflows/main.yml
vendored
30
.github/workflows/main.yml
vendored
@@ -12,37 +12,33 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: npm-${{ hashFiles('package-lock.json') }}
|
||||
restore-keys: npm-
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Install Playwright browsers
|
||||
run: npx playwright install chromium
|
||||
run: pnpm exec playwright install chromium
|
||||
|
||||
- name: Formatter/Linter
|
||||
run: npm run biome:check
|
||||
run: pnpm run biome:check
|
||||
- name: Typecheck
|
||||
run: npm run typecheck
|
||||
run: pnpm run typecheck
|
||||
- name: Unit tests
|
||||
run: npm run test:unit:browser
|
||||
run: pnpm run test:unit:browser
|
||||
- name: Knip unused check
|
||||
run: npm run knip
|
||||
run: pnpm run knip
|
||||
- name: Check translations jsons
|
||||
run: npm run check-translation-jsons:no-write
|
||||
run: pnpm run check-translation-jsons:no-write
|
||||
- name: Check homemade badges
|
||||
run: npm run check-homemade-badges
|
||||
run: pnpm run check-homemade-badges
|
||||
- name: Check articles
|
||||
run: npm run check-articles
|
||||
run: pnpm run check-articles
|
||||
- name: Check test DB migrations
|
||||
run: npm run check-test-db-migrations
|
||||
run: pnpm run check-test-db-migrations
|
||||
|
||||
18
.github/workflows/translation-progress.yml
vendored
18
.github/workflows/translation-progress.yml
vendored
@@ -11,26 +11,22 @@ jobs:
|
||||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: npm-${{ hashFiles('package-lock.json') }}
|
||||
restore-keys: npm-
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci --ignore-scripts
|
||||
run: pnpm install --frozen-lockfile --ignore-scripts
|
||||
|
||||
- name: Update translation progress issue
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
npm run check-translation-jsons
|
||||
pnpm run check-translation-jsons
|
||||
gh issue edit 1104 --body-file ./translation-progress.md
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,4 +1,5 @@
|
||||
node_modules
|
||||
package-lock.json
|
||||
|
||||
/.cache*
|
||||
/build
|
||||
|
||||
18
AGENTS.md
18
AGENTS.md
@@ -2,18 +2,18 @@
|
||||
|
||||
- only rarely use comments, prefer descriptive variable and function names (leave existing comments as is)
|
||||
- if you encounter an existing TODO comment assume it is there for a reason and do not remove it
|
||||
- task is not considered completely until `npm run checks` passes
|
||||
- task is not considered completely until `pnpm run checks` passes
|
||||
- normal file structure has constants at the top immediately followed by the main function body of the file. Helpers are used to structure the code and they are at the bottom of the file (main implementation first, at the top of the file)
|
||||
- note: any formatting issue (such as tabs vs. spaces) can be resolved by running the `npm run biome:fix` command
|
||||
- note: any formatting issue (such as tabs vs. spaces) can be resolved by running the `pnpm run biome:fix` command
|
||||
|
||||
## Commands
|
||||
|
||||
- `npm run typecheck` runs TypeScript type checking
|
||||
- `npm run biome:fix` runs Biome code formatter and linter
|
||||
- `npm run test:unit:browser` runs all unit tests and browser tests
|
||||
- `npm run test:e2e` runs all e2e tests
|
||||
- `npm run test:e2e:flaky-detect` runs all e2e tests and repeats each 10 times
|
||||
- `npm run i18n:sync` syncs translation jsons with English
|
||||
- `pnpm run typecheck` runs TypeScript type checking
|
||||
- `pnpm run biome:fix` runs Biome code formatter and linter
|
||||
- `pnpm run test:unit:browser` runs all unit tests and browser tests
|
||||
- `pnpm run test:e2e` runs all e2e tests
|
||||
- `pnpm run test:e2e:flaky-detect` runs all e2e tests and repeats each 10 times
|
||||
- `pnpm run i18n:sync` syncs translation jsons with English
|
||||
|
||||
## Typescript
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
- by default everything should be translated via i18next
|
||||
- some a11y labels or text that should not normally be encountered by user (example given, error message by server) can be english
|
||||
- before adding a new translation, check that one doesn't already exist you can reuse (particularly in the common.json)
|
||||
- add only English translation and use `npm run i18n:sync` to initialize other jsons with empty string ready for translators
|
||||
- add only English translation and use `pnpm run i18n:sync` to initialize other jsons with empty string ready for translators
|
||||
|
||||
## Commit messages
|
||||
|
||||
|
||||
@@ -82,8 +82,8 @@ Then there is a sequence of commands you need to run:
|
||||
```bash
|
||||
git clone https://github.com/sendou-ink/sendou.ink.git # Clones repository
|
||||
cd sendou.ink # Change to the project's folder
|
||||
npm install # Install dependencies
|
||||
npm run dev # Setup the development environment and run the project
|
||||
pnpm install # Install dependencies
|
||||
pnpm dev # Setup the development environment and run the project
|
||||
```
|
||||
|
||||
You should then be able to access the application by visiting http://localhost:5173
|
||||
|
||||
@@ -1,329 +0,0 @@
|
||||
# Tournament LFG Implementation Plan
|
||||
|
||||
## Overview
|
||||
|
||||
Add a new `/to/:id/looking` route that provides SendouQ-style matchmaking for tournament team formation. Players and teams can find each other before the tournament starts.
|
||||
|
||||
## Phase 1: Database Migration
|
||||
|
||||
**File**: `migrations/118-tournament-lfg.js`
|
||||
|
||||
Create three new tables:
|
||||
|
||||
### TournamentLFGGroup
|
||||
| Column | Type | Notes |
|
||||
|--------|------|-------|
|
||||
| id | integer primary key | Auto-increment |
|
||||
| tournamentId | integer not null | FK to Tournament |
|
||||
| tournamentTeamId | integer | FK to TournamentTeam (null for unregistered groups) |
|
||||
| visibility | text | JSON (AssociationVisibility) |
|
||||
| chatCode | text not null | Unique room code for group chat |
|
||||
| createdAt | integer | Default now |
|
||||
|
||||
### TournamentLFGGroupMember
|
||||
| Column | Type | Notes |
|
||||
|--------|------|-------|
|
||||
| groupId | integer not null | FK to TournamentLFGGroup |
|
||||
| userId | integer not null | FK to User |
|
||||
| role | text not null | OWNER / MANAGER / REGULAR |
|
||||
| note | text | Public note |
|
||||
| isStayAsSub | integer | Boolean (0/1), default 0 |
|
||||
| createdAt | integer | Default now |
|
||||
|
||||
### TournamentLFGLike
|
||||
| Column | Type | Notes |
|
||||
|--------|------|-------|
|
||||
| likerGroupId | integer not null | FK to TournamentLFGGroup |
|
||||
| targetGroupId | integer not null | FK to TournamentLFGGroup |
|
||||
| createdAt | integer | Default now |
|
||||
|
||||
All tables use `STRICT` mode, `ON DELETE CASCADE` for FKs, and have indexes on FK columns.
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: TypeScript Types
|
||||
|
||||
**File**: `app/db/tables.ts`
|
||||
|
||||
Add interfaces for the three new tables following existing patterns (`GeneratedAlways`, `Generated`, `JSONColumnTypeNullable`).
|
||||
|
||||
Add to the `DB` interface:
|
||||
- `TournamentLFGGroup`
|
||||
- `TournamentLFGGroupMember`
|
||||
- `TournamentLFGLike`
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: Feature File Structure
|
||||
|
||||
```
|
||||
app/features/tournament-lfg/
|
||||
├── TournamentLFGRepository.server.ts # Database operations
|
||||
├── tournament-lfg-types.ts # TypeScript types (LFGGroup, LFGGroupMember, etc.)
|
||||
├── tournament-lfg-schemas.server.ts # Zod validation schemas
|
||||
├── tournament-lfg-constants.ts # Constants (note max length, etc.)
|
||||
├── tournament-lfg-utils.ts # Utility functions
|
||||
├── routes/
|
||||
│ └── to.$id.looking.tsx # Main route (loader + action + component)
|
||||
├── loaders/
|
||||
│ └── to.$id.looking.server.ts # Data loader
|
||||
├── actions/
|
||||
│ └── to.$id.looking.server.ts # Action handler
|
||||
└── components/
|
||||
└── LFGGroupCard.tsx # Group card (mirrors SendouQ GroupCard pattern)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: Repository Implementation
|
||||
|
||||
**File**: `app/features/tournament-lfg/TournamentLFGRepository.server.ts`
|
||||
|
||||
Mirror `SQGroupRepository.server.ts` pattern. Key functions:
|
||||
|
||||
**Group Management:**
|
||||
- `findGroupsByTournamentId(tournamentId)` - Get all active groups
|
||||
- `addMember(groupId, { userId, role, stayAsSub? })` - Add member to group
|
||||
- `morphGroups({ survivingGroupId, otherGroupId })` - Merge two groups
|
||||
|
||||
**Likes:**
|
||||
- `addLike({ likerGroupId, targetGroupId })` - Add like
|
||||
- `deleteLike({ likerGroupId, targetGroupId })` - Remove like
|
||||
- `allLikesByGroupId(groupId)` - Get { given: [], received: [] }
|
||||
|
||||
**Member Management:**
|
||||
- `updateMemberNote({ groupId, userId, value })` - Update public note
|
||||
- `updateMemberRole({ userId, groupId, role })` - Change role
|
||||
- `updateStayAsSub({ groupId, userId, value })` - Toggle sub preference
|
||||
- `kickMember({ groupId, userId })` - Owner kicks member
|
||||
|
||||
**Tournament Integration:**
|
||||
- `cleanupForTournamentStart(tournamentId)` - Delete groups, preserve stayAsSub members
|
||||
- `getSubsForTournament(tournamentId)` - Get users who opted to stay as sub
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: Route Implementation
|
||||
|
||||
### 5.1 Route Registration
|
||||
|
||||
**File**: `app/routes.ts`
|
||||
|
||||
Add inside `/to/:id` children:
|
||||
```typescript
|
||||
route("looking", "features/tournament-lfg/routes/to.$id.looking.tsx"),
|
||||
```
|
||||
|
||||
### 5.2 Loader
|
||||
|
||||
**File**: `app/features/tournament-lfg/loaders/to.$id.looking.server.ts`
|
||||
|
||||
Returns:
|
||||
- `groups` - All visible groups (filtered by visibility)
|
||||
- `ownGroup` - User's current group (if any)
|
||||
- `likes` - { given: [], received: [] } for own group
|
||||
- `privateNotes` - User's private notes on other players (reuse from SendouQ)
|
||||
- `lastUpdated` - Timestamp for auto-refresh
|
||||
|
||||
### 5.3 Action
|
||||
|
||||
**File**: `app/features/tournament-lfg/actions/to.$id.looking.server.ts`
|
||||
|
||||
Actions:
|
||||
- `JOIN_QUEUE` - Create new group
|
||||
- `LIKE` / `UNLIKE` - Like/unlike another group
|
||||
- `ACCEPT` - Accept mutual like (triggers team creation/merge)
|
||||
- `LEAVE_GROUP` - Leave current group
|
||||
- `KICK_FROM_GROUP` - Owner kicks member
|
||||
- `GIVE_MANAGER` / `REMOVE_MANAGER` - Role management
|
||||
- `UPDATE_NOTE` - Update public note
|
||||
- `UPDATE_STAY_AS_SUB` - Toggle sub preference
|
||||
- `REFRESH_GROUP` - Refresh activity timestamp
|
||||
|
||||
**ACCEPT Action Flow:**
|
||||
1. Verify mutual like exists
|
||||
2. Check if either group has `tournamentTeamId`
|
||||
3. If neither: Create new `TournamentTeam` (use auto-generated name)
|
||||
4. Merge groups (use `morphGroups`)
|
||||
5. Add all members to `TournamentTeamMember`
|
||||
6. Send `TO_LFG_TEAM_FORMED` notification
|
||||
7. If team reaches `maxMembersPerTeam`: delete the LFG group
|
||||
|
||||
### 5.4 Component
|
||||
|
||||
**File**: `app/features/tournament-lfg/routes/to.$id.looking.tsx`
|
||||
|
||||
Structure (mirror `/q/looking`):
|
||||
- Three-column desktop layout: My Group | Groups | Invitations
|
||||
- Tab structure on mobile
|
||||
- Reuse `GroupCard` display pattern (weapons, VC, tier)
|
||||
- Reuse `MemberAdder` for quick-add trusted players
|
||||
- Reuse `GroupLeaver` component
|
||||
- Chat integration for groups with 2+ members
|
||||
- "Stay as sub" checkbox in join form
|
||||
|
||||
---
|
||||
|
||||
## Phase 6: Tournament Integration
|
||||
|
||||
### 6.1 Add "Looking" Tab
|
||||
|
||||
**File**: `app/features/tournament/routes/to.$id.tsx`
|
||||
|
||||
Add new `SubNavLink` (show only before tournament starts, not for invitationals):
|
||||
```tsx
|
||||
{!tournament.hasStarted && !tournament.isInvitational && (
|
||||
<SubNavLink to="looking">{t("tournament:tabs.looking")}</SubNavLink>
|
||||
)}
|
||||
```
|
||||
|
||||
### 6.2 Tournament.ts Getter
|
||||
|
||||
**File**: `app/features/tournament-bracket/core/Tournament.ts`
|
||||
|
||||
Add:
|
||||
```typescript
|
||||
get lfgEnabled() {
|
||||
return !this.isInvitational && !this.hasStarted && this.subsFeatureEnabled;
|
||||
}
|
||||
```
|
||||
|
||||
### 6.3 Auto-cleanup on Tournament Start
|
||||
|
||||
When tournament bracket starts, call `TournamentLFGRepository.cleanupForTournamentStart(tournamentId)`:
|
||||
- Delete all `TournamentLFGGroup` records
|
||||
- Preserve `TournamentLFGGroupMember` records where `stayAsSub = 1` for subs list
|
||||
|
||||
---
|
||||
|
||||
## Phase 7: Notifications
|
||||
|
||||
**File**: `app/features/notifications/notifications-types.ts`
|
||||
|
||||
Add three new notification types:
|
||||
|
||||
```typescript
|
||||
| NotificationItem<
|
||||
"TO_LFG_LIKED",
|
||||
{
|
||||
tournamentId: number;
|
||||
tournamentName: string;
|
||||
likerUsername: string;
|
||||
}
|
||||
>
|
||||
| NotificationItem<
|
||||
"TO_LFG_TEAM_FORMED",
|
||||
{
|
||||
tournamentId: number;
|
||||
tournamentName: string;
|
||||
teamName: string;
|
||||
tournamentTeamId: number;
|
||||
}
|
||||
>
|
||||
| NotificationItem<
|
||||
"TO_LFG_CHAT_MESSAGE",
|
||||
{
|
||||
tournamentId: number;
|
||||
tournamentName: string;
|
||||
teamName: string;
|
||||
tournamentTeamId: number;
|
||||
}
|
||||
>
|
||||
```
|
||||
|
||||
**File**: `app/features/notifications/notifications-utils.ts`
|
||||
|
||||
Add notification link handlers and icon mappings.
|
||||
|
||||
---
|
||||
|
||||
## Phase 8: Translations
|
||||
|
||||
**File**: `public/locales/en/tournament.json`
|
||||
|
||||
Add keys:
|
||||
- `tabs.looking`
|
||||
- `lfg.join.header`, `lfg.join.stayAsSub`, `lfg.join.visibility`
|
||||
- `lfg.myGroup.header`, `lfg.myGroup.empty`
|
||||
- `lfg.groups.header`, `lfg.groups.empty`
|
||||
- `lfg.invitations.header`, `lfg.invitations.empty`, `lfg.invitations.accept`
|
||||
- `lfg.actions.like`, `lfg.actions.unlike`, `lfg.actions.leave`
|
||||
|
||||
Run `npm run i18n:sync` after adding.
|
||||
|
||||
---
|
||||
|
||||
## Phase 9: Group Merging Logic
|
||||
|
||||
When two groups merge via ACCEPT:
|
||||
|
||||
| Group A has team | Group B has team | Result |
|
||||
|------------------|------------------|--------|
|
||||
| No | No | Create new TournamentTeam, both join it |
|
||||
| Yes | No | B's members join A's team |
|
||||
| No | Yes | A's members join B's team |
|
||||
| Yes | Yes | Accepting team absorbs liker team (accepting team name persists) |
|
||||
|
||||
Auto-generated team name format: `"<owner_username>'s Team"` (e.g., "Sendou's Team" - can be changed later on registration page)
|
||||
|
||||
After merge:
|
||||
- Combined group stays in LFG queue
|
||||
- When `maxMembersPerTeam` reached, group is auto-removed from queue
|
||||
|
||||
---
|
||||
|
||||
## Key Files to Reference
|
||||
|
||||
| Purpose | File |
|
||||
|---------|------|
|
||||
| Repository pattern | `app/features/sendouq/SQGroupRepository.server.ts` |
|
||||
| Route pattern | `app/features/sendouq/routes/q.looking.tsx` |
|
||||
| Action pattern | `app/features/sendouq/actions/q.looking.server.ts` |
|
||||
| GroupCard UI | `app/features/sendouq/components/GroupCard.tsx` |
|
||||
| Tournament tabs | `app/features/tournament/routes/to.$id.tsx` |
|
||||
| Team creation | `app/features/tournament/TournamentTeamRepository.server.ts` |
|
||||
| Notification types | `app/features/notifications/notifications-types.ts` |
|
||||
| Visibility type | `app/features/associations/associations-types.ts` |
|
||||
|
||||
---
|
||||
|
||||
## Implementation Order
|
||||
|
||||
1. Migration (100-tournament-lfg.js)
|
||||
2. Types (tables.ts + tournament-lfg-types.ts)
|
||||
3. Repository (TournamentLFGRepository.server.ts)
|
||||
4. Schemas (tournament-lfg-schemas.server.ts)
|
||||
5. Loader (to.$id.looking.server.ts)
|
||||
6. Action (to.$id.looking.server.ts)
|
||||
7. Route component (to.$id.looking.tsx)
|
||||
8. Tournament integration (tab, cleanup hook)
|
||||
9. Notifications (types + utils)
|
||||
10. Translations
|
||||
11. Testing
|
||||
|
||||
---
|
||||
|
||||
## Verification
|
||||
|
||||
1. **Manual Testing:**
|
||||
- Join LFG as solo player
|
||||
- Create group with 2 players
|
||||
- Like another group, verify notification sent
|
||||
- Accept mutual like, verify team created
|
||||
- Verify team appears on registration page
|
||||
- Test "stay as sub" checkbox
|
||||
- Test visibility filtering
|
||||
|
||||
2. **Unit Tests:**
|
||||
- Repository functions (create, merge, delete, likes)
|
||||
- Visibility filtering logic
|
||||
|
||||
3. **E2E Tests:**
|
||||
- Full flow: join -> like -> accept -> team formed
|
||||
- Leave group
|
||||
- Kick from group
|
||||
|
||||
4. **Run checks:**
|
||||
```bash
|
||||
npm run checks
|
||||
```
|
||||
@@ -1,166 +0,0 @@
|
||||
# Tournament LFG Feature Spec
|
||||
|
||||
## Overview
|
||||
|
||||
New `/to/:id/looking` route that provides SendouQ-style matchmaking for tournament team formation. Players and teams can find each other before tournament starts.
|
||||
|
||||
## Route
|
||||
|
||||
`/to/:id/looking` (new route, mirrors `/q/looking`)
|
||||
|
||||
## Data Model
|
||||
|
||||
Separate tables from SendouQ (cleaner separation):
|
||||
|
||||
### TournamentLFGGroup
|
||||
|
||||
| Column | Type | Description |
|
||||
|--------|------|-------------|
|
||||
| id | number | Primary key |
|
||||
| tournamentTeamId | number | null | FK to TournamentTeam |
|
||||
| visibility | string | JSON visibility info (/scrims style) |
|
||||
| chatCode | string | Unique room code for group chat |
|
||||
| createdAt | number | Timestamp |
|
||||
|
||||
### TournamentLFGGroupMember
|
||||
|
||||
| Column | Type | Description |
|
||||
|--------|------|-------------|
|
||||
| groupId | number | FK to TournamentLFGGroup |
|
||||
| userId | number | FK to User |
|
||||
| role | string | OWNER / MANAGER / REGULAR |
|
||||
| note | string | Public note visible to group members |
|
||||
| stayAsSub | boolean | Convert to sub if team not formed by start |
|
||||
| createdAt | number | Timestamp |
|
||||
|
||||
### TournamentLFGLike
|
||||
|
||||
| Column | Type | Description |
|
||||
|--------|------|-------------|
|
||||
| likerGroupId | number | FK to TournamentLFGGroup |
|
||||
| targetGroupId | number | FK to TournamentLFGGroup |
|
||||
| createdAt | number | Timestamp |
|
||||
|
||||
### TournamentSub
|
||||
|
||||
Redundant, removed.
|
||||
|
||||
## Who Can Join
|
||||
|
||||
- **Solo players** - Looking for a team
|
||||
- **Partial groups (2-3 players)** - Looking for more members
|
||||
- **Already-registered tournament teams** - Recruiting up to `maxMembersPerTeam`
|
||||
|
||||
## Features
|
||||
|
||||
### Joining the Queue
|
||||
|
||||
- Players reuse weapon/VC data from `/q/settings` (`User.qWeaponPool`, `User.vc`, `User.languages`)
|
||||
- Checkbox on join: "Add me as sub if I don't find a team"
|
||||
- Support for notes (like SendouQ public member notes)
|
||||
- Uses schema based SendouForm (forms.md for details)
|
||||
|
||||
### Visibility System (Scrims-style)
|
||||
|
||||
- **Base visibility**: team/org/public
|
||||
- **Not-found visibility**: Time-delayed expansion if no match found
|
||||
- Uses existing `AssociationVisibility` system from scrims
|
||||
|
||||
### Likes & Matching
|
||||
|
||||
1. Players/groups can like each other
|
||||
2. Target group receives `TO_LFG_LIKED` notification
|
||||
3. On mutual like, accepting party sees accept button
|
||||
4. Accept click triggers:
|
||||
- If neither party is registered team → create a team (some default name is used)
|
||||
- If one party is registered team → Other party joins that team
|
||||
- If both parties are teams, the accepting team absorbs the liker team (accepting team's name is used for the new merged team)
|
||||
5. After merge, combined group stays in queue to recruit more members
|
||||
|
||||
### Team Formation
|
||||
|
||||
- **Immediate registration**: When first two players merge, default name is used
|
||||
- Newly formed team stays in LFG queue
|
||||
- Teams can grow up to `maxMembersPerTeam` (typically 6 for 4v4)
|
||||
- When `maxMembersPerTeam` is reached, team is automatically removed from the queue
|
||||
- Solo players liking registered teams get absorbed as new members
|
||||
|
||||
### Tournament Start Auto-Cleanup
|
||||
|
||||
When tournament starts:
|
||||
1. All unregistered LFG groups are deleted
|
||||
2. Players who checked "stay as sub" are shown in a simple list "`TournamentLFGGroupMember` reused here even if they technically are no longer members of anything)
|
||||
3. Their sub data uses existing `/q/settings` weapon/VC preferences
|
||||
|
||||
## Notifications
|
||||
|
||||
| Type | When | Meta |
|
||||
|------|------|------|
|
||||
| `TO_LFG_LIKED` | Someone likes your group | `{ tournamentId, tournamentName, likerUsername }` |
|
||||
| `TO_LFG_TEAM_FORMED` | You join/form a team via LFG | `{ tournamentId, tournamentName, teamName, tournamentTeamId }` |
|
||||
| `TO_LFG_CHAT_MESSAGE` | Chat message sent | `{ tournamentId, tournamentName, teamName, tournamentTeamId }` |
|
||||
|
||||
## UI
|
||||
|
||||
### Reuse from `/q/looking`
|
||||
|
||||
- `GroupCard` component (weapons, VC, tier display)
|
||||
- Tab structure (My Group, Groups, Invitations)
|
||||
- `MemberAdder` component (invite link, quick add), note for these same invite link and quick add endpoint is used as on /to/:id/register page
|
||||
- `GroupLeaver` component
|
||||
- Private user notes system
|
||||
|
||||
### Tab Structure
|
||||
|
||||
1. **My Group** - Current group members, invite link, leave button, chat (if 2+ members)
|
||||
2. **Groups** - Other groups looking, with like/unlike buttons
|
||||
3. **Invitations** - Groups that have liked your group, with accept/decline
|
||||
|
||||
### Accept Flow
|
||||
|
||||
Simple button click (SendouQ-style)
|
||||
|
||||
## Files to Create/Modify
|
||||
|
||||
### New Files
|
||||
|
||||
```
|
||||
app/features/tournament-lfg/
|
||||
├── core/
|
||||
│ └── TournamentLFG.server.ts # Main class (like SendouQ.server.ts)
|
||||
├── routes/
|
||||
│ ├── to.$id.looking.tsx # Main LFG page
|
||||
│ └── to.$id.looking.new.tsx # Join LFG form (if needed)
|
||||
├── loaders/
|
||||
│ └── to.$id.looking.server.ts # Data loader
|
||||
├── actions/
|
||||
│ └── to.$id.looking.server.ts # Action handler
|
||||
├── components/
|
||||
│ └── (reuse from sendouq where possible)
|
||||
├── TournamentLFGRepository.server.ts # Database queries
|
||||
├── tournament-lfg-types.ts # TypeScript types
|
||||
├── tournament-lfg-schemas.server.ts # Zod validation
|
||||
└── tournament-lfg-constants.ts # Constants
|
||||
```
|
||||
|
||||
### Migrations
|
||||
|
||||
```
|
||||
migrations/XXX-tournament-lfg.js # Create new tables
|
||||
```
|
||||
|
||||
### Modified Files
|
||||
|
||||
- `app/routes.ts` - Add new route
|
||||
- `app/features/notifications/notifications-types.ts` - Add new notification types
|
||||
- `app/features/tournament-bracket/core/Tournament.ts` - Add LFG-related getters
|
||||
- `app/features/tournament/components/TournamentTabs.tsx` - Add "Looking" tab
|
||||
|
||||
## Differences to SendouQ
|
||||
|
||||
- no invite code (tournament teams have their own invite code by default)
|
||||
- no expiredAt
|
||||
|
||||
## Open Questions
|
||||
|
||||
- How we should define the autogenerated tournament team name?
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
/* https://github.com/tldraw/tldraw/blob/2352985e949d14270fc89dc60144239c37c8ff91/packages/tldraw/src/hooks/useStylesheet.ts */
|
||||
@import url("https://fonts.googleapis.com/css2?family=Caveat+Brush&family=Source+Code+Pro&family=Source+Sans+Pro&family=Crimson+Pro&display=block");
|
||||
@import url("tldraw/tldraw.css");
|
||||
@import url("@tldraw/tldraw/tldraw.css");
|
||||
|
||||
@font-face {
|
||||
font-family: "Recursive";
|
||||
|
||||
@@ -15,7 +15,7 @@ import { IS_E2E_TEST_RUN } from "~/utils/e2e";
|
||||
* console.log(Seasons.list[0].starts); // Logs the start date of the first season
|
||||
*/
|
||||
export const list =
|
||||
// when we do npm run setup NODE_ENV is not set -> use test seasons
|
||||
// when we do pnpm run setup NODE_ENV is not set -> use test seasons
|
||||
!process.env.NODE_ENV ||
|
||||
IS_E2E_TEST_RUN ||
|
||||
// this gets checked when the project is running
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Auto-generated team name data from adj.json and sub.json
|
||||
// Regenerate with: npx tsx scripts/generate-team-names.ts
|
||||
// Regenerate with: pnpm exec tsx scripts/generate-team-names.ts
|
||||
|
||||
export const ADJECTIVES = [
|
||||
"Inkless",
|
||||
|
||||
@@ -567,7 +567,7 @@ NOTE: before adding a new one, verify one does not already exist.
|
||||
- Select options: `options.fieldName.value`
|
||||
- Mode names: `modes.SZ`, `modes.TC`, etc.
|
||||
|
||||
Run `npm run i18n:sync` after adding English translations to initialize other language files.
|
||||
Run `pnpm run i18n:sync` after adding English translations to initialize other language files.
|
||||
|
||||
## E2E Testing
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ Guides on how to do different things when developing sendou.ink
|
||||
|
||||
## Fix style/lint errors (Biome)
|
||||
|
||||
Run the `npm run biome:fix` command. Also you might want to set up Biome as an extension to your IDE and run automatically when you save a file.
|
||||
Run the `pnpm run biome:fix` command. Also you might want to set up Biome as an extension to your IDE and run automatically when you save a file.
|
||||
|
||||
## Add a new database migration
|
||||
|
||||
@@ -22,8 +22,8 @@ export function up(db) {
|
||||
Note: No need to implement the "down" migration
|
||||
|
||||
3) Update the typings in `app/db/tables.ts`
|
||||
4) Run `npm run migrate up` to apply your migration
|
||||
4) Set env var `DB_PATH=db-test.sqlite3` in `.env` file & run the `npm run migrate up` command again to update the database used in unit tests
|
||||
4) Run `pnpm run migrate up` to apply your migration
|
||||
4) Set env var `DB_PATH=db-test.sqlite3` in `.env` file & run the `pnpm run migrate up` command again to update the database used in unit tests
|
||||
|
||||
## Add a new translation string
|
||||
|
||||
@@ -55,4 +55,4 @@ When utilizing feature specific translations ensure the json is loaded. This is
|
||||
|
||||
### Sync
|
||||
|
||||
Use the `npm run i18n:sync` command to sync translation jsons with English (removing and adding keys for each language as needed). There is not currently a check in the pipeline that this was done but it should always be ran when a new translation string has been added or removed.
|
||||
Use the `pnpm run i18n:sync` command to sync translation jsons with English (removing and adding keys for each language as needed). There is not currently a check in the pipeline that this was done but it should always be ran when a new translation string has been added or removed.
|
||||
|
||||
@@ -7,19 +7,19 @@ Note: These are mostly useful if you are running the site in production as an ad
|
||||
## Add new badge to the database
|
||||
|
||||
```bash
|
||||
npx tsx scripts/add-badge.ts fire_green "Octofin Eliteboard"
|
||||
pnpm exec tsx scripts/add-badge.ts fire_green "Octofin Eliteboard"
|
||||
```
|
||||
|
||||
## Rename display name of a badge
|
||||
|
||||
```bash
|
||||
npx tsx scripts/rename-badge.ts 10 "New 4v4 Sundaes"
|
||||
pnpm exec tsx scripts/rename-badge.ts 10 "New 4v4 Sundaes"
|
||||
```
|
||||
|
||||
## Add many badge owners
|
||||
|
||||
```bash
|
||||
npx tsx scripts/add-badge-winners.ts 10 "750705955909664791,79237403620945920"
|
||||
pnpm exec tsx scripts/add-badge-winners.ts 10 "750705955909664791,79237403620945920"
|
||||
```
|
||||
|
||||
## Converting gifs (badges) to thumbnail (.png)
|
||||
@@ -33,7 +33,7 @@ sips -s format png ./sundae.gif --out .
|
||||
While in the folder with the images:
|
||||
|
||||
```bash
|
||||
for i in *.png; do npx @squoosh/cli --avif '{"cqLevel":33,"cqAlphaLevel":-1,"denoiseLevel":0,"tileColsLog2":0,"tileRowsLog2":0,"speed":6,"subsample":1,"chromaDeltaQ":false,"sharpness":0,"tune":0}' $i; done
|
||||
for i in *.png; do pnpm dlx @squoosh/cli --avif '{"cqLevel":33,"cqAlphaLevel":-1,"denoiseLevel":0,"tileColsLog2":0,"tileRowsLog2":0,"speed":6,"subsample":1,"chromaDeltaQ":false,"sharpness":0,"tune":0}' $i; done
|
||||
```
|
||||
|
||||
Note: it only works with Node 16.
|
||||
@@ -48,19 +48,19 @@ Note: it only works with Node 16.
|
||||
1. Update `CURRENT_PATCH` constants
|
||||
1. Update `PATCHES` constant with the late patch + remove the oldest
|
||||
1. Update the stage list in `stage-ids.ts` and `create-misc-json.ts`. Add images from Lean's repository and avify them.
|
||||
1. `npx tsx scripts/create-misc-json.ts`
|
||||
1. `npx tsx scripts/create-gear-json.ts`
|
||||
1. `npx tsx scripts/create-analyzer-json.ts`
|
||||
1. `pnpm exec tsx scripts/create-misc-json.ts`
|
||||
1. `pnpm exec tsx scripts/create-gear-json.ts`
|
||||
1. `pnpm exec tsx scripts/create-analyzer-json.ts`
|
||||
8a. Double check that no hard-coded special damages changed
|
||||
1. `npx tsx scripts/create-object-dmg-json.ts`
|
||||
1. `pnpm exec tsx scripts/create-object-dmg-json.ts`
|
||||
1. Fill new weapon IDs by category to `weapon-ids.ts` (easy to take from the diff of English weapons.json)
|
||||
1. Get gear IDs for each slot from /output folder and update `gear-ids.ts`.
|
||||
1. Replace `object-dmg.json` with the `object-dmg.json` in /output folder
|
||||
1. Replace `weapon-params.ts` with the `params.json` in /output folder
|
||||
1. Delete all images inside `main-weapons`, `main-weapons-outlined`, `main-weapons-outlined-2` and `gear` folders.
|
||||
1. Replace with images from Lean's repository.
|
||||
1. Run the `npx tsx scripts/replace-img-names.ts` command
|
||||
1. Run the `npx tsx scripts/replace-weapon-names.ts` command
|
||||
1. Run the `pnpm exec tsx scripts/replace-img-names.ts` command
|
||||
1. Run the `pnpm exec tsx scripts/replace-weapon-names.ts` command
|
||||
1. Run the .avif generating command in each image folder.
|
||||
2. Update manually any languages that use English `gear.json` and `weapons.json` files
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ This guide explains how to profile and optimize slow routes in sendou.ink.
|
||||
Install autocannon globally:
|
||||
|
||||
```bash
|
||||
npm install -g autocannon
|
||||
pnpm add -g autocannon
|
||||
```
|
||||
|
||||
## Step 1: Identify Slow Routes
|
||||
@@ -100,7 +100,7 @@ db.close();
|
||||
Run with:
|
||||
|
||||
```bash
|
||||
npx tsx profile-query.ts
|
||||
pnpm exec tsx profile-query.ts
|
||||
```
|
||||
|
||||
## Step 3: Analyze Query Plans
|
||||
@@ -221,7 +221,7 @@ After applying fixes, restart the dev server and re-run autocannon:
|
||||
|
||||
```bash
|
||||
# Restart server to pick up changes
|
||||
# (Ctrl+C and npm run dev)
|
||||
# (Ctrl+C and pnpm dev)
|
||||
|
||||
# Re-benchmark
|
||||
autocannon -c 10 -d 10 http://localhost:301/leaderboards
|
||||
@@ -246,7 +246,7 @@ console.log(`Rows: ${result.length}`);
|
||||
db.close();
|
||||
EOF
|
||||
|
||||
npx tsx profile.ts
|
||||
pnpm exec tsx profile.ts
|
||||
|
||||
# 3. Clean up
|
||||
rm profile.ts
|
||||
|
||||
@@ -96,7 +96,7 @@ async function globalSetup(_config: FullConfig) {
|
||||
// Use port 6173 as the base - tests will rewrite URLs as needed
|
||||
// biome-ignore lint/suspicious/noConsole: CLI script output
|
||||
console.log("Building the application...");
|
||||
execSync("npm run build", {
|
||||
execSync("pnpm run build", {
|
||||
stdio: "inherit",
|
||||
env: {
|
||||
...process.env,
|
||||
@@ -125,13 +125,13 @@ async function globalSetup(_config: FullConfig) {
|
||||
if (!fs.existsSync(dbPath)) {
|
||||
// biome-ignore lint/suspicious/noConsole: CLI script output
|
||||
console.log(`Setting up database for worker ${i}: ${dbPath}`);
|
||||
execSync(`DB_PATH=${dbPath} npm run migrate up`, { stdio: "inherit" });
|
||||
execSync(`DB_PATH=${dbPath} pnpm run migrate up`, { stdio: "inherit" });
|
||||
}
|
||||
|
||||
// Start server
|
||||
// biome-ignore lint/suspicious/noConsole: CLI script output
|
||||
console.log(`Starting server for worker ${i} on port ${port}...`);
|
||||
const serverProcess = spawn("npm", ["start"], {
|
||||
const serverProcess = spawn("pnpm", ["start"], {
|
||||
env: {
|
||||
...process.env,
|
||||
DB_PATH: dbPath,
|
||||
|
||||
15164
package-lock.json
generated
15164
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
197
package.json
197
package.json
@@ -2,125 +2,132 @@
|
||||
"name": "sendou.ink",
|
||||
"version": "3.0.0",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@10.33.0",
|
||||
"sideEffects": false,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"deploy": "npm ci && npm run build",
|
||||
"deploy": "pnpm install --frozen-lockfile && pnpm run build",
|
||||
"build": "react-router build",
|
||||
"dev": "cross-env DB_PATH=db.sqlite3 npm run migrate up && npm run setup && react-router dev --host",
|
||||
"dev": "cross-env DB_PATH=db.sqlite3 pnpm run migrate up && pnpm run setup && react-router dev --host",
|
||||
"dev:prod": "cross-env DB_PATH=db-prod.sqlite3 VITE_PROD_MODE=true react-router dev --host",
|
||||
"start": "npm run migrate up && react-router-serve build/server/index.js",
|
||||
"start": "pnpm run migrate up && react-router-serve build/server/index.js",
|
||||
"migrate": "ley",
|
||||
"migrate:prod": "cross-env DB_PATH=db-prod.sqlite3 npm run migrate up",
|
||||
"migrate:prod": "cross-env DB_PATH=db-prod.sqlite3 pnpm run migrate up",
|
||||
"check-translation-jsons": "node --experimental-strip-types scripts/check-translation-jsons.ts",
|
||||
"check-translation-jsons:no-write": "node --experimental-strip-types scripts/check-translation-jsons.ts --no-write",
|
||||
"check-homemade-badges": "node --experimental-strip-types scripts/check-homemade-badges.ts",
|
||||
"check-articles": "tsx scripts/check-articles.ts",
|
||||
"refresh-prod-db": "node --experimental-strip-types scripts/refresh-prod-db.ts && npm run migrate:prod",
|
||||
"biome:check": "npx @biomejs/biome check --error-on-warnings .",
|
||||
"biome:fix": "npx @biomejs/biome check --error-on-warnings --write .",
|
||||
"biome:fix:unsafe": "npx @biomejs/biome check --error-on-warnings --write --unsafe .",
|
||||
"refresh-prod-db": "node --experimental-strip-types scripts/refresh-prod-db.ts && pnpm run migrate:prod",
|
||||
"biome:check": "biome check --error-on-warnings .",
|
||||
"biome:fix": "biome check --error-on-warnings --write .",
|
||||
"biome:fix:unsafe": "biome check --error-on-warnings --write --unsafe .",
|
||||
"typecheck": "react-router typegen && tsc --noEmit",
|
||||
"test:unit:browser": "cross-env VITE_SITE_DOMAIN=http://localhost:5173 BROWSER_HEADLESS=true vitest --silent=passed-only run",
|
||||
"test:browser:ui": "cross-env VITE_SITE_DOMAIN=http://localhost:5173 vitest --silent=passed-only --project browser",
|
||||
"test:unit:browser:ui": "cross-env VITE_SITE_DOMAIN=http://localhost:5173 vitest --silent=passed-only",
|
||||
"test:e2e": "npx playwright test",
|
||||
"test:e2e:flaky-detect": "npx playwright test --repeat-each=10 --max-failures=1",
|
||||
"test:e2e:generate-seeds": "cross-env DB_PATH=db-test.sqlite3 npm run migrate up && cross-env DB_PATH=db-test.sqlite3 npx vite-node scripts/generate-e2e-seed-dbs.ts",
|
||||
"test:e2e": "playwright test",
|
||||
"test:e2e:flaky-detect": "playwright test --repeat-each=10 --max-failures=1",
|
||||
"test:e2e:generate-seeds": "cross-env DB_PATH=db-test.sqlite3 pnpm run migrate up && cross-env DB_PATH=db-test.sqlite3 vite-node scripts/generate-e2e-seed-dbs.ts",
|
||||
"check-test-db-migrations": "node --experimental-strip-types scripts/check-test-db-migrations.ts",
|
||||
"checks": "npm run biome:fix && npm run test:unit:browser && npm run check-translation-jsons && npm run typecheck && npm run knip && npm run check-test-db-migrations",
|
||||
"checks": "pnpm run biome:fix && pnpm run test:unit:browser && pnpm run check-translation-jsons && pnpm run typecheck && pnpm run knip && pnpm run check-test-db-migrations",
|
||||
"setup": "cross-env DB_PATH=db.sqlite3 vite-node ./scripts/setup.ts",
|
||||
"i18n:sync": "i18next-locales-sync -e true -p en -s da de es-ES es-US fr-CA fr-EU he it ja ko nl pl pt-BR ru zh -l locales && npm run biome:fix",
|
||||
"i18n:sync": "i18next-locales-sync -e true -p en -s da de es-ES es-US fr-CA fr-EU he it ja ko nl pl pt-BR ru zh -l locales && pnpm run biome:fix",
|
||||
"knip": "knip",
|
||||
"sync-weapon-params": "tsx scripts/sync-weapon-params.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.1019.0",
|
||||
"@aws-sdk/lib-storage": "^3.1019.0",
|
||||
"@date-fns/tz": "^1.4.1",
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@dnd-kit/modifiers": "^9.0.0",
|
||||
"@dnd-kit/sortable": "^10.0.0",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
"@epic-web/cachified": "^5.6.2",
|
||||
"@faker-js/faker": "^10.4.0",
|
||||
"@formatjs/intl-durationformat": "^0.10.3",
|
||||
"@internationalized/date": "^3.12.0",
|
||||
"@react-router/node": "^7.13.2",
|
||||
"@react-router/serve": "^7.13.2",
|
||||
"@remix-run/form-data-parser": "^0.16.0",
|
||||
"@tldraw/tldraw": "^3.12.1",
|
||||
"@zumer/snapdom": "^2.7.0",
|
||||
"aws-sdk": "^2.1693.0",
|
||||
"better-sqlite3": "^12.8.0",
|
||||
"clsx": "^2.1.1",
|
||||
"compressorjs": "^1.2.1",
|
||||
"date-fns": "^4.1.0",
|
||||
"edmonds-blossom-fixed": "^1.0.1",
|
||||
"gray-matter": "^4.0.3",
|
||||
"i18next": "^25.10.10",
|
||||
"i18next-browser-languagedetector": "^8.2.1",
|
||||
"i18next-http-backend": "^3.0.2",
|
||||
"ics": "^3.11.0",
|
||||
"isbot": "^5.1.36",
|
||||
"jsoncrush": "^1.1.8",
|
||||
"kysely": "^0.28.14",
|
||||
"lru-cache": "^11.2.7",
|
||||
"lucide-react": "^1.7.0",
|
||||
"markdown-to-jsx": "^9.7.13",
|
||||
"nanoid": "^5.1.7",
|
||||
"neverthrow": "^8.2.0",
|
||||
"@aws-sdk/client-s3": "3.1019.0",
|
||||
"@aws-sdk/lib-storage": "3.1019.0",
|
||||
"@date-fns/tz": "1.4.1",
|
||||
"@dnd-kit/core": "6.3.1",
|
||||
"@dnd-kit/modifiers": "9.0.0",
|
||||
"@dnd-kit/sortable": "10.0.0",
|
||||
"@dnd-kit/utilities": "3.2.2",
|
||||
"@epic-web/cachified": "5.6.2",
|
||||
"@faker-js/faker": "10.4.0",
|
||||
"@formatjs/intl-durationformat": "0.10.3",
|
||||
"@internationalized/date": "3.12.0",
|
||||
"@react-router/node": "7.13.2",
|
||||
"@react-router/serve": "7.13.2",
|
||||
"@remix-run/form-data-parser": "0.16.0",
|
||||
"@tldraw/tldraw": "3.12.1",
|
||||
"@zumer/snapdom": "2.7.0",
|
||||
"aws-sdk": "2.1693.0",
|
||||
"better-sqlite3": "12.8.0",
|
||||
"clsx": "2.1.1",
|
||||
"compressorjs": "1.2.1",
|
||||
"date-fns": "4.1.0",
|
||||
"edmonds-blossom-fixed": "1.0.1",
|
||||
"gray-matter": "4.0.3",
|
||||
"i18next": "25.10.10",
|
||||
"i18next-browser-languagedetector": "8.2.1",
|
||||
"i18next-http-backend": "3.0.2",
|
||||
"ics": "3.11.0",
|
||||
"isbot": "5.1.36",
|
||||
"jsoncrush": "1.1.8",
|
||||
"kysely": "0.28.14",
|
||||
"lru-cache": "11.2.7",
|
||||
"lucide-react": "1.7.0",
|
||||
"markdown-to-jsx": "9.7.13",
|
||||
"nanoid": "5.1.7",
|
||||
"neverthrow": "8.2.0",
|
||||
"node-cron": "4.2.1",
|
||||
"nprogress": "^0.2.0",
|
||||
"openskill": "^4.1.1",
|
||||
"p-limit": "^7.3.0",
|
||||
"partysocket": "^1.1.16",
|
||||
"react": "^19.2.4",
|
||||
"react-aria-components": "^1.16.0",
|
||||
"react-charts": "^3.0.0-beta.57",
|
||||
"react-dom": "^19.2.4",
|
||||
"react-error-boundary": "^6.1.1",
|
||||
"nprogress": "0.2.0",
|
||||
"openskill": "4.1.1",
|
||||
"p-limit": "7.3.0",
|
||||
"partysocket": "1.1.16",
|
||||
"react": "19.2.4",
|
||||
"react-aria-components": "1.16.0",
|
||||
"react-charts": "3.0.0-beta.57",
|
||||
"react-dom": "19.2.4",
|
||||
"react-error-boundary": "6.1.1",
|
||||
"react-flip-toolkit": "7.2.4",
|
||||
"react-i18next": "^16.5.8",
|
||||
"react-router": "^7.13.2",
|
||||
"react-use": "^17.6.0",
|
||||
"react-use-draggable-scroll": "^0.4.7",
|
||||
"remeda": "^2.33.6",
|
||||
"remix-auth": "^4.2.0",
|
||||
"remix-auth-oauth2": "^3.4.1",
|
||||
"remix-i18next": "^7.4.2",
|
||||
"slugify": "^1.6.8",
|
||||
"swr": "^2.4.1",
|
||||
"web-push": "^3.6.7",
|
||||
"zod": "^4.3.6"
|
||||
"react-i18next": "16.5.8",
|
||||
"react-router": "7.13.2",
|
||||
"react-use": "17.6.0",
|
||||
"react-use-draggable-scroll": "0.4.7",
|
||||
"remeda": "2.33.6",
|
||||
"remix-auth": "4.2.0",
|
||||
"remix-auth-oauth2": "3.4.1",
|
||||
"remix-i18next": "7.4.2",
|
||||
"slugify": "1.6.8",
|
||||
"swr": "2.4.1",
|
||||
"web-push": "3.6.7",
|
||||
"zod": "4.3.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "2.4.9",
|
||||
"@playwright/test": "^1.58.2",
|
||||
"@react-router/dev": "^7.13.2",
|
||||
"@types/better-sqlite3": "^7.6.13",
|
||||
"@types/node": "^25.5.0",
|
||||
"@types/node-cron": "^3.0.11",
|
||||
"@types/nprogress": "^0.2.3",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@types/web-push": "^3.6.4",
|
||||
"@vitest/browser-playwright": "^4.1.2",
|
||||
"@vitest/ui": "^4.1.2",
|
||||
"babel-plugin-react-compiler": "^19.1.0-rc.2",
|
||||
"cross-env": "^10.1.0",
|
||||
"dotenv": "^17.3.1",
|
||||
"i18next-locales-sync": "^2.1.1",
|
||||
"knip": "^6.0.6",
|
||||
"ley": "^0.8.1",
|
||||
"sql-formatter": "^15.7.2",
|
||||
"tsx": "^4.21.0",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^7.3.1",
|
||||
"vite-node": "^5.3.0",
|
||||
"vite-plugin-babel": "^1.6.0",
|
||||
"vite-tsconfig-paths": "^6.1.1",
|
||||
"vitest": "^4.1.2",
|
||||
"vitest-browser-react": "^2.1.0"
|
||||
"@playwright/test": "1.58.2",
|
||||
"@react-router/dev": "7.13.2",
|
||||
"@types/better-sqlite3": "7.6.13",
|
||||
"@types/node": "25.5.0",
|
||||
"@types/node-cron": "3.0.11",
|
||||
"@types/nprogress": "0.2.3",
|
||||
"@types/react": "19.2.14",
|
||||
"@types/react-dom": "19.2.3",
|
||||
"@types/web-push": "3.6.4",
|
||||
"@vitest/browser-playwright": "4.1.2",
|
||||
"@vitest/ui": "4.1.2",
|
||||
"babel-plugin-react-compiler": "19.1.0-rc.2",
|
||||
"cross-env": "10.1.0",
|
||||
"dotenv": "17.3.1",
|
||||
"i18next-locales-sync": "2.1.1",
|
||||
"knip": "6.0.6",
|
||||
"ley": "0.8.1",
|
||||
"sql-formatter": "15.7.2",
|
||||
"tsx": "4.21.0",
|
||||
"typescript": "5.9.3",
|
||||
"vite": "7.3.1",
|
||||
"vite-node": "5.3.0",
|
||||
"vite-plugin-babel": "1.6.0",
|
||||
"vite-tsconfig-paths": "6.1.1",
|
||||
"vitest": "4.1.2",
|
||||
"vitest-browser-react": "2.1.0"
|
||||
},
|
||||
"pnpm": {
|
||||
"onlyBuiltDependencies": [
|
||||
"better-sqlite3",
|
||||
"esbuild"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
10600
pnpm-lock.yaml
generated
Normal file
10600
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@
|
||||
/**
|
||||
* Backfill Tournament Tiers Script
|
||||
*
|
||||
* Run with: npx tsx scripts/backfill-tournament-tiers.ts
|
||||
* Run with: pnpm exec tsx scripts/backfill-tournament-tiers.ts
|
||||
*
|
||||
* Retroactively calculates and sets tiers for all finalized tournaments,
|
||||
* then populates series tier history based on those tiers.
|
||||
|
||||
@@ -57,7 +57,7 @@ for (const dbPath of DB_FILES) {
|
||||
|
||||
if (hasErrors) {
|
||||
console.error(
|
||||
"\nRun `npm run test:e2e:generate-seeds` to regenerate test databases.",
|
||||
"\nRun `pnpm run test:e2e:generate-seeds` to regenerate test databases.",
|
||||
);
|
||||
process.exit(1);
|
||||
} else {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// for testing use the command `npx tsx ./scripts/create-league-divisions.ts 6 'https://gist.githubusercontent.com/sendou-ink/38aa4d5d8426035ce178c09598ae627f/raw/17be9bb53a9f017c2097d0624f365d1c5a029f01/league.csv'`
|
||||
// for testing use the command `pnpm exec tsx ./scripts/create-league-divisions.ts 6 'https://gist.githubusercontent.com/sendou-ink/38aa4d5d8426035ce178c09598ae627f/raw/17be9bb53a9f017c2097d0624f365d1c5a029f01/league.csv'`
|
||||
|
||||
import "dotenv/config";
|
||||
import { z } from "zod";
|
||||
|
||||
@@ -42,7 +42,7 @@ async function generatePreSeededDatabases() {
|
||||
fs.copyFileSync(baseDbPath, outputPath);
|
||||
|
||||
execSync(
|
||||
`npx vite-node scripts/seed-single-variation.ts -- ${variation} ${outputPath}`,
|
||||
`pnpm exec vite-node scripts/seed-single-variation.ts -- ${variation} ${outputPath}`,
|
||||
{ stdio: "inherit" },
|
||||
);
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
/**
|
||||
* Tournament Tiering Experiment Script
|
||||
*
|
||||
* Run with: npx tsx scripts/tournament-tiers-experiment.ts
|
||||
* Run with: pnpm exec tsx scripts/tournament-tiers-experiment.ts
|
||||
*
|
||||
* Calculates tournament tiers based on top teams' average SeedingSkill.
|
||||
* Tweak the THRESHOLDS object to experiment with different tier distributions.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// usage: npx tsx ./scripts/unlock-league-matches.ts <parentTournamentId>
|
||||
// usage: pnpm exec tsx ./scripts/unlock-league-matches.ts <parentTournamentId>
|
||||
import "dotenv/config";
|
||||
import { sql } from "~/db/sql";
|
||||
import invariant from "~/utils/invariant";
|
||||
|
||||
Reference in New Issue
Block a user