diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 0dcf2f0..da08252 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -31,6 +31,7 @@ See [README.md](README.md) for a full architecture map. Key files: - **Re-skin:** Edit colors in `_variables.scss` only. - **Add a page:** Create component folder → add route in `app.routes.ts` → add nav link (if needed). - **Update content (dev):** Run the API, then use Swagger UI at [http://localhost:8000/docs](http://localhost:8000/docs) to create/update content. -- **Update content (seed):** Edit [backend/seed.py](backend/seed.py) and run `docker compose exec api python seed.py`. -- **Run the app:** `docker compose up --build` → [localhost:4200](http://localhost:4200). +- **Update content (seed):** Edit [backend/seed.py](backend/seed.py) and restart the API (autoseed runs on empty DB), or hit `curl -X POST http://localhost:8000/api/admin/reset` to re-seed. +- **Run the app (dev):** From the `backend/` directory, set `KMTN_DATABASE_URL=sqlite+aiosqlite:///./kmountain.db`, then `uvicorn app.main:app --reload` + `ng serve` in parallel. Autoseed populates data on first run. +- **Run the app (Docker):** `docker compose up --build` → [localhost:4200](http://localhost:4200). - **Build:** `ng build` → `dist/`, then `docker compose up --build -d` for full stack. diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 3deed1b..78cebb2 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -1,10 +1,15 @@ { "permissions": { "allow": [ - "*" + "Bash(*)", + "Read(*)", + "Edit(*)", + "Write(*)", + "Glob(*)", + "Grep(*)" ], "additionalDirectories": [ "/workspaces/web_app/.vscode" ] } -} \ No newline at end of file +} diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 43fbde7..8fbf7cf 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,8 +1,6 @@ -FROM ubuntu:latest +FROM python:3.12-alpine -RUN apt update -y && apt install -y git npm python3 bash podman podman-compose fuse-overlayfs +RUN apk update && apk add git nodejs npm # Install Angular.js via NPM -RUN npm install -g @angular/cli@21.2.14 - -# ENV SHELL /bin/bash \ No newline at end of file +RUN npm install -g @angular/cli@21.2.14 \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 7a9e218..28b2c57 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -7,7 +7,8 @@ "vscode": { "extensions": [ "Angular.ng-template", - "magnobiet.sass-extension-pack" + "magnobiet.sass-extension-pack", + "Anthropic.claude-code" ] } } diff --git a/.gitignore b/.gitignore index 882b9ac..68a7c15 100644 --- a/.gitignore +++ b/.gitignore @@ -45,3 +45,6 @@ Thumbs.db # Environment files .env + +# Dev SQLite database +backend/kmountain.db \ No newline at end of file diff --git a/README.md b/README.md index b28ff77..7561704 100644 --- a/README.md +++ b/README.md @@ -6,27 +6,19 @@ Renders a hero page, about page, broadcast schedule, donation tiers, upcoming ev ## Quick Start -### Option A — Docker Compose (recommended) +### Option A — Dev Container (recommended) -```bash -# Start all services -docker compose up --build - -# Seed the database (first run only) -docker compose exec api python seed.py -``` +Open the repo in VS Code and click **Reopen in Container** (or run `Dev Containers: Reopen in Container` from the command palette). The dev container starts both the backend (port 8000) and frontend (port 4200) automatically. Open [http://localhost:4200](http://localhost:4200). ### Option B — Manual (frontend + backend separately) ```bash -# Database -createdb kmountain -export KMTN_DATABASE_URL=postgresql+asyncpg://postgres:postgres@localhost:5432/kmountain - -# Backend -cd backend && pip install -r requirements.txt +# Backend (uses SQLite for local dev — no database server needed) +cd backend +pip install -r requirements.txt +export KMTN_DATABASE_URL="sqlite+aiosqlite:///./kmountain.db" uvicorn app.main:app --reload # Frontend (new terminal) @@ -39,11 +31,10 @@ Open [http://localhost:4200](http://localhost:4200) and ensure the backend is ru | Requirement | Version | Notes | |---|---|---| -| Docker + Docker Compose | 24+ | For the Docker Compose path | -| Node.js | 20+ | For manual frontend development | +| Node.js | 20+ | For frontend development | | npm | 11+ | | -| Python | 3.12+ | For manual backend development | -| PostgreSQL | 16+ | For manual backend development (Docker handles it) | +| Python | 3.12+ | For backend development | +| PostgreSQL | 16+ | For production only (dev uses SQLite) | ## Architecture @@ -58,29 +49,28 @@ Open [http://localhost:4200](http://localhost:4200) and ensure the backend is ru ┌────▼─────┐ ┌──────▼─────┐ │ Nginx │ /api/* │ FastAPI │ │ (static) │─ proxy ──▶ │ :8000 │ - └────┬─────┘ └──────┬─────┘ - │ │ - ┌────▼─────┐ ┌──────▼─────┐ - │ Angular 21│ │ PostgreSQL │ - │ SPA │ │ :5432 │ - └──────────┘ └────────────┘ + └──────────┘ └──────┬─────┘ + │ + ┌──────▼─────┐ + │ PostgreSQL │ + │ :5432 │ + └────────────┘ ``` **Three tiers:** -1. **Frontend** — Angular 21 SPA built via multi-stage Dockerfile and served by Nginx. In production, Nginx serves the static files and proxies `/api/*` requests to the backend. In development, both run on separate ports and the browser talks directly to both. +1. **Frontend** — Angular 21 SPA. In production, served by Nginx. In development, served by the Angular dev server on port 4200. 2. **Backend** — Python FastAPI with async SQLAlchemy. Provides CRUD REST endpoints for programs (broadcast schedule), events (upcoming events), and donation tiers. Auto-generates Swagger/OpenAPI docs at `/docs`. -3. **Database** — PostgreSQL 16. Schema contains three tables: `programs`, `events`, `donation_tiers`. Initial data is loaded via [seed.py](backend/seed.py). +3. **Database** — PostgreSQL 16. Schema contains three tables: `programs`, `events`, `donation_tiers`. Initial data is loaded via [seed.py](backend/seed.py). Local development uses SQLite for convenience. ## Project Structure ``` . -├── docker-compose.yml # 3-service stack (db, api, frontend) -├── Dockerfile # Multi-stage: Angular build → Nginx serve -├── nginx.conf # Static files + /api/* proxy -├── .env.example # Environment variable template +├── docker-compose.dev.yml # Dev-sidecar compose (dev container) ├── .devcontainer/ # VS Code dev container +│ ├── Dockerfile # Alpine Python 3.12 + Node.js + Angular CLI +│ └── devcontainer.json # Dev container config │ ├── src/ # Angular 21 frontend │ ├── app/ # Standalone components @@ -95,7 +85,6 @@ Open [http://localhost:4200](http://localhost:4200) and ensure the backend is ru │ └── _themes.scss # SCSS → CSS custom properties │ └── backend/ # FastAPI backend - ├── Dockerfile # Python gunicorn/uvicorn ├── requirements.txt ├── seed.py # Initial data for all models └── app/ @@ -112,43 +101,40 @@ Open [http://localhost:4200](http://localhost:4200) and ensure the backend is ru ## Local Development -### Docker Compose +### Dev Container (recommended) -```bash -docker compose up --build # Start all services -docker compose exec api python seed.py # Seed database (first run) -docker compose logs -f api # Stream backend logs -docker compose down # Stop and remove containers -``` - -The `pgdata` volume persists the database across container restarts. +Open the repo in VS Code → **Reopen in Container**. The dev container starts both services automatically. See [.devcontainer/](.devcontainer/) for the configuration. ### Manual Development -Run all three layers in parallel: +Run both layers in parallel — development uses SQLite (no database server needed): ```bash -# 1. Database (install PostgreSQL 16 locally) -createdb kmountain - -# 2. Backend (new terminal) +# 1. Backend (new terminal) cd backend pip install -r requirements.txt -export KMTN_DATABASE_URL=postgresql+asyncpg://postgres:postgres@localhost:5432/kmountain +export KMTN_DATABASE_URL="sqlite+aiosqlite:///./kmountain.db" export KMTN_CORS_ORIGINS='["http://localhost:4200"]' uvicorn app.main:app --reload -# 3. Frontend (another terminal) +# 2. Frontend (another terminal) npm install ng serve ``` +The database auto-seeds on first startup. To reset and re-seed at any time: + +```bash +curl -X POST http://localhost:8000/api/admin/reset +``` + ## Configuration | Variable | Default | Where | Description | |---|---|---|---| -| `KMTN_DATABASE_URL` | `postgresql+asyncpg://postgres:postgres@localhost:5432/kmountain` | [backend/app/config.py](backend/app/config.py) | Async PostgreSQL connection string | +| `KMTN_DATABASE_URL` | `postgresql+asyncpg://...` (prod) / `sqlite+aiosqlite:///./kmountain.db` (dev) | [backend/app/config.py](backend/app/config.py) | Database connection string | | `KMTN_CORS_ORIGINS` | `["http://localhost:4200"]` | [backend/app/config.py](backend/app/config.py) | CORS allowlist (JSON array) | +| `KMTN_ENV` | `development` | [backend/app/config.py](backend/app/config.py) | Set to `production` to disable admin endpoints | | `apiBaseUrl` | `http://localhost:8000` (dev) / `''` (prod) | [src/environments/](src/environments/) | Where the Angular app finds the API | Environment variables are prefixed with `KMTN_` in the backend. For production, set `KMTN_DATABASE_URL` to your managed PostgreSQL connection and update `KMTN_CORS_ORIGINS` to your actual domain. @@ -158,21 +144,17 @@ Environment variables are prefixed with `KMTN_` in the backend. For production, ```bash # Frontend (produces dist/radio-station/browser/) ng build --configuration production - -# Backend Docker image -docker build -t kmountain-api -f backend/Dockerfile backend/ - -# Full production stack -docker compose up --build -d ``` +For production deployment, serve the built static files with Nginx and run the backend behind it. + ## Testing ```bash ng test # Angular + Vitest ``` -The backend API is self-documented at [http://localhost:8000/docs](http://localhost:8000/docs) (Swagger UI). Run `docker compose exec api python seed.py` and visit that URL to interactively test endpoints. +The backend API is self-documented at [http://localhost:8000/docs](http://localhost:8000/docs) (Swagger UI). The database auto-seeds on first startup — visit that URL to interactively test endpoints. ## API Reference @@ -200,33 +182,41 @@ Full interactive API docs at [http://localhost:8000/docs](http://localhost:8000/ ## Deployment -### Production (Docker Compose on a VPS) +### Production + +For production, you'll need to set up: +- A PostgreSQL database (managed or self-hosted) +- The FastAPI backend (gunicorn/uvicorn) +- Nginx to serve the Angular build and proxy `/api/*` to the backend ```bash -# 1. Clone repo on server -git clone kmountain -cd kmountain +# 1. Build the frontend +ng build --configuration production -# 2. Edit docker-compose.yml -# - Change POSTGRES_PASSWORD to a strong value -# - Update KMTN_CORS_ORIGINS to your domain -# - Optionally set KMTN_DATABASE_URL to a managed PostgreSQL +# 2. Configure the backend +export KMTN_DATABASE_URL="postgresql+asyncpg://user:pass@host:5432/kmountain" +export KMTN_CORS_ORIGINS='["https://yourdomain.com"]' +export KMTN_ENV="production" -# 3. Build and start -docker compose up --build -d +# 3. Start the backend +cd backend +pip install -r requirements.txt +uvicorn app.main:app --host 0.0.0.0 --port 8000 -# 4. Seed the database -docker compose exec api python seed.py +# 4. Serve the frontend with Nginx (or your preferred web server), +# pointing /api/* requests to http://localhost:8000 ``` +The database auto-seeds on first startup. + ### Production hardening checklist -- [ ] Change the default `POSTGRES_PASSWORD` in [docker-compose.yml](docker-compose.yml) +- [ ] Use a managed PostgreSQL database (RDS, Cloud SQL, etc.) - [ ] Update `KMTN_CORS_ORIGINS` to your actual domain (not `localhost`) -- [ ] Set `KMTN_DATABASE_URL` to a managed PostgreSQL connection (RDS, Cloud SQL, etc.) +- [ ] Set `KMTN_ENV="production"` to disable admin endpoints - [ ] Add a reverse proxy (Caddy, Traefik) in front of Nginx for HTTPS -- [ ] Set up log rotation for Docker containers -- [ ] Configure Docker image registry and push/pull workflow +- [ ] Set up log rotation +- [ ] Configure a process manager (systemd, PM2) for the backend ## Development Tools @@ -245,6 +235,6 @@ docker compose exec api python seed.py | Styling | SCSS with design tokens (variables → mixins → CSS custom properties) | | Routing | Angular Router (lazy loaded) + Nginx SPA fallback | | HTTP | RxJS observables (frontend → FastAPI) | -| Containers | Docker Compose (3 services: db, api, frontend) | +| Containers | Dev container (VS Code Remote - Containers) | | CI/CD | _(not yet configured)_ | | Testing | Vitest (via Angular CLI) | diff --git a/backend/__pycache__/seed.cpython-312.pyc b/backend/__pycache__/seed.cpython-312.pyc index db7128d..af1e52f 100644 Binary files a/backend/__pycache__/seed.cpython-312.pyc and b/backend/__pycache__/seed.cpython-312.pyc differ diff --git a/backend/app/__pycache__/__init__.cpython-312.pyc b/backend/app/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..951db82 Binary files /dev/null and b/backend/app/__pycache__/__init__.cpython-312.pyc differ diff --git a/backend/app/__pycache__/config.cpython-312.pyc b/backend/app/__pycache__/config.cpython-312.pyc index 4314697..64826b3 100644 Binary files a/backend/app/__pycache__/config.cpython-312.pyc and b/backend/app/__pycache__/config.cpython-312.pyc differ diff --git a/backend/app/__pycache__/main.cpython-312.pyc b/backend/app/__pycache__/main.cpython-312.pyc index a1c4972..f4ac95d 100644 Binary files a/backend/app/__pycache__/main.cpython-312.pyc and b/backend/app/__pycache__/main.cpython-312.pyc differ diff --git a/backend/app/__pycache__/models.cpython-312.pyc b/backend/app/__pycache__/models.cpython-312.pyc index 8cdabdf..e304f65 100644 Binary files a/backend/app/__pycache__/models.cpython-312.pyc and b/backend/app/__pycache__/models.cpython-312.pyc differ diff --git a/backend/app/api/__pycache__/__init__.cpython-312.pyc b/backend/app/api/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..a634744 Binary files /dev/null and b/backend/app/api/__pycache__/__init__.cpython-312.pyc differ diff --git a/backend/app/api/__pycache__/admin.cpython-312.pyc b/backend/app/api/__pycache__/admin.cpython-312.pyc new file mode 100644 index 0000000..79edb2a Binary files /dev/null and b/backend/app/api/__pycache__/admin.cpython-312.pyc differ diff --git a/backend/app/api/admin.py b/backend/app/api/admin.py new file mode 100644 index 0000000..1d2c659 --- /dev/null +++ b/backend/app/api/admin.py @@ -0,0 +1,26 @@ +from fastapi import APIRouter + +from app.config import settings +from app.database import get_session +from app.models import DonationTier, Event, Program + +router = APIRouter() + + +@router.post("/reset") +async def reset_database(): + """Truncate all tables and re-seed. Dev-only — disabled in production.""" + if settings.ENV == "production": + return {"error": "Admin reset is disabled in production"} + + from app.database import async_session + from seed import seed as run_seed + + async with async_session() as session: + await session.execute(Program.__table__.delete()) + await session.execute(Event.__table__.delete()) + await session.execute(DonationTier.__table__.delete()) + await session.commit() + + await run_seed() + return {"status": "reset complete"} diff --git a/backend/app/config.py b/backend/app/config.py index 91bf5f5..85c2f98 100644 --- a/backend/app/config.py +++ b/backend/app/config.py @@ -4,6 +4,7 @@ from pydantic_settings import BaseSettings class Settings(BaseSettings): DATABASE_URL: str = "postgresql+asyncpg://postgres:postgres@localhost:5432/kmountain" CORS_ORIGINS: list[str] = ["http://localhost:4200", "http://localhost:3000"] + ENV: str = "development" model_config = {"env_prefix": "KMTN_"} diff --git a/backend/app/main.py b/backend/app/main.py index 9e0b8f9..b227cf4 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -2,18 +2,29 @@ from contextlib import asynccontextmanager from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware +from sqlalchemy import func, select from app.config import settings -from app.database import engine -from app.models import Base +from app.database import async_session, engine +from app.models import Base, Program from app.api import programs, events, tiers @asynccontextmanager async def lifespan(app: FastAPI): - # Create tables on startup (seed.py handles data seeding) + # Create tables on startup async with engine.begin() as conn: await conn.run_sync(Base.metadata.create_all) + + # Auto-seed if database is empty + async with async_session() as session: + result = await session.execute(select(func.count(Program.id))) + count = result.scalar() + if count == 0: + print(" → Database is empty — running seed...") + from seed import seed as run_seed + await run_seed() + yield @@ -36,3 +47,8 @@ app.add_middleware( app.include_router(programs.router, prefix="/api/programs", tags=["programs"]) app.include_router(events.router, prefix="/api/events", tags=["events"]) app.include_router(tiers.router, prefix="/api/tiers", tags=["tiers"]) + +# Dev-only admin router (disabled in production) +if settings.ENV != "production": + from app.api import admin + app.include_router(admin.router, prefix="/api/admin", tags=["admin"]) diff --git a/backend/app/models.py b/backend/app/models.py index 48af1e3..6defaa3 100644 --- a/backend/app/models.py +++ b/backend/app/models.py @@ -6,7 +6,7 @@ from sqlalchemy import ( Numeric, Boolean, Date, - ARRAY, + JSON, ) from sqlalchemy.orm import DeclarativeBase @@ -47,5 +47,5 @@ class DonationTier(Base): name = Column(String(50), unique=True, nullable=False) amount = Column(Numeric(10, 2), nullable=False) icon = Column(String(10), nullable=False) - benefits = Column(ARRAY(Text), nullable=False) + benefits = Column(JSON, nullable=False) display_order = Column(Integer, nullable=False) diff --git a/backend/requirements.txt b/backend/requirements.txt index 69a6773..8cd3a41 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -2,6 +2,7 @@ fastapi==0.115.6 uvicorn[standard]==0.34.0 psycopg2-binary==2.9.10 asyncpg==0.30.0 +aiosqlite==0.20.0 sqlalchemy[asyncio]==2.0.36 alembic==1.14.1 python-multipart==0.0.20 diff --git a/backend/seed.py b/backend/seed.py index 4204ec2..9c094ce 100644 --- a/backend/seed.py +++ b/backend/seed.py @@ -1,14 +1,18 @@ """ Seed script: populates the database with station data. +Idempotent — safe to run multiple times. Uses get-or-create (upsert) for each entity. + Usage: - DATABASE_URL=postgresql://... python -m app.seed - (or from the repo root: cd backend && python seed.py) + KMTN_DATABASE_URL=... python seed.py + (run from the backend/ directory) """ import asyncio from datetime import date +from sqlalchemy import select + from app.database import async_session from app.models import Program, Event, DonationTier @@ -90,29 +94,83 @@ TIERS: list[dict] = [ ] +# ── Helpers ──────────────────────────────────────────────── + +async def _upsert_program(session, data: dict) -> None: + """Get-or-create a program matched on (day_of_week, time).""" + existing = await session.execute( + select(Program).where( + Program.day_of_week == data["day_of_week"], + Program.time == data["time"], + ) + ) + program = existing.scalar_one_or_none() + if program: + for key, value in data.items(): + setattr(program, key, value) + else: + session.add(Program(**data)) + + +async def _upsert_event(session, data: dict) -> None: + """Get-or-create an event matched on (date, title).""" + existing = await session.execute( + select(Event).where( + Event.date == data["date"], + Event.title == data["title"], + ) + ) + event = existing.scalar_one_or_none() + if event: + for key, value in data.items(): + setattr(event, key, value) + else: + session.add(Event(**data, active=True)) + + +async def _upsert_tier(session, data: dict) -> None: + """Get-or-create a donation tier matched on name.""" + existing = await session.execute( + select(DonationTier).where(DonationTier.name == data["name"]) + ) + tier = existing.scalar_one_or_none() + if tier: + for key, value in data.items(): + setattr(tier, key, value) + else: + session.add(DonationTier(**data)) + + # ── Main ────────────────────────────────────────────────── -async def seed(): +async def seed() -> None: + """Populate the database with station data (idempotent).""" async with async_session() as session: - # Programs for data in PROGRAMS: - session.add(Program(**data)) + await _upsert_program(session, data) await session.commit() print(f" ✓ Seeded {len(PROGRAMS)} programs") - # Events for data in EVENTS: - event = Event(**data, active=True) - session.add(event) + await _upsert_event(session, data) await session.commit() print(f" ✓ Seeded {len(EVENTS)} events") - # Tiers for data in TIERS: - session.add(DonationTier(**data)) + await _upsert_tier(session, data) await session.commit() print(f" ✓ Seeded {len(TIERS)} donation tiers") +async def truncate_all() -> None: + """Remove all seed data from the database.""" + async with async_session() as session: + await session.execute(Program.__table__.delete()) + await session.execute(Event.__table__.delete()) + await session.execute(DonationTier.__table__.delete()) + await session.commit() + print(" ✓ Truncated all tables") + + if __name__ == "__main__": asyncio.run(seed()) diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 3d6a5f7..69125f1 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -1,5 +1,6 @@ -# Dev-sidecar compose: app container + PostgreSQL sidecar. +# Dev-sidecar compose: app container only (no database service). # Used by .devcontainer/devcontainer.json via dockerComposeFile. +# Development uses SQLite — no Docker-in-Docker database needed. services: app: @@ -11,15 +12,12 @@ services: - "8000:8000" # FastAPI - "4200:4200" # Angular dev server environment: - KMTN_DATABASE_URL: postgresql+asyncpg://postgres:postgres@db:5432/kmountain + KMTN_DATABASE_URL: sqlite+aiosqlite:///./backend/kmountain.db KMTN_CORS_ORIGINS: '["http://localhost:4200"]' volumes: - ..:/workspaces/web_app:cached - node_modules:/workspaces/web_app/node_modules - backend_venv:/app/.venv - depends_on: - db: - condition: service_healthy command: > sh -c " pip install -r backend/requirements.txt && @@ -30,24 +28,6 @@ services: npx ng serve --host 0.0.0.0 --port 4200 " - db: - image: docker.io/library/postgres:16-alpine - restart: "no" - environment: - POSTGRES_DB: kmountain - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - volumes: - - pgdata:/var/lib/postgresql/data - ports: - - "5432:5432" - healthcheck: - test: ["CMD-SHELL", "pg_isready -U postgres"] - interval: 5s - timeout: 5s - retries: 5 - volumes: - pgdata: node_modules: backend_venv: diff --git a/package-lock.json b/package-lock.json index a07c724..f959b50 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2204,9 +2204,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2224,9 +2221,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2244,9 +2238,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2264,9 +2255,6 @@ "riscv64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2284,9 +2272,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2304,9 +2289,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2324,9 +2306,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2770,9 +2749,6 @@ "arm" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2794,9 +2770,6 @@ "arm" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2818,9 +2791,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2842,9 +2812,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2866,9 +2833,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2890,9 +2854,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -3070,9 +3031,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -3090,9 +3048,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -3110,9 +3065,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -3130,9 +3082,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -3309,9 +3258,6 @@ "arm" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -3326,9 +3272,6 @@ "arm" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -3343,9 +3286,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -3360,9 +3300,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -3377,9 +3314,6 @@ "loong64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -3394,9 +3328,6 @@ "loong64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -3411,9 +3342,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -3428,9 +3356,6 @@ "ppc64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -3445,9 +3370,6 @@ "riscv64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -3462,9 +3384,6 @@ "riscv64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -3479,9 +3398,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -3496,9 +3412,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -3513,9 +3426,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [