working dev environment

This commit is contained in:
2026-06-18 08:29:03 +00:00
parent 6fc00b8ce9
commit 57b882128d
21 changed files with 200 additions and 210 deletions
+3 -2
View File
@@ -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.
+6 -1
View File
@@ -1,7 +1,12 @@
{
"permissions": {
"allow": [
"*"
"Bash(*)",
"Read(*)",
"Edit(*)",
"Write(*)",
"Glob(*)",
"Grep(*)"
],
"additionalDirectories": [
"/workspaces/web_app/.vscode"
+2 -4
View File
@@ -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
+2 -1
View File
@@ -7,7 +7,8 @@
"vscode": {
"extensions": [
"Angular.ng-template",
"magnobiet.sass-extension-pack"
"magnobiet.sass-extension-pack",
"Anthropic.claude-code"
]
}
}
+3
View File
@@ -45,3 +45,6 @@ Thumbs.db
# Environment files
.env
# Dev SQLite database
backend/kmountain.db
+62 -72
View File
@@ -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 <repo-url> 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) |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+26
View File
@@ -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"}
+1
View File
@@ -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_"}
+19 -3
View File
@@ -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"])
+2 -2
View File
@@ -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)
+1
View File
@@ -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
+68 -10
View File
@@ -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())
+3 -23
View File
@@ -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:
-90
View File
@@ -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": [