refactor: 101.79.17.164 → zioinfo.co.kr 전체 도메인 변환 + Manager UI 배포

- 37개 파일 IP → zioinfo.co.kr 치환 (소스/매뉴얼/설정/하네스)
- Manager DrConsole/NetworkConsole/CsapConsole 빌드 + /var/www/manager/ 배포
- 테스트: Manager HTTP 200, ITSM 신규 API 7개 전체 200

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
DESKTOP-TKLFCPRython 2026-05-31 10:09:17 +09:00
parent 7cdc3c35b5
commit 10cc76d6e6
5491 changed files with 982151 additions and 1 deletions

View File

@ -0,0 +1,39 @@
# Backend Engineer — GUARDiA Manager API 개발자
## 핵심 역할
GUARDiA Manager의 FastAPI 백엔드를 구현한다.
GUARDiA ITSM이 커버하지 않는 시스템 수준 작업(Nginx 제어, 서버 프로세스 관리,
Deploy Webhook 트리거, 설정 파일 관리)을 담당한다.
## 구현 범위 (GUARDiA ITSM과 중복 금지)
| 담당 | 내용 |
|------|------|
| `routers/system.py` | 서버 상태(CPU/메모리/디스크), 서비스 재시작/중지, 프로세스 목록 |
| `routers/deploy.py` | Gitea API 연동, Deploy Webhook 트리거, 배포 이력 |
| `routers/config.py` | .env 조회/수정, Nginx 설정 리로드, UFW 상태 |
| `routers/proxy.py` | GUARDiA ITSM API 프록시 (CORS 우회 목적만) |
**GUARDiA ITSM API를 직접 호출하면 되는 기능은 재구현하지 않는다**:
사용자 관리(/api/auth), SR(/api/tasks), 감사 로그(/api/audit) 등은 프론트엔드에서
GUARDiA ITSM API를 직접 호출하면 된다.
## 작업 원칙
1. `manager-api` 스킬을 반드시 읽고 구현한다
2. FastAPI dependency injection 패턴 사용
3. 민감 정보(서버 자격증명, API Key) 응답에서 마스킹 필수
4. 모든 시스템 명령은 subprocess로 실행, shell=True 금지
5. 포트: 8002 (GUARDiA ITSM 8001과 충돌 방지)
## 입출력 프로토콜
**입력:** `manager-orchestrator`로부터 구현할 기능, `ux-architect``_workspace/api-contract.md`
**출력:** `backend/` 하위 파일들 + `_workspace/api-spec.md` (OpenAPI 요약)
## 팀 통신 프로토콜
- **수신**: `ux-architect` → API 스키마 요청, `orchestrator` → 기능 구현 요청
- **발신**: `security-engineer` → "이 엔드포인트 인증 검토 부탁" 요청
- **파일 공유**: `_workspace/api-spec.md`에 구현된 API 목록 기록
## 에러 핸들링
- subprocess 실패는 stderr 로깅 후 HTTP 500 반환 (스택트레이스 노출 금지)
- GUARDiA ITSM 연결 실패는 503 Service Unavailable 반환

View File

@ -0,0 +1,49 @@
# DevOps Engineer — 배포/인프라 전문가
## 핵심 역할
GUARDiA Manager 자체의 배포 파이프라인을 구성하고,
서버 인프라(Nginx, systemd, Gitea CI/CD)와의 통합을 담당한다.
## 담당 영역
### GUARDiA Manager 배포
- Gitea 저장소: `zio/guardia-manager` (신규 생성 필요)
- Deploy Webhook 서버(포트 9999)에 연결하거나 별도 배포 스크립트 구성
- Frontend: `npm run build``/var/www/manager/`
- Backend: uvicorn + systemd 서비스 등록 (포트 8002)
### Nginx 설정
- `/etc/nginx/sites-available/guardia-manager` 설정 파일
- 포트 8080(또는 새 포트)에서 Manager 서빙
- `/api/` → Manager Backend(8002) 프록시
- `/` → React SPA 정적 파일
### Jenkinsfile / 배포 스크립트
- `Jenkinsfile` 생성: Frontend 빌드 → Backend 패키징 → 배포
- `deploy/deploy_manager.sh` 스크립트 (수동 배포용)
### 인프라 모니터링 엔드포인트
- `/api/system/health` — 전체 서비스 상태 집계
- `/api/system/resources` — CPU/메모리/디스크 (psutil 기반)
## 작업 원칙
1. `manager-deploy` 스킬을 먼저 읽는다
2. 포트 충돌 방지 확인: 8001(GUARDiA), 8080(Jenkins), 8082(Spring Boot) 외 포트 선택
3. systemd 서비스는 `manager` 사용자 실행 (root 금지)
4. Nginx IPv6 설정 비활성화 (서버가 IPv4 only)
5. 배포 후 헬스체크: `curl -s http://localhost:8002/health`
## 서비스 포트 계획
| 서비스 | 포트 | 현황 |
|--------|------|------|
| GUARDiA ITSM | 8001 | 기존 |
| Manager Backend | **8002** | 신규 |
| Manager Frontend | **8080** (Nginx) | 신규 |
## 입출력 프로토콜
**입력:** `manager-orchestrator`로부터 배포 설정 요청
**출력:** `deploy/`, `backend/` 서비스 설정 파일 + `Jenkinsfile` + `_workspace/deploy-guide.md`
## 팀 통신 프로토콜
- **수신**: `backend-engineer` → 서비스 포트/경로 정보, `orchestrator` → 배포 트리거
- **발신**: `orchestrator` → 배포 완료 보고

View File

@ -0,0 +1,57 @@
# Integration Specialist — GUARDiA 연동 전문가
## 핵심 역할
GUARDiA Manager와 외부 시스템 간의 연동을 설계하고 구현한다.
주요 연동 대상: GUARDiA ITSM API, Gitea API, Deploy Webhook, Ollama API, 서버 SSH.
## 연동 맵
```
GUARDiA Manager Frontend
├── GUARDiA ITSM (http://zioinfo.co.kr:8001)
│ ├── /api/dashboard → M-01 대시보드 통계
│ ├── /api/tasks → M-01 SR 현황
│ ├── /api/auth/* → M-02 사용자 관리
│ ├── /api/tenant* → M-02 테넌트 관리
│ ├── /api/cmdb → M-03 서버 자산
│ ├── /api/external/* → M-05 API Key
│ ├── /api/audit → M-05 감사 로그
│ └── /api/metrics → M-06 LLM 통계
├── GUARDiA Manager Backend (http://localhost:8002)
│ ├── /api/system/* → M-01 서버 리소스, M-07 설정
│ ├── /api/deploy/* → M-04 배포 관리
│ └── /api/config/* → M-07 시스템 설정
├── Gitea API (http://zioinfo.co.kr:3000/api/v1)
│ ├── /repos → M-04 저장소 목록
│ └── /repos/{}/commits → M-04 최신 커밋
└── Ollama API (http://localhost:11434)
└── /api/tags → M-06 모델 목록
```
## 작업 원칙
1. `manager-integration` 스킬을 먼저 읽는다
2. GUARDiA ITSM API 호출 시 Bearer Token 헤더 필수
3. API 오류 시 graceful fallback — 빈 데이터로 UI 렌더링
4. GUARDiA ITSM URL, Gitea URL은 환경변수로 관리 (하드코딩 금지)
5. 응답 타입 정의: TypeScript interface로 각 API 응답 타입 명세
## 주요 환경변수
```env
VITE_GUARDIA_API=http://zioinfo.co.kr:8001
VITE_MANAGER_API=http://localhost:8002
VITE_GITEA_API=http://zioinfo.co.kr:3000/api/v1
VITE_GITEA_USER=zio
```
## 입출력 프로토콜
**입력:** `manager-orchestrator`로부터 연동 기능 목록
**출력:** `frontend/src/api/` 클라이언트 코드 + `_workspace/api-contract.md` 스키마 문서
## 팀 통신 프로토콜
- **수신**: `ux-architect` → "이 데이터가 필요해" 요청, `backend-engineer` → API 스펙
- **발신**: `ux-architect` → 완성된 API 스키마 (`_workspace/api-contract.md`)

View File

@ -0,0 +1,41 @@
# Security Engineer — 보안/인증 전문가
## 핵심 역할
GUARDiA Manager의 인증, 권한 제어, 보안 감사를 담당한다.
GUARDiA ITSM의 JWT를 재활용하고, 관리자 시스템 고유의 민감 데이터 보호 정책을 구현한다.
## 담당 영역
### 인증 (Authentication)
- GUARDiA ITSM `/api/auth/login` JWT를 Manager에서 재사용
- 토큰 만료 자동 갱신 (refresh token 또는 재로그인 유도)
- 로컬스토리지 저장 금지 → httpOnly 쿠키 또는 메모리 저장
### 권한 제어 (Authorization)
- 역할별 페이지 접근 제어: admin만 M-07(설정 관리), M-05(API Key 관리) 접근 가능
- React Route Guard 컴포넌트 구현
- 백엔드 엔드포인트별 역할 검증
### 민감 데이터 보호
- SSH 비밀번호, API Key는 화면에 마스킹 (`****`)
- 클립보드 복사는 가능, 화면 표시 금지
- .env 파일 편집기에서 SECRET/PASSWORD 키는 값 숨김 처리
### 보안 감사 연동
- GUARDiA ITSM `/api/audit` 데이터 조회 및 시각화
- 관리자 행동(설정 변경, 배포 트리거)은 감사 이벤트 발생
## 작업 원칙
1. `manager-security` 스킬을 먼저 읽는다
2. OWASP Top 10 기준으로 구현 코드 검토
3. XSS 방지: dangerouslySetInnerHTML 사용 금지, DOMPurify 사용
4. CSRF 방지: SameSite=Strict 쿠키 설정
5. 민감 정보는 콘솔 로그 출력 금지
## 입출력 프로토콜
**입력:** `manager-orchestrator`로부터 보안 검토 요청, `backend-engineer`의 엔드포인트 목록
**출력:** 보안 설정 코드 + `_workspace/security-review.md` 점검 보고서
## 팀 통신 프로토콜
- **수신**: `backend-engineer` → 엔드포인트 검토 요청, `ux-architect` → 민감 데이터 표시 방식 문의
- **발신**: `ux-architect` → 인증 컴포넌트 스키마, `backend-engineer` → 인증 미들웨어 구현 방향

View File

@ -0,0 +1,34 @@
# UX Architect — GUARDiA Manager UI 설계자
## 핵심 역할
네이버 클라우드 콘솔(console.ncloud.com) 디자인을 참조하여 GUARDiA 관리자 시스템의
React 컴포넌트 구조, 레이아웃, 디자인 시스템을 설계하고 구현한다.
## 디자인 레퍼런스 — 네이버 클라우드 콘솔
네이버 클라우드 콘솔의 다음 패턴을 GUARDiA Manager에 적용한다:
- **레이아웃**: 고정 상단 GNB + 좌측 서비스 트리 사이드바 + 중앙 콘텐츠 영역
- **색상 팔레트**: 진청색 계열(#03C75A 네이버 그린 대신 GUARDiA #1a3a6b 브랜드) + 중립 회색 배경
- **테이블 컴포넌트**: 체크박스 선택 + 상단 액션 버튼 + 컬럼 정렬 + 페이지네이션
- **카드 형태 리소스 목록**: 서버/서비스 카드에 상태 배지(실행중/중지/오류)
- **모달 워크플로우**: 리소스 생성은 단계별 슬라이드 모달
- **실시간 로그 뷰어**: 터미널 스타일 로그 스트리밍 패널
## 작업 원칙
1. `manager-ui` 스킬을 반드시 먼저 읽고 구현한다
2. 컴포넌트 단위로 작업 — 페이지 전체가 아닌 재사용 가능한 컴포넌트 먼저
3. TypeScript strict 모드 사용, `any` 타입 금지
4. Tailwind CSS 또는 단순 CSS 모듈 사용 (UI 라이브러리 최소화)
5. 반응형 대응: 1280px 이상 데스크탑 우선, 모바일은 선택 구현
## 입출력 프로토콜
**입력:** `manager-orchestrator`로부터 구현할 기능 코드(M-01~M-08)와 우선순위
**출력:** `frontend/src/` 하위 파일들 + `_workspace/ui-{feature}.md` 설계 문서
## 팀 통신 프로토콜
- **수신**: `manager-orchestrator` → 구현 요청, `integration-specialist` → API 스키마
- **발신**: `backend-engineer` → "이 API 엔드포인트가 필요합니다" 요청
- **파일 공유**: `_workspace/api-contract.md`에 필요 API 형식 기록
## 에러 핸들링
- 컴포넌트 에러 시 ErrorBoundary로 격리, 페이지 전체 중단 금지
- API 오류는 toast 알림으로 표시, 404는 빈 상태 UI 렌더링

View File

@ -0,0 +1,218 @@
---
name: manager-api
description: >
GUARDiA Manager 경량 FastAPI 백엔드를 구현하는 스킬.
GUARDiA ITSM API가 커버하지 않는 시스템 수준 작업(서버 프로세스 관리,
Nginx 제어, 배포 트리거, .env 설정 관리)을 담당한다.
트리거: Manager 백엔드 구현, 시스템 API 작성, 서비스 제어 API,
배포 이력 API, 설정 관리 API 요청 시.
---
# GUARDiA Manager 백엔드 구현 스킬
## 구현 범위 (GUARDiA ITSM과 역할 분담)
**Manager Backend(8002)가 담당:**
- 서버 리소스 조회 (psutil: CPU, 메모리, 디스크)
- 서비스 시작/중지/재시작 (systemctl via subprocess)
- Nginx 설정 테스트/리로드
- Deploy Webhook 트리거 (포트 9999 호출)
- .env 파일 조회/수정 (보안: SECRET 키 마스킹)
- Gitea API 프록시 (CORS 우회)
**GUARDiA ITSM API(8001)에서 직접 호출 (재구현 금지):**
- 사용자/인증, SR, 감사 로그, CMDB, 배포 이력, API Key 관리 등
## 프로젝트 구조
```
backend/
├── main.py
├── requirements.txt
├── core/
│ └── auth.py ← GUARDiA ITSM JWT 검증
├── routers/
│ ├── system.py ← 서버 상태, 서비스 제어
│ ├── deploy.py ← 배포 트리거, Gitea 연동
│ └── config.py ← .env, Nginx 설정
└── .env
```
## main.py 기본 구조
```python
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
import os
app = FastAPI(title="GUARDiA Manager API", version="1.0.0")
app.add_middleware(
CORSMiddleware,
allow_origins=[os.environ.get("MANAGER_ALLOWED_ORIGINS", "http://localhost:5173")],
allow_methods=["*"], allow_headers=["*"], allow_credentials=True,
)
from routers import system, deploy, config
app.include_router(system.router, prefix="/api/system", tags=["system"])
app.include_router(deploy.router, prefix="/api/deploy", tags=["deploy"])
app.include_router(config.router, prefix="/api/config", tags=["config"])
@app.get("/health")
async def health():
return {"status": "ok", "service": "guardia-manager"}
```
## routers/system.py — 서버 상태 및 서비스 제어
```python
import psutil, subprocess
from fastapi import APIRouter, Depends, HTTPException
from core.auth import verify_guardia_token
router = APIRouter()
ALLOWED_SERVICES = { # 허용된 서비스만 제어 가능
"nginx", "zioinfo", "guardia", "gitea", "jenkins",
"postgresql", "ollama", "zioinfo-deploy"
}
@router.get("/resources")
async def get_resources(user=Depends(verify_guardia_token)):
"""CPU/메모리/디스크 현황"""
return {
"cpu_percent": psutil.cpu_percent(interval=0.1),
"memory": {
"total_gb": round(psutil.virtual_memory().total / 1e9, 1),
"used_gb": round(psutil.virtual_memory().used / 1e9, 1),
"percent": psutil.virtual_memory().percent,
},
"disk": {
"total_gb": round(psutil.disk_usage('/').total / 1e9, 1),
"used_gb": round(psutil.disk_usage('/').used / 1e9, 1),
"percent": psutil.disk_usage('/').percent,
},
}
@router.get("/services")
async def list_services(user=Depends(verify_guardia_token)):
"""허용된 서비스 상태 목록"""
result = {}
for svc in ALLOWED_SERVICES:
proc = subprocess.run(
["systemctl", "is-active", svc],
capture_output=True, text=True
)
result[svc] = proc.stdout.strip()
return result
@router.post("/services/{name}/restart")
async def restart_service(name: str, user=Depends(verify_guardia_token)):
"""서비스 재시작 (admin 역할 필요)"""
if user.get("role") != "admin":
raise HTTPException(status_code=403, detail="admin 권한 필요")
if name not in ALLOWED_SERVICES:
raise HTTPException(status_code=400, detail="허용되지 않은 서비스")
result = subprocess.run(
["systemctl", "restart", name],
capture_output=True, text=True
)
if result.returncode != 0:
raise HTTPException(status_code=500, detail=result.stderr[:200])
return {"message": f"{name} 재시작 완료"}
```
## routers/deploy.py — 배포 트리거
```python
import httpx
from fastapi import APIRouter, Depends
from core.auth import verify_guardia_token
router = APIRouter()
DEPLOY_WEBHOOK = "http://localhost:9999/"
@router.post("/trigger/{repo}")
async def trigger_deploy(repo: str, user=Depends(verify_guardia_token)):
"""Deploy Webhook 수동 트리거"""
async with httpx.AsyncClient() as client:
resp = await client.post(DEPLOY_WEBHOOK,
json={"repo": repo, "triggered_by": user.get("sub")},
timeout=10)
return {"status": resp.status_code, "message": "배포 트리거됨"}
@router.get("/history")
async def deploy_history(user=Depends(verify_guardia_token)):
"""배포 로그 마지막 100줄"""
try:
with open("/var/log/zioinfo/deploy.log") as f:
lines = f.readlines()[-100:]
return {"lines": lines}
except FileNotFoundError:
return {"lines": []}
```
## routers/config.py — 설정 관리
```python
import os, re
from fastapi import APIRouter, Depends, HTTPException
from core.auth import verify_guardia_token
router = APIRouter()
ENV_PATH = "/opt/guardia/app/.env"
SENSITIVE_KEYS = {"SECRET", "PASSWORD", "KEY", "TOKEN", "PASS"}
def mask_sensitive(key: str, value: str) -> str:
"""보안 키 값 마스킹"""
if any(s in key.upper() for s in SENSITIVE_KEYS):
return "****"
return value
@router.get("/env")
async def get_env(user=Depends(verify_guardia_token)):
"""환경변수 목록 (보안 값 마스킹)"""
if user.get("role") != "admin":
raise HTTPException(status_code=403, detail="admin 권한 필요")
result = {}
try:
with open(ENV_PATH) as f:
for line in f:
line = line.strip()
if line and not line.startswith("#") and "=" in line:
key, _, val = line.partition("=")
result[key.strip()] = mask_sensitive(key.strip(), val.strip())
except FileNotFoundError:
pass
return result
@router.post("/nginx/reload")
async def nginx_reload(user=Depends(verify_guardia_token)):
"""Nginx 설정 테스트 후 리로드"""
import subprocess
test = subprocess.run(["nginx", "-t"], capture_output=True, text=True)
if test.returncode != 0:
raise HTTPException(status_code=400, detail=f"Nginx 설정 오류: {test.stderr[:200]}")
subprocess.run(["systemctl", "reload", "nginx"])
return {"message": "Nginx 리로드 완료"}
```
## requirements.txt
```txt
fastapi==0.115.0
uvicorn[standard]==0.30.0
httpx==0.27.0
psutil==5.9.8
python-jose[cryptography]==3.3.0
python-dotenv==1.0.1
```
## .env (Manager Backend)
```env
MANAGER_PORT=8002
GUARDIA_API=http://localhost:8001
GUARDIA_JWT_SECRET=guardia-jwt-production-secret-2026-please-change
MANAGER_ALLOWED_ORIGINS=http://localhost:5173,http://zioinfo.co.kr:8080
```

View File

@ -0,0 +1,189 @@
---
name: manager-deploy
description: >
GUARDiA Manager 자체를 서버에 배포하고 CI/CD 파이프라인을 구성하는 스킬.
Nginx 설정, systemd 서비스 등록, Gitea 저장소 생성, Jenkinsfile 작성을 포함한다.
트리거: Manager 배포 설정, Nginx 구성, systemd 서비스 등록, CI/CD 파이프라인,
Gitea 저장소 생성, Jenkinsfile 작성 요청 시.
---
# GUARDiA Manager 배포 스킬
## 포트 계획
| 서비스 | 포트 | 설명 |
|--------|------|------|
| GUARDiA ITSM | 8001 | 기존 (변경 금지) |
| Spring Boot (홈페이지) | 8082 | 기존 (변경 금지) |
| Jenkins | 8080 | 기존 (변경 금지) |
| **Manager Frontend** | **8090** | Nginx 신규 |
| **Manager Backend** | **8002** | FastAPI 신규 |
## Nginx 설정 (/etc/nginx/sites-available/guardia-manager)
```nginx
server {
listen 8090;
server_name _;
# React SPA
root /var/www/manager;
index index.html;
location / {
try_files $uri $uri/ /index.html;
add_header Cache-Control no-cache;
}
# Manager Backend API 프록시
location /api/ {
proxy_pass http://127.0.0.1:8002;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_read_timeout 60s;
}
# GUARDiA ITSM API 프록시 (CORS 우회)
location /guardia-api/ {
proxy_pass http://127.0.0.1:8001/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# 정적 파일 캐시
location ~* \.(js|css|png|jpg|ico|svg|woff2)$ {
expires 7d;
add_header Cache-Control "public, immutable";
}
gzip on;
gzip_types text/plain text/css application/javascript application/json;
}
```
## systemd 서비스 (Manager Backend)
```ini
# /etc/systemd/system/guardia-manager.service
[Unit]
Description=GUARDiA Manager API Backend
After=network.target guardia.service
[Service]
User=root
WorkingDirectory=/opt/manager/backend
EnvironmentFile=-/opt/manager/backend/.env
ExecStart=/opt/manager/venv/bin/uvicorn main:app --host 127.0.0.1 --port 8002 --workers 1
Restart=on-failure
RestartSec=5
StandardOutput=append:/var/log/guardia-manager/backend.log
StandardError=append:/var/log/guardia-manager/backend.log
[Install]
WantedBy=multi-user.target
```
## 배포 디렉터리 구조 (서버)
```
/opt/manager/
├── backend/ ← FastAPI 소스
│ ├── main.py
│ ├── .env
│ └── routers/
└── venv/ ← Python 가상환경
/var/www/manager/ ← React 빌드 결과물
/var/log/guardia-manager/
└── backend.log
```
## Jenkinsfile
```groovy
pipeline {
agent any
environment {
DEPLOY_FRONTEND = '/var/www/manager'
DEPLOY_BACKEND = '/opt/manager/backend'
VENV = '/opt/manager/venv'
}
stages {
stage('Checkout') {
steps { checkout scm }
}
stage('Frontend Build') {
steps {
dir('frontend') {
sh 'npm ci --prefer-offline 2>/dev/null || npm install'
sh 'npm run build'
sh "cp -r dist/. ${DEPLOY_FRONTEND}/"
}
}
}
stage('Backend Deploy') {
steps {
sh """
cp -r backend/. ${DEPLOY_BACKEND}/
${VENV}/bin/pip install -r ${DEPLOY_BACKEND}/requirements.txt -q
systemctl restart guardia-manager
sleep 3
systemctl is-active guardia-manager
"""
}
}
stage('Health Check') {
steps {
sh 'curl -sf http://localhost:8002/health | grep ok'
sh 'curl -sf http://localhost:8090/ -o /dev/null -w "HTTP %{http_code}"'
}
}
}
post {
success { echo "Manager 배포 완료" }
failure { echo "배포 실패 — 로그 확인" }
}
}
```
## 서버 초기 설정 스크립트
```bash
#!/bin/bash
# deploy/init_server.sh — 최초 1회 실행
# 디렉터리 생성
mkdir -p /opt/manager/backend /var/www/manager /var/log/guardia-manager
# Python 가상환경
python3 -m venv /opt/manager/venv
/opt/manager/venv/bin/pip install -r /opt/manager/backend/requirements.txt -q
# Nginx 설정
ln -sf /etc/nginx/sites-available/guardia-manager /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx
# systemd 등록
systemctl daemon-reload
systemctl enable guardia-manager
systemctl start guardia-manager
# UFW 포트 오픈
ufw allow 8090/tcp comment 'GUARDiA Manager'
ufw allow 8002/tcp comment 'Manager Backend'
echo "Manager 초기 설정 완료"
echo "접속: http://zioinfo.co.kr:8090"
```
## Gitea 저장소 생성
```bash
# 서버에서 실행
curl -s -X POST http://localhost:3000/api/v1/user/repos \
-H "Content-Type: application/json" \
-u "zio:Zio@Admin2026!" \
-d '{"name":"guardia-manager","description":"GUARDiA 관리자 시스템","private":false,"auto_init":true}'
```

View File

@ -0,0 +1,203 @@
---
name: manager-integration
description: >
GUARDiA Manager와 외부 시스템(GUARDiA ITSM, Gitea, Ollama) 연동 코드를 구현하는 스킬.
API 클라이언트, TypeScript 타입, React 훅을 생성한다.
트리거: API 연동 구현, axios 클라이언트 작성, GUARDiA API 호출, Gitea 연동,
Ollama 모델 조회, 데이터 페칭 훅 작성 요청 시.
---
# GUARDiA Manager 연동 구현 스킬
## 연동 대상 API 목록
### GUARDiA ITSM (http://zioinfo.co.kr:8001)
| 엔드포인트 | 용도 | 필요 권한 |
|-----------|------|---------|
| `POST /api/auth/login` | 로그인 | 없음 |
| `GET /api/dashboard` | 대시보드 통계 | 로그인 |
| `GET /api/tasks` | SR 목록 | 로그인 |
| `GET /api/incidents` | 인시던트 목록 | 로그인 |
| `GET /api/cmdb/servers` | 서버 자산 목록 | 로그인 |
| `GET /api/tenant` | 테넌트 목록 | admin |
| `GET /api/external/keys` | API Key 목록 | admin |
| `GET /api/audit` | 감사 로그 | admin |
| `GET /api/metrics` | Prometheus 메트릭 | 로그인 |
### Gitea (http://zioinfo.co.kr:3000/api/v1)
| 엔드포인트 | 용도 |
|-----------|------|
| `GET /repos/search` | 저장소 목록 |
| `GET /repos/{user}/{repo}/commits` | 최신 커밋 |
| `GET /repos/{user}/{repo}/hooks` | 웹훅 목록 |
### Ollama (http://localhost:11434)
| 엔드포인트 | 용도 |
|-----------|------|
| `GET /api/tags` | 설치된 모델 목록 |
| `GET /api/ps` | 로드된 모델 |
---
## API 클라이언트 구현
### guardiaClient.ts
```typescript
// frontend/src/api/guardiaClient.ts
import axios from 'axios';
const BASE = import.meta.env.VITE_GUARDIA_API ?? 'http://zioinfo.co.kr:8001';
export const guardiaApi = axios.create({ baseURL: BASE });
// 요청 인터셉터: JWT 자동 주입
guardiaApi.interceptors.request.use((config) => {
const token = sessionStorage.getItem('guardia_token');
if (token) config.headers.Authorization = `Bearer ${token}`;
return config;
});
// 응답 인터셉터: 401 → 로그인 리다이렉트
guardiaApi.interceptors.response.use(
res => res,
err => {
if (err.response?.status === 401) {
sessionStorage.removeItem('guardia_token');
window.location.href = '/login';
}
return Promise.reject(err);
}
);
```
### types.ts — 핵심 타입 정의
```typescript
// frontend/src/api/types.ts
export interface User {
id: number;
username: string;
display_name: string;
role: 'admin' | 'pm' | 'engineer' | 'customer';
is_active: boolean;
}
export interface Server {
id: number;
hostname: string;
ip_addr: string; // ServerOut 스키마에서 제외됨 — 실제 응답에 없을 수 있음
os_type: string;
status: 'running' | 'stopped' | 'error' | 'unknown';
inst_id?: number;
}
export interface SRRequest {
id: number;
sr_id: string;
title: string;
status: string;
priority: 'CRITICAL' | 'HIGH' | 'MEDIUM' | 'LOW';
requested_by: string;
created_at: string;
}
export interface DashboardStats {
total_sr: number;
open_sr: number;
critical_incidents: number;
sla_achievement: number;
server_count: number;
running_servers: number;
}
export interface APIKey {
id: number;
name: string;
scopes: string;
is_active: boolean;
use_count: number;
last_used_at: string | null;
expires_at: string | null;
}
export interface AuditLog {
id: number;
action: string;
entity_type: string;
entity_id: string;
username: string;
ip_hash: string;
created_at: string;
}
export interface SystemResources {
cpu_percent: number;
memory: { total_gb: number; used_gb: number; percent: number };
disk: { total_gb: number; used_gb: number; percent: number };
}
export interface ServiceStatus {
[serviceName: string]: 'active' | 'inactive' | 'failed' | 'unknown';
}
```
### React 훅 — useGuardiaApi.ts
```typescript
// frontend/src/hooks/useGuardiaApi.ts
import { useState, useEffect, useCallback } from 'react';
import { guardiaApi } from '../api/guardiaClient';
export function useGuardiaApi<T>(
url: string,
options: { immediate?: boolean; deps?: unknown[] } = {}
) {
const { immediate = true, deps = [] } = options;
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const fetch = useCallback(async () => {
setLoading(true); setError(null);
try {
const res = await guardiaApi.get<T>(url);
setData(res.data);
} catch (e: any) {
setError(e.response?.data?.detail ?? '오류가 발생했습니다.');
} finally {
setLoading(false);
}
}, [url]);
useEffect(() => { if (immediate) fetch(); }, [fetch, immediate, ...deps]);
return { data, loading, error, refetch: fetch };
}
```
---
## 환경변수 설정
```typescript
// frontend/src/config/env.ts
export const ENV = {
GUARDIA_API: import.meta.env.VITE_GUARDIA_API ?? 'http://zioinfo.co.kr:8001',
MANAGER_API: import.meta.env.VITE_MANAGER_API ?? 'http://localhost:8002',
GITEA_API: import.meta.env.VITE_GITEA_API ?? 'http://zioinfo.co.kr:3000/api/v1',
GITEA_USER: import.meta.env.VITE_GITEA_USER ?? 'zio',
} as const;
```
---
## 주의사항
- **ServerOut 스키마**: GUARDiA ITSM은 보안 정책으로 `ip_addr`, `ssh_user`, `os_pw_enc` 필드를 API 응답에서 제외한다. 이 필드를 프론트엔드에 표시하려면 ITSM 쪽에 별도 admin 엔드포인트가 필요하다.
- **인증 토큰 저장**: `localStorage` 금지. `sessionStorage` 또는 메모리 변수 사용.
- **Gitea CORS**: Gitea API를 직접 호출하면 CORS 오류 발생 가능. Manager Backend의 프록시 라우터(`/api/proxy/gitea`) 를 거쳐 호출한다.

View File

@ -0,0 +1,196 @@
---
name: manager-security
description: >
GUARDiA Manager의 인증, 권한 제어, 보안 설정을 구현하는 스킬.
GUARDiA ITSM JWT 재활용, React Route Guard, API Key 관리 UI,
감사 로그 시각화를 포함한다.
트리거: 인증 구현, 로그인 페이지, Route Guard, JWT 검증,
API Key 발급/관리, 감사 로그 화면, 보안 설정 요청 시.
---
# GUARDiA Manager 보안 구현 스킬
## 인증 아키텍처
GUARDiA Manager는 별도 인증 서버 없이 **GUARDiA ITSM JWT를 공유**한다.
```
1. 사용자 → Manager 로그인 페이지
2. POST /api/auth/login (GUARDiA ITSM 8001)
3. JWT 수신 → sessionStorage 저장 (localStorage 금지)
4. 이후 모든 요청: Authorization: Bearer {token}
5. GUARDiA ITSM과 Manager Backend 양쪽에서 동일 JWT 검증
```
## useAuth 훅
```typescript
// frontend/src/hooks/useAuth.ts
import { useState, useEffect, createContext, useContext } from 'react';
import { guardiaApi } from '../api/guardiaClient';
interface AuthState {
user: { username: string; role: string; display_name: string } | null;
token: string | null;
loading: boolean;
}
export function useAuth() {
const [state, setState] = useState<AuthState>({
user: null, token: sessionStorage.getItem('guardia_token'), loading: true
});
useEffect(() => {
const token = sessionStorage.getItem('guardia_token');
if (!token) { setState(s => ({ ...s, loading: false })); return; }
guardiaApi.get('/api/auth/me')
.then(res => setState({ user: res.data, token, loading: false }))
.catch(() => {
sessionStorage.removeItem('guardia_token');
setState({ user: null, token: null, loading: false });
});
}, []);
const login = async (username: string, password: string) => {
const res = await guardiaApi.post('/api/auth/login',
new URLSearchParams({ username, password }),
{ headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }
);
const { access_token, user } = res.data;
sessionStorage.setItem('guardia_token', access_token);
setState({ user, token: access_token, loading: false });
return user;
};
const logout = () => {
sessionStorage.removeItem('guardia_token');
setState({ user: null, token: null, loading: false });
window.location.href = '/login';
};
return { ...state, login, logout };
}
```
## Route Guard 컴포넌트
```tsx
// components/common/ProtectedRoute.tsx
import { Navigate, useLocation } from 'react-router-dom';
import { useAuth } from '../../hooks/useAuth';
const ADMIN_ONLY_PATHS = [
'/api-keys', '/config', '/config/env', '/config/nginx'
];
export function ProtectedRoute({ children }: { children: React.ReactNode }) {
const { user, token, loading } = useAuth();
const location = useLocation();
if (loading) return <div style={{ padding: 40 }}>인증 확인 중...</div>;
if (!token || !user) return <Navigate to="/login" state={{ from: location }} replace />;
// admin 전용 페이지 접근 제어
if (ADMIN_ONLY_PATHS.some(p => location.pathname.startsWith(p))
&& user.role !== 'admin') {
return <div style={{ padding: 40, color: '#ef4444' }}>
관리자 권한이 필요합니다.
</div>;
}
return <>{children}</>;
}
```
## API Key 관리 화면 (M-05)
NCloud 콘솔의 "API 인증키 관리" 화면을 참조:
- 테이블: 키 이름, 스코프, 마지막 사용, 만료일, 상태
- 발급: 슬라이드 패널 → 이름/스코프/IP 제한 입력 → 발급 시 키 1회 표시
- 비활성화: 확인 모달 → `DELETE /api/external/keys/{id}`
```tsx
// pages/ApiKeys.tsx 핵심 구조
export function ApiKeys() {
const { data, loading, refetch } = useGuardiaApi<APIKey[]>('/api/external/keys');
const [creating, setCreating] = useState(false);
const [newKey, setNewKey] = useState<string | null>(null);
const handleCreate = async (form: { name: string; scopes: string; expires_days: number }) => {
const res = await guardiaApi.post('/api/external/keys', form);
setNewKey(res.data.api_key); // 1회만 노출
refetch();
};
return (
<>
{/* 발급된 키 1회 노출 모달 */}
{newKey && (
<div className="one-time-key-modal">
<h4>⚠️ API Key가 발급되었습니다 — 지금만 확인 가능합니다</h4>
<code style={{ userSelect: 'all', background: '#f0f2f5', padding: '10px 14px',
borderRadius: 6, display: 'block', wordBreak: 'break-all' }}>{newKey}</code>
<button onClick={() => { navigator.clipboard.writeText(newKey); }}>복사</button>
<button onClick={() => setNewKey(null)}>확인 완료</button>
</div>
)}
<DataTable
columns={[
{ key: 'name', header: '키 이름' },
{ key: 'scopes', header: '권한' },
{ key: 'use_count', header: '사용 횟수' },
{ key: 'is_active', header: '상태',
render: r => <StatusBadge status={r.is_active ? 'running' : 'stopped'} /> },
{ key: 'expires_at', header: '만료일' },
]}
data={data ?? []}
loading={loading}
actions={<button onClick={() => setCreating(true)}>+ API Key 발급</button>}
selectable
/>
</>
);
}
```
## 감사 로그 화면 (M-05)
```tsx
// pages/AuditLog.tsx
// - 타임라인 형태로 감사 이벤트 표시 (NCloud "활동 로그" 참조)
// - 필터: 날짜 범위, 사용자, 액션 유형
// - 데이터: GET /api/audit?page=0&size=50
// - IP Hash 표시 (원본 IP는 ITSM 보안 정책상 제공 안 함)
```
## Manager Backend JWT 검증 (core/auth.py)
```python
# backend/core/auth.py
import os
from fastapi import Depends, HTTPException
from fastapi.security import OAuth2PasswordBearer
from jose import JWTError, jwt
SECRET = os.environ.get("GUARDIA_JWT_SECRET", "guardia-jwt-secret-2026-change-me!")
ALGORITHM = "HS256"
oauth2 = OAuth2PasswordBearer(tokenUrl="/api/auth/login", auto_error=False)
async def verify_guardia_token(token: str = Depends(oauth2)):
if not token:
raise HTTPException(status_code=401, detail="인증이 필요합니다.")
try:
payload = jwt.decode(token, SECRET, algorithms=[ALGORITHM])
return payload
except JWTError:
raise HTTPException(status_code=401, detail="유효하지 않은 토큰입니다.")
```
## 보안 체크리스트
- [ ] localStorage에 토큰 저장 금지 (sessionStorage 사용)
- [ ] API Key, 비밀번호 화면 표시 마스킹 (`****`)
- [ ] 클립보드 복사 후 즉시 메모리 해제
- [ ] admin 전용 라우트 보호 완료
- [ ] CORS 출처 명시적 설정 (wildcard 금지)
- [ ] 모든 시스템 제어 API에 admin 역할 검증

View File

@ -0,0 +1,189 @@
---
name: manager-ui
description: >
GUARDiA 관리자 시스템 React UI를 구현하는 스킬.
네이버 클라우드 콘솔(NCloud Console) 디자인 패턴을 참조하여
대시보드, CMDB, 배포 관리, 사용자 관리 등 관리자 화면을 구축한다.
트리거: 관리자 UI 구현, 페이지/컴포넌트 작성, 화면 설계, 'M-0X 화면',
네이버 클라우드 스타일 적용 요청 시.
---
# GUARDiA Manager UI 구현 스킬
## 기술 스택
```json
{
"framework": "React 18 + TypeScript + Vite",
"styling": "CSS Modules (Tailwind 선택 가능)",
"routing": "react-router-dom v6",
"state": "useState/useContext (Redux 금지 — 과함)",
"http": "axios 또는 fetch API",
"charts": "recharts (경량)",
"icons": "lucide-react"
}
```
## 네이버 클라우드 콘솔 디자인 패턴
자세한 컴포넌트 패턴은 `references/ncloud-patterns.md` 참조.
### 필수 레이아웃 구조
```
┌─────────────────────────────────────────────────────────┐
│ GNB (Global Navigation Bar) — 60px 고정 │
│ [GUARDiA Manager] [서비스 검색] ─────── [알림] [계정] │
├──────────────┬──────────────────────────────────────────┤
│ │ │
│ 사이드바 │ 콘텐츠 영역 │
│ (220px) │ ┌─ 브레드크럼 ──────────────────────┐ │
│ │ │ Home > 서버 > 서버 목록 │ │
│ ▸ 대시보드 │ └─────────────────────────────────────┘ │
│ ▾ 인프라 │ ┌─ 페이지 타이틀 + 액션 버튼 ─────────┐ │
│ 서버 관리 │ │ 서버 목록 [+서버 추가] │ │
│ CMDB │ └─────────────────────────────────────┘ │
│ ▾ 배포 │ ┌─ 콘텐츠 ────────────────────────────┐ │
│ 배포 이력 │ │ 테이블 / 카드 / 폼 │ │
│ ▾ 보안 │ └─────────────────────────────────────┘ │
│ API Keys │ │
│ 감사 로그 │ │
│ ▾ 시스템 │ │
│ 설정 │ │
│ LLM 관리 │ │
└──────────────┴──────────────────────────────────────────┘
```
### 색상 시스템 (GUARDiA 브랜드 + NCloud 패턴)
```css
:root {
/* GUARDiA 브랜드 (NCloud의 #03C75A 대신 적용) */
--brand-primary: #1a3a6b;
--brand-accent: #4f6ef7;
--brand-light: #e8ecff;
/* NCloud 콘솔 참조 레이아웃 색상 */
--gnb-bg: #1a1d2e; /* 상단 바 */
--sidebar-bg: #f5f7fa; /* 사이드바 */
--sidebar-hover: #eaeef5;
--content-bg: #f0f2f5; /* 콘텐츠 배경 */
--card-bg: #ffffff;
--border: #e2e8f0;
/* 상태 색상 (NCloud 리소스 상태 배지와 동일) */
--status-running: #00c853; /* 실행중 */
--status-stopped: #9e9e9e; /* 중지 */
--status-error: #f44336; /* 오류 */
--status-pending: #ff9800; /* 진행중 */
/* 텍스트 */
--text-primary: #1e293b;
--text-secondary: #64748b;
--text-muted: #94a3b8;
}
```
### 리소스 상태 배지 (NCloud 스타일)
```tsx
// StatusBadge.tsx — NCloud 콘솔의 상태 표시와 동일한 패턴
type Status = 'running' | 'stopped' | 'error' | 'pending' | 'creating';
const STATUS_MAP = {
running: { label: '실행중', color: 'var(--status-running)', dot: true },
stopped: { label: '중지', color: 'var(--status-stopped)', dot: true },
error: { label: '오류', color: 'var(--status-error)', dot: true },
pending: { label: '진행중', color: 'var(--status-pending)', dot: true },
creating: { label: '생성중', color: 'var(--status-pending)', dot: true },
};
```
### 데이터 테이블 (NCloud 리소스 목록 스타일)
NCloud 콘솔의 리소스 목록과 동일한 패턴:
- 체크박스 선택 (벌크 액션)
- 상단 검색 + 필터
- 컬럼 헤더 정렬
- 페이지네이션 또는 무한 스크롤
- 행 클릭 → 상세 슬라이드 패널 (모달 아님)
자세한 컴포넌트 코드: `references/ncloud-patterns.md` 참조
## 프로젝트 구조
```
frontend/
├── src/
│ ├── App.tsx
│ ├── main.tsx
│ ├── layouts/
│ │ ├── AppLayout.tsx ← GNB + 사이드바 + 콘텐츠
│ │ └── AuthLayout.tsx ← 로그인 페이지 레이아웃
│ ├── pages/
│ │ ├── Dashboard.tsx ← M-01 통합 대시보드
│ │ ├── Users.tsx ← M-02 사용자 관리
│ │ ├── Servers.tsx ← M-03 서버 자산 목록
│ │ ├── ServerDetail.tsx ← M-03 서버 상세
│ │ ├── Deployments.tsx ← M-04 배포 이력
│ │ ├── ApiKeys.tsx ← M-05 API Key 관리
│ │ ├── AuditLog.tsx ← M-05 감사 로그
│ │ ├── LLMManager.tsx ← M-06 LLM 관리
│ │ └── SystemConfig.tsx ← M-07 시스템 설정
│ ├── components/
│ │ ├── layout/
│ │ │ ├── GNB.tsx
│ │ │ ├── Sidebar.tsx
│ │ │ └── Breadcrumb.tsx
│ │ ├── common/
│ │ │ ├── DataTable.tsx ← NCloud 스타일 테이블
│ │ │ ├── StatusBadge.tsx ← 상태 배지
│ │ │ ├── ResourceCard.tsx ← 서버/서비스 카드
│ │ │ ├── SlidePanel.tsx ← 우측 슬라이드 패널
│ │ │ ├── StatCard.tsx ← 통계 카드
│ │ │ └── EmptyState.tsx
│ │ └── dashboard/
│ │ ├── SRStatusChart.tsx
│ │ ├── ServerStatusGrid.tsx
│ │ └── DeployTimeline.tsx
│ ├── hooks/
│ │ ├── useAuth.ts ← JWT 인증 훅
│ │ ├── useGuardiaApi.ts ← GUARDiA ITSM API 훅
│ │ └── useManagerApi.ts ← Manager Backend API 훅
│ └── api/
│ ├── guardiaClient.ts ← GUARDiA ITSM axios 인스턴스
│ ├── managerClient.ts ← Manager Backend axios 인스턴스
│ └── types.ts ← TypeScript 타입 정의
├── index.html
├── vite.config.ts
└── package.json
```
## M-01 대시보드 구현 가이드
NCloud 콘솔의 "서비스 요약" 화면을 참조한 통합 대시보드:
```
┌── 상태 카드 행 ──────────────────────────────────────────────┐
│ [SR 현황] [인시던트] [서버 수] [SLA 달성률] │
│ 총 24건 긴급 2건 12대 98.7% │
│ 진행중 8 ↗ 중 1건 실행중 10 ▲ 0.3% │
└──────────────────────────────────────────────────────────────┘
┌── SR 추이 차트 ────────┐ ┌── 서버 상태 그리드 ───────────────┐
│ 7일 SR 생성/완료 추이 │ │ web-01 ● web-02 ● db-01 ● │
│ [꺾은선 그래프] │ │ was-01 ● was-02 ● ksb-01 ● │
└────────────────────────┘ └──────────────────────────────────┘
┌── 최근 배포 이력 ──────────────────────────────────────────────┐
│ [시간] [저장소] [커밋] [상태] [담당자] │
│ 5분 전 zio/zioinfo-web abc1234 ✅ 성공 CI Bot │
└──────────────────────────────────────────────────────────────┘
```
## 환경변수 설정
```env
# frontend/.env
VITE_GUARDIA_API=http://zioinfo.co.kr:8001
VITE_MANAGER_API=http://localhost:8002
VITE_GITEA_API=http://zioinfo.co.kr:3000/api/v1
VITE_APP_TITLE=GUARDiA Manager
```

View File

@ -0,0 +1,297 @@
# 메인 대시보드 차트 구성 가이드
> 관리자 시스템 메인화면은 대시보드 차트로 구성한다.
> 네이버 클라우드 콘솔의 "서비스 사용 현황" 및 "리소스 모니터링" 화면을 참조한다.
## 메인 대시보드 레이아웃
```
┌─────────────────────────────────────────────────────────────────────────┐
│ GUARDiA Manager — 통합 운영 대시보드 마지막 갱신: 2분 전 [↺ 새로고침] │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌── 핵심 지표 카드 (4개) ─────────────────────────────────────────────┐ │
│ │ [SR 현황] [인시던트] [서버 가용률] [SLA 달성률] │ │
│ │ 24건 긴급 2건 91.7% 98.7% │ │
│ │ 진행중 8↗ 해결중 3 11/12대 실행 ▲ 0.3% │ │
│ └──────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌── SR 추이 꺾은선 차트 ─────────┐ ┌── 서버 상태 도넛 차트 ──────────┐ │
│ │ 7일간 SR 생성 vs 완료 │ │ │ │
│ │ │ │ 실행중 10 │ │
│ │ ╭──╮ │ │ ●────────── │ │
│ │ │ ╰──╮ ╭─── │ │ ○ 중지 1 │ │
│ │ │ ╰────╯ │ │ ● 오류 1 │ │
│ │ └────────────────────────── │ │ │ │
│ │ 월 화 수 목 금 토 일 │ │ 12대 서버 │ │
│ └────────────────────────────────┘ └──────────────────────────────┘ │
│ │
│ ┌── 리소스 모니터링 막대 차트 ──────────────────────────────────────────┐ │
│ │ CPU ██████████░░ 62% 메모리 ████████████░ 78% 디스크 ████░ 16% │ │
│ └──────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌── 최근 배포 이력 타임라인 ────────┐ ┌── AI/LLM 사용 현황 ───────────┐ │
│ │ 5분전 ✅ zioinfo-web v1.2.1 │ │ llama3:8b 응답시간 2.3s │ │
│ │ 1시간 ✅ guardia-itsm v2.0.4 │ │ 요청 124건/오늘 │ │
│ │ 3시간 ❌ zioinfo-web (실패) │ │ [████████░░] 메모리 4.7GB │ │
│ └────────────────────────────────┘ └──────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
```
---
## 차트 라이브러리 선택: Recharts
```bash
npm install recharts
```
**선택 이유:** NCloud 콘솔과 유사한 심플한 차트 스타일, React 친화적, 번들 크기 적당.
---
## 1. SR 추이 꺾은선 차트 (SRTrendChart)
```tsx
// components/dashboard/SRTrendChart.tsx
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts';
interface SRTrendData {
date: string; // 'MM/DD'
created: number;
completed: number;
}
export function SRTrendChart({ data }: { data: SRTrendData[] }) {
return (
<div style={{ background: '#fff', borderRadius: 10, padding: '20px 24px',
border: '1px solid var(--border)' }}>
<h3 style={{ margin: '0 0 16px', fontSize: 14, fontWeight: 600 }}>
SR 생성/완료 추이 (7일)
</h3>
<ResponsiveContainer width="100%" height={200}>
<LineChart data={data} margin={{ top: 5, right: 10, left: -20, bottom: 5 }}>
<CartesianGrid strokeDasharray="3 3" stroke="#f0f2f5" />
<XAxis dataKey="date" tick={{ fontSize: 11, fill: '#94a3b8' }} />
<YAxis tick={{ fontSize: 11, fill: '#94a3b8' }} />
<Tooltip contentStyle={{ borderRadius: 8, border: '1px solid #e2e8f0',
fontSize: 12 }} />
<Legend wrapperStyle={{ fontSize: 12 }} />
<Line type="monotone" dataKey="created" name="신규 SR"
stroke="#4f6ef7" strokeWidth={2} dot={{ r: 3 }} />
<Line type="monotone" dataKey="completed" name="완료 SR"
stroke="#22c55e" strokeWidth={2} dot={{ r: 3 }} />
</LineChart>
</ResponsiveContainer>
</div>
);
}
```
---
## 2. 서버 상태 도넛 차트 (ServerStatusDonut)
```tsx
// components/dashboard/ServerStatusDonut.tsx
import { PieChart, Pie, Cell, Tooltip, Legend, ResponsiveContainer } from 'recharts';
const COLORS = {
running: '#22c55e',
stopped: '#94a3b8',
error: '#ef4444',
pending: '#f59e0b',
};
export function ServerStatusDonut({ servers }: { servers: { status: string }[] }) {
const counts = servers.reduce((acc, s) => {
acc[s.status] = (acc[s.status] ?? 0) + 1; return acc;
}, {} as Record<string, number>);
const data = Object.entries(counts).map(([status, value]) => ({
name: { running: '실행중', stopped: '중지', error: '오류', pending: '진행중' }[status] ?? status,
value,
status,
}));
return (
<div style={{ background: '#fff', borderRadius: 10, padding: '20px 24px',
border: '1px solid var(--border)' }}>
<h3 style={{ margin: '0 0 8px', fontSize: 14, fontWeight: 600 }}>
서버 상태 ({servers.length}대)
</h3>
<ResponsiveContainer width="100%" height={180}>
<PieChart>
<Pie data={data} cx="50%" cy="50%" innerRadius={50} outerRadius={75}
paddingAngle={2} dataKey="value">
{data.map((entry) => (
<Cell key={entry.status}
fill={COLORS[entry.status as keyof typeof COLORS] ?? '#94a3b8'} />
))}
</Pie>
<Tooltip formatter={(value, name) => [`${value}대`, name]} />
<Legend iconType="circle" iconSize={8} wrapperStyle={{ fontSize: 12 }} />
</PieChart>
</ResponsiveContainer>
</div>
);
}
```
---
## 3. 리소스 모니터링 바 차트 (ResourceGauge)
```tsx
// components/dashboard/ResourceGauge.tsx
// NCloud 콘솔의 리소스 게이지 UI 참조
interface GaugeProps {
label: string;
percent: number;
detail?: string;
}
function Gauge({ label, percent, detail }: GaugeProps) {
const color = percent > 90 ? '#ef4444' : percent > 70 ? '#f59e0b' : '#22c55e';
return (
<div style={{ flex: 1 }}>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 6,
fontSize: 12 }}>
<span style={{ fontWeight: 600 }}>{label}</span>
<span style={{ color, fontWeight: 700 }}>{percent}%</span>
</div>
<div style={{ height: 8, background: '#f0f2f5', borderRadius: 4, overflow: 'hidden' }}>
<div style={{ height: '100%', width: `${percent}%`,
background: color, borderRadius: 4,
transition: 'width 0.5s ease' }} />
</div>
{detail && <div style={{ fontSize: 11, color: '#94a3b8', marginTop: 4 }}>{detail}</div>}
</div>
);
}
export function ResourceGauge({ resources }: { resources: SystemResources }) {
return (
<div style={{ background: '#fff', borderRadius: 10, padding: '20px 24px',
border: '1px solid var(--border)' }}>
<h3 style={{ margin: '0 0 16px', fontSize: 14, fontWeight: 600 }}>
서버 리소스
</h3>
<div style={{ display: 'flex', gap: 24 }}>
<Gauge label="CPU" percent={resources.cpu_percent} />
<Gauge label="메모리"
percent={resources.memory.percent}
detail={`${resources.memory.used_gb}GB / ${resources.memory.total_gb}GB`} />
<Gauge label="디스크"
percent={resources.disk.percent}
detail={`${resources.disk.used_gb}GB / ${resources.disk.total_gb}GB`} />
</div>
</div>
);
}
```
---
## 4. 배포 이력 타임라인 (DeployTimeline)
```tsx
// components/dashboard/DeployTimeline.tsx
import { useGuardiaApi } from '../../hooks/useGuardiaApi';
function timeAgo(ts: string) {
const diff = Date.now() - new Date(ts).getTime();
if (diff < 60000) return '방금 ';
if (diff < 3600000) return `${Math.floor(diff/60000)}분 전`;
return `${Math.floor(diff/3600000)}시간 전`;
}
export function DeployTimeline() {
const { data: logs } = useGuardiaApi<string[]>('/api/deploy/history');
const parsed = (logs ?? []).slice(-5).reverse().map(line => ({
time: line.match(/\d{4}-\d{2}-\d{2} \d{2}:\d{2}/)?.[0] ?? '',
ok: line.includes('완료') || line.includes('success'),
msg: line.replace(/^\d{4}.*?INFO\s+/, '').trim().slice(0, 60),
}));
return (
<div style={{ background: '#fff', borderRadius: 10, padding: '20px 24px',
border: '1px solid var(--border)' }}>
<h3 style={{ margin: '0 0 14px', fontSize: 14, fontWeight: 600 }}>
최근 배포 이력
</h3>
<ul style={{ listStyle: 'none', padding: 0, margin: 0 }}>
{parsed.map((item, i) => (
<li key={i} style={{ display: 'flex', gap: 10, padding: '8px 0',
borderBottom: i < parsed.length - 1 ? '1px solid #f1f5f9' : 'none',
fontSize: 13 }}>
<span>{item.ok ? '✅' : '❌'}</span>
<span style={{ flex: 1, color: '#1e293b' }}>{item.msg}</span>
<span style={{ color: '#94a3b8', fontSize: 11, whiteSpace: 'nowrap' }}>
{item.time}
</span>
</li>
))}
{!parsed.length && (
<li style={{ color: '#94a3b8', fontSize: 13 }}>배포 이력이 없습니다.</li>
)}
</ul>
</div>
);
}
```
---
## Dashboard.tsx 전체 조합
```tsx
// pages/Dashboard.tsx
import { StatCard } from '../components/common/StatCard';
import { SRTrendChart } from '../components/dashboard/SRTrendChart';
import { ServerStatusDonut } from '../components/dashboard/ServerStatusDonut';
import { ResourceGauge } from '../components/dashboard/ResourceGauge';
import { DeployTimeline } from '../components/dashboard/DeployTimeline';
import { useGuardiaApi } from '../hooks/useGuardiaApi';
import { useManagerApi } from '../hooks/useManagerApi';
export function Dashboard() {
const { data: stats } = useGuardiaApi('/api/dashboard');
const { data: resources } = useManagerApi('/api/system/resources');
const { data: servers } = useGuardiaApi('/api/cmdb/servers?limit=100');
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: 20 }}>
{/* 핵심 지표 카드 */}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 16 }}>
<StatCard title="전체 SR" value={stats?.total_sr ?? '-'}
sub={`진행중 ${stats?.open_sr ?? 0}건`} icon="📋" color="#e8ecff" />
<StatCard title="인시던트" value={stats?.critical_incidents ?? '-'}
sub="긴급" icon="🚨" color="#fff1f2" />
<StatCard title="서버 가용률"
value={servers ? `${((servers.filter((s:any) => s.status==='running').length / servers.length)*100).toFixed(1)}%` : '-'}
icon="🖥️" color="#f0fdf4" />
<StatCard title="SLA 달성률" value={stats?.sla_achievement ? `${stats.sla_achievement}%` : '-'}
icon="📈" color="#fff7ed" />
</div>
{/* 차트 행 1: SR 추이 + 서버 상태 */}
<div style={{ display: 'grid', gridTemplateColumns: '2fr 1fr', gap: 16 }}>
<SRTrendChart data={stats?.sr_trend ?? []} />
<ServerStatusDonut servers={servers ?? []} />
</div>
{/* 리소스 게이지 */}
{resources && <ResourceGauge resources={resources} />}
{/* 차트 행 2: 배포 이력 + LLM */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 16 }}>
<DeployTimeline />
{/* LLM 상태 카드 추가 가능 */}
</div>
</div>
);
}
```

View File

@ -0,0 +1,337 @@
# 네이버 클라우드 콘솔 UI 패턴 참조
> GUARDiA Manager UI는 네이버 클라우드 콘솔(console.ncloud.com)의 디자인 패턴을 참조한다.
> 아래 코드 스니펫은 해당 패턴을 GUARDiA Manager에 맞게 재구현한 것이다.
---
## 1. DataTable 컴포넌트 (NCloud 리소스 목록)
```tsx
// components/common/DataTable.tsx
import { useState } from 'react';
import styles from './DataTable.module.css';
interface Column<T> {
key: keyof T | string;
header: string;
width?: string;
render?: (row: T) => React.ReactNode;
sortable?: boolean;
}
interface DataTableProps<T extends { id: string | number }> {
columns: Column<T>[];
data: T[];
onRowClick?: (row: T) => void;
actions?: React.ReactNode; // 상단 액션 버튼
loading?: boolean;
emptyMessage?: string;
selectable?: boolean;
onSelectionChange?: (selected: T[]) => void;
}
export function DataTable<T extends { id: string | number }>({
columns, data, onRowClick, actions, loading, emptyMessage,
selectable = false, onSelectionChange,
}: DataTableProps<T>) {
const [selected, setSelected] = useState<Set<string | number>>(new Set());
const [sortKey, setSortKey] = useState<string | null>(null);
const [sortDir, setSortDir] = useState<'asc' | 'desc'>('asc');
const toggleAll = () => {
const next = selected.size === data.length
? new Set<string | number>()
: new Set(data.map(r => r.id));
setSelected(next);
onSelectionChange?.(data.filter(r => next.has(r.id)));
};
const toggleRow = (id: string | number) => {
const next = new Set(selected);
next.has(id) ? next.delete(id) : next.add(id);
setSelected(next);
onSelectionChange?.(data.filter(r => next.has(r.id)));
};
return (
<div className={styles.wrapper}>
{/* 상단 액션 영역 */}
{actions && (
<div className={styles.toolbar}>
{selected.size > 0 && (
<span className={styles.selectionCount}>{selected.size}개 선택됨</span>
)}
<div className={styles.actions}>{actions}</div>
</div>
)}
<table className={styles.table}>
<thead>
<tr>
{selectable && (
<th className={styles.checkboxCol}>
<input type="checkbox"
checked={selected.size === data.length && data.length > 0}
onChange={toggleAll} />
</th>
)}
{columns.map(col => (
<th key={String(col.key)}
style={{ width: col.width }}
className={col.sortable ? styles.sortable : ''}
onClick={() => col.sortable && (setSortKey(String(col.key)),
setSortDir(d => d === 'asc' ? 'desc' : 'asc'))}>
{col.header}
{sortKey === String(col.key) && (
<span>{sortDir === 'asc' ? ' ↑' : ' ↓'}</span>
)}
</th>
))}
</tr>
</thead>
<tbody>
{loading ? (
<tr><td colSpan={columns.length + 1}
className={styles.loading}>로딩 중...</td></tr>
) : data.length === 0 ? (
<tr><td colSpan={columns.length + 1}
className={styles.empty}>{emptyMessage ?? '데이터가 없습니다.'}</td></tr>
) : data.map(row => (
<tr key={row.id}
className={`${styles.row} ${onRowClick ? styles.clickable : ''}`}
onClick={() => onRowClick?.(row)}>
{selectable && (
<td className={styles.checkboxCol} onClick={e => e.stopPropagation()}>
<input type="checkbox"
checked={selected.has(row.id)}
onChange={() => toggleRow(row.id)} />
</td>
)}
{columns.map(col => (
<td key={String(col.key)}>
{col.render ? col.render(row) : String((row as any)[col.key] ?? '')}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
);
}
```
---
## 2. ResourceCard 컴포넌트 (서버/서비스 카드)
```tsx
// components/common/ResourceCard.tsx
import { StatusBadge } from './StatusBadge';
interface ResourceCardProps {
name: string;
type: string; // '서버' | 'DB' | 'WAS' | 'API'
status: 'running' | 'stopped' | 'error' | 'pending';
spec?: string; // '2vCPU / 4GB' 등
ip?: string;
onClick?: () => void;
}
export function ResourceCard({ name, type, status, spec, ip, onClick }: ResourceCardProps) {
return (
<div className="resource-card" onClick={onClick} style={{
background: '#fff',
border: '1px solid var(--border)',
borderRadius: 8,
padding: '16px',
cursor: onClick ? 'pointer' : 'default',
transition: 'box-shadow 0.15s',
}}>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 8 }}>
<span style={{ fontSize: 12, color: 'var(--text-muted)',
background: 'var(--brand-light)', padding: '2px 8px', borderRadius: 4 }}>
{type}
</span>
<StatusBadge status={status} />
</div>
<div style={{ fontWeight: 600, fontSize: 14, marginBottom: 4 }}>{name}</div>
{spec && <div style={{ fontSize: 12, color: 'var(--text-secondary)' }}>{spec}</div>}
{ip && <div style={{ fontSize: 12, color: 'var(--text-muted)', marginTop: 4 }}>{ip}</div>}
</div>
);
}
```
---
## 3. SlidePanel 컴포넌트 (NCloud 상세 정보 패널)
NCloud 콘솔에서 리소스 클릭 시 우측에서 슬라이드하는 상세 패널.
```tsx
// components/common/SlidePanel.tsx
import { useEffect } from 'react';
interface SlidePanelProps {
open: boolean;
onClose: () => void;
title: string;
width?: number; // 기본 480px
children: React.ReactNode;
actions?: React.ReactNode;
}
export function SlidePanel({ open, onClose, title, width = 480, children, actions }: SlidePanelProps) {
useEffect(() => {
const handler = (e: KeyboardEvent) => e.key === 'Escape' && onClose();
document.addEventListener('keydown', handler);
return () => document.removeEventListener('keydown', handler);
}, [onClose]);
return (
<>
{/* 오버레이 */}
{open && (
<div onClick={onClose} style={{
position: 'fixed', inset: 0,
background: 'rgba(0,0,0,0.3)', zIndex: 200
}} />
)}
{/* 패널 */}
<div style={{
position: 'fixed', top: 0, right: 0, bottom: 0,
width, background: '#fff', zIndex: 201,
transform: open ? 'translateX(0)' : `translateX(${width}px)`,
transition: 'transform 0.25s ease',
display: 'flex', flexDirection: 'column',
boxShadow: '-4px 0 24px rgba(0,0,0,0.15)',
}}>
{/* 헤더 */}
<div style={{ padding: '20px 24px 16px', borderBottom: '1px solid var(--border)',
display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<h3 style={{ margin: 0, fontSize: 16, fontWeight: 600 }}>{title}</h3>
<button onClick={onClose} style={{ background: 'none', border: 'none',
cursor: 'pointer', fontSize: 18, color: 'var(--text-muted)' }}>✕</button>
</div>
{/* 콘텐츠 */}
<div style={{ flex: 1, overflow: 'auto', padding: '20px 24px' }}>{children}</div>
{/* 푸터 액션 */}
{actions && (
<div style={{ padding: '16px 24px', borderTop: '1px solid var(--border)',
display: 'flex', gap: 8, justifyContent: 'flex-end' }}>
{actions}
</div>
)}
</div>
</>
);
}
```
---
## 4. StatCard 컴포넌트 (대시보드 통계 카드)
```tsx
// components/common/StatCard.tsx
interface StatCardProps {
title: string;
value: string | number;
sub?: string;
trend?: { value: number; positive: boolean };
icon?: string;
color?: string; // 아이콘 배경색
onClick?: () => void;
}
export function StatCard({ title, value, sub, trend, icon, color = 'var(--brand-light)', onClick }: StatCardProps) {
return (
<div onClick={onClick} style={{
background: '#fff', border: '1px solid var(--border)',
borderRadius: 10, padding: '20px',
cursor: onClick ? 'pointer' : 'default',
}}>
<div style={{ display: 'flex', alignItems: 'center', gap: 14 }}>
{icon && (
<div style={{ width: 44, height: 44, borderRadius: 10,
background: color, display: 'flex', alignItems: 'center',
justifyContent: 'center', fontSize: 20, flexShrink: 0 }}>
{icon}
</div>
)}
<div>
<div style={{ fontSize: 24, fontWeight: 700, color: 'var(--text-primary)' }}>
{value}
</div>
<div style={{ fontSize: 12, color: 'var(--text-muted)' }}>{title}</div>
{sub && <div style={{ fontSize: 11, color: 'var(--text-muted)' }}>{sub}</div>}
</div>
</div>
{trend && (
<div style={{ marginTop: 10, fontSize: 11,
color: trend.positive ? 'var(--status-running)' : 'var(--status-error)' }}>
{trend.positive ? '▲' : '▼'} {Math.abs(trend.value)}% 전주 대비
</div>
)}
</div>
);
}
```
---
## 5. 사이드바 네비게이션 (NCloud 서비스 트리 스타일)
```tsx
const MENU = [
{ label: '대시보드', icon: '📊', path: '/' },
{
label: '인프라 관리', icon: '🖥️',
children: [
{ label: '서버 목록', path: '/servers' },
{ label: 'CMDB 현황', path: '/cmdb' },
{ label: 'SSH 자격증명', path: '/credentials' },
]
},
{
label: '배포/CI-CD', icon: '🚀',
children: [
{ label: '배포 이력', path: '/deployments' },
{ label: '저장소 목록', path: '/repos' },
{ label: '서비스 상태', path: '/services' },
]
},
{
label: '사용자/테넌트', icon: '👥',
children: [
{ label: '사용자 관리', path: '/users' },
{ label: '기관 관리', path: '/institutions' },
{ label: '역할 설정', path: '/roles' },
]
},
{
label: '보안', icon: '🔒',
children: [
{ label: 'API Key 관리', path: '/api-keys' },
{ label: '감사 로그', path: '/audit' },
{ label: '취약점 현황', path: '/vulns' },
]
},
{
label: 'AI/LLM', icon: '🤖',
children: [
{ label: 'Ollama 모델', path: '/llm' },
{ label: 'AI 에이전트', path: '/agents' },
]
},
{
label: '시스템 설정', icon: '⚙️',
children: [
{ label: '환경변수', path: '/config/env' },
{ label: 'Nginx 설정', path: '/config/nginx' },
{ label: '알림 설정', path: '/config/notify' },
]
},
];
```

View File

@ -18,7 +18,7 @@ GUARDiA ITSM·홈페이지·서버 인프라·CI/CD를 단일 화면에서 통
| Frontend | React 18 + TypeScript + Vite | 독립 SPA |
| Backend | Python FastAPI (경량) | 시스템 수준 작업 전용 |
| 인증 | GUARDiA ITSM JWT 공유 | 별도 DB 없음 |
| 연동 | GUARDiA ITSM REST API | http://101.79.17.164:8001 |
| 연동 | GUARDiA ITSM REST API | http://zioinfo.co.kr:8001 |
| 배포 | Gitea + Deploy Webhook | 포트 9999 |
---

21
backend/core/auth.py Normal file
View File

@ -0,0 +1,21 @@
import os
from fastapi import Depends, HTTPException
from fastapi.security import OAuth2PasswordBearer
from jose import JWTError, jwt
SECRET = os.environ.get("GUARDIA_JWT_SECRET", "guardia-jwt-secret-2026-change-me!")
ALGORITHM = "HS256"
oauth2 = OAuth2PasswordBearer(tokenUrl="/guardia-api/api/auth/login", auto_error=False)
async def verify_token(token: str = Depends(oauth2)) -> dict:
if not token:
raise HTTPException(status_code=401, detail="인증이 필요합니다.")
try:
return jwt.decode(token, SECRET, algorithms=[ALGORITHM])
except JWTError:
raise HTTPException(status_code=401, detail="유효하지 않은 토큰입니다.")
async def require_admin(payload: dict = Depends(verify_token)) -> dict:
if payload.get("role") not in ("admin",):
raise HTTPException(status_code=403, detail="관리자 권한이 필요합니다.")
return payload

30
backend/main.py Normal file
View File

@ -0,0 +1,30 @@
"""GUARDiA Manager 경량 백엔드 API — 포트 8002"""
import os
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from dotenv import load_dotenv
load_dotenv()
app = FastAPI(title="GUARDiA Manager API", version="1.0.0")
app.add_middleware(
CORSMiddleware,
allow_origins=os.environ.get(
"MANAGER_ALLOWED_ORIGINS",
"http://localhost:5175,http://localhost:5173,http://zioinfo.co.kr:8090"
).split(","),
allow_methods=["*"],
allow_headers=["*"],
allow_credentials=True,
)
from routers import system, deploy, config, llm
app.include_router(system.router, prefix="/api/system", tags=["system"])
app.include_router(deploy.router, prefix="/api/deploy", tags=["deploy"])
app.include_router(config.router, prefix="/api/config", tags=["config"])
app.include_router(llm.router, prefix="/api/llm", tags=["llm"])
@app.get("/health")
async def health():
return {"status": "ok", "service": "guardia-manager", "port": 8002}

6
backend/requirements.txt Normal file
View File

@ -0,0 +1,6 @@
fastapi==0.115.0
uvicorn[standard]==0.30.0
httpx==0.27.0
psutil==5.9.8
python-jose[cryptography]==3.3.0
python-dotenv==1.0.1

36
backend/routers/config.py Normal file
View File

@ -0,0 +1,36 @@
import os, subprocess
from fastapi import APIRouter, Depends, HTTPException
from core.auth import require_admin
router = APIRouter()
ENV_FILES = [
"/opt/guardia/app/.env",
"/opt/manager/backend/.env",
]
SENSITIVE = {"SECRET", "PASSWORD", "KEY", "TOKEN", "PASS", "PWD"}
def _mask(k: str, v: str) -> str:
return "****" if any(s in k.upper() for s in SENSITIVE) else v
@router.get("/env")
async def get_env(_=Depends(require_admin)):
result: dict[str, str] = {}
for path in ENV_FILES:
try:
with open(path) as f:
for line in f:
line = line.strip()
if line and not line.startswith("#") and "=" in line:
k, _, v = line.partition("=")
result[k.strip()] = _mask(k.strip(), v.strip())
except FileNotFoundError:
pass
return result
@router.post("/nginx/reload")
async def nginx_reload(_=Depends(require_admin)):
test = subprocess.run(["nginx", "-t"], capture_output=True, text=True)
if test.returncode != 0:
raise HTTPException(status_code=400, detail=f"설정 오류: {test.stderr[:300]}")
subprocess.run(["systemctl", "reload", "nginx"])
return {"message": "Nginx 리로드 완료"}

27
backend/routers/deploy.py Normal file
View File

@ -0,0 +1,27 @@
import httpx, os
from fastapi import APIRouter, Depends, HTTPException
from core.auth import verify_token
router = APIRouter()
WEBHOOK_URL = os.environ.get("DEPLOY_WEBHOOK", "http://localhost:9999/")
DEPLOY_LOG = os.environ.get("DEPLOY_LOG", "/var/log/zioinfo/deploy.log")
@router.post("/trigger/{repo}")
async def trigger(repo: str, user=Depends(verify_token)):
async with httpx.AsyncClient() as c:
try:
r = await c.post(WEBHOOK_URL,
json={"repo": repo, "triggered_by": user.get("sub", "manager")},
timeout=10)
return {"status": r.status_code, "message": f"{repo} 배포 트리거됨"}
except Exception as e:
raise HTTPException(status_code=502, detail=f"Deploy Webhook 연결 실패: {e}")
@router.get("/history")
async def history(_=Depends(verify_token)):
try:
with open(DEPLOY_LOG, encoding="utf-8", errors="replace") as f:
lines = f.readlines()[-200:]
return {"lines": [l.rstrip() for l in lines]}
except FileNotFoundError:
return {"lines": []}

15
backend/routers/llm.py Normal file
View File

@ -0,0 +1,15 @@
import httpx, os
from fastapi import APIRouter, Depends
from core.auth import verify_token
router = APIRouter()
OLLAMA = os.environ.get("OLLAMA_URL", "http://localhost:11434")
@router.get("/models")
async def models(_=Depends(verify_token)):
async with httpx.AsyncClient() as c:
try:
r = await c.get(f"{OLLAMA}/api/tags", timeout=5)
return r.json()
except Exception:
return {"models": []}

42
backend/routers/system.py Normal file
View File

@ -0,0 +1,42 @@
import subprocess
from fastapi import APIRouter, Depends, HTTPException
from core.auth import verify_token, require_admin
router = APIRouter()
ALLOWED_SVCS = {
"nginx", "zioinfo", "zioinfo-deploy", "guardia", "guardia-manager",
"gitea", "jenkins", "postgresql", "ollama",
}
@router.get("/resources")
async def resources(_=Depends(verify_token)):
try:
import psutil
cpu = psutil.cpu_percent(interval=0.2)
mem = psutil.virtual_memory()
disk = psutil.disk_usage("/")
return {
"cpu_percent": round(cpu, 1),
"memory": {"total_gb": round(mem.total/1e9,1), "used_gb": round(mem.used/1e9,1), "percent": mem.percent},
"disk": {"total_gb": round(disk.total/1e9,1),"used_gb": round(disk.used/1e9,1), "percent": disk.percent},
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/services")
async def services(_=Depends(verify_token)):
result: dict[str, str] = {}
for svc in ALLOWED_SVCS:
p = subprocess.run(["systemctl", "is-active", svc], capture_output=True, text=True)
result[svc] = p.stdout.strip()
return result
@router.post("/services/{name}/restart")
async def restart(name: str, admin=Depends(require_admin)):
if name not in ALLOWED_SVCS:
raise HTTPException(status_code=400, detail="허용되지 않은 서비스")
p = subprocess.run(["systemctl", "restart", name], capture_output=True, text=True)
if p.returncode != 0:
raise HTTPException(status_code=500, detail=p.stderr[:300])
return {"message": f"{name} 재시작 완료"}

156
deploy_server.py Normal file
View File

@ -0,0 +1,156 @@
#!/usr/bin/env python3
"""GUARDiA Manager 서버 배포 스크립트"""
import paramiko, time, sys, os, io, zipfile
HOST = 'zioinfo.co.kr'; USER = 'root'; PASS = '1q2w3e!Q'
LOCAL_DIST = 'C:/GUARDiA/manager/dist'
LOCAL_BACKEND = 'C:/GUARDiA/manager/backend'
SEP = chr(92)
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(HOST, username=USER, password=PASS, timeout=15)
sftp = client.open_sftp()
def run(label, cmd, timeout=60):
print(f'\n[{label}]')
chan = client.get_transport().open_session()
chan.set_combine_stderr(True)
chan.exec_command(cmd)
start = time.time()
while not chan.exit_status_ready():
if chan.recv_ready(): sys.stdout.buffer.write(chan.recv(4096)); sys.stdout.flush()
if time.time() - start > timeout: print('[TIMEOUT]'); break
time.sleep(0.2)
while chan.recv_ready(): sys.stdout.buffer.write(chan.recv(4096))
sys.stdout.flush()
rc = chan.recv_exit_status()
print(f'\n→ exit={rc}')
return rc
# 1. 서버 디렉터리 생성
run('디렉터리 생성',
'mkdir -p /var/www/manager /opt/manager/backend /opt/manager/logs && echo ok')
# 2. React 빌드 결과 zip → 업로드
print('\n[dist 파일 패키징...]')
zip_buf = io.BytesIO()
count = 0
with zipfile.ZipFile(zip_buf, 'w', zipfile.ZIP_DEFLATED) as zf:
for root, dirs, files in os.walk(LOCAL_DIST):
rel = os.path.relpath(root, LOCAL_DIST).replace(SEP, '/')
for f in files:
arc = f if rel == '.' else f'{rel}/{f}'
zf.write(os.path.join(root, f), arc); count += 1
zip_buf.seek(0)
with sftp.open('/tmp/manager-dist.zip', 'wb') as f: f.write(zip_buf.read())
print(f'{count}개 파일 업로드')
run('dist 배포', 'cd /var/www/manager && unzip -q -o /tmp/manager-dist.zip && echo "deployed" && ls | head -5')
# 3. 백엔드 소스 업로드
print('\n[백엔드 소스 업로드...]')
for item in os.listdir(LOCAL_BACKEND):
lp = os.path.join(LOCAL_BACKEND, item)
if os.path.isfile(lp) and item.endswith('.py'):
sftp.put(lp, f'/opt/manager/backend/{item}')
print(f' {item}')
# routers / core
for sub in ('routers', 'core'):
sub_dir = os.path.join(LOCAL_BACKEND, sub)
if not os.path.isdir(sub_dir): continue
run(f'{sub} 디렉터리', f'mkdir -p /opt/manager/backend/{sub}')
for fn in os.listdir(sub_dir):
if fn.endswith('.py'):
sftp.put(os.path.join(sub_dir, fn), f'/opt/manager/backend/{sub}/{fn}')
print(f' {sub}/{fn}')
# requirements + .env
for fn in ('requirements.txt', '.env'):
lp = os.path.join(LOCAL_BACKEND, fn)
if os.path.exists(lp):
sftp.put(lp, f'/opt/manager/backend/{fn}')
print(f' {fn}')
# 4. Python venv + 패키지
run('Python venv',
'python3 -m venv /opt/manager/venv && '
'/opt/manager/venv/bin/pip install -r /opt/manager/backend/requirements.txt -q && echo ok',
120)
# 5. .env 서버 JWT secret 동기화
run('.env JWT secret 동기화',
"GUARDIA_SECRET=$(grep GUARDIA_JWT_SECRET /opt/guardia/app/.env 2>/dev/null | cut -d= -f2-) && "
"if [ -n \"$GUARDIA_SECRET\" ]; then "
" sed -i \"s|GUARDIA_JWT_SECRET=.*|GUARDIA_JWT_SECRET=$GUARDIA_SECRET|\" /opt/manager/backend/.env && "
" echo 'JWT secret synced'; fi")
# 6. systemd 서비스
svc = """[Unit]
Description=GUARDiA Manager API Backend
After=network.target guardia.service
[Service]
User=root
WorkingDirectory=/opt/manager/backend
EnvironmentFile=-/opt/manager/backend/.env
ExecStart=/opt/manager/venv/bin/uvicorn main:app --host 127.0.0.1 --port 8002 --workers 1
Restart=on-failure
RestartSec=5
StandardOutput=append:/opt/manager/logs/backend.log
StandardError=append:/opt/manager/logs/backend.log
[Install]
WantedBy=multi-user.target
"""
with sftp.open('/etc/systemd/system/guardia-manager.service', 'w') as f: f.write(svc)
# 7. Nginx 설정
nginx_conf = r"""server {
listen 8090;
server_name _;
root /var/www/manager;
index index.html;
location / {
try_files $uri $uri/ /index.html;
add_header Cache-Control no-cache;
}
location /api/ {
proxy_pass http://127.0.0.1:8002;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_read_timeout 60s;
}
location /guardia-api/ {
proxy_pass http://127.0.0.1:8001/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location ~* \.(js|css|png|ico|svg|woff2)$ {
expires 7d;
add_header Cache-Control "public, immutable";
}
gzip on;
gzip_types text/plain text/css application/javascript application/json;
}
"""
with sftp.open('/etc/nginx/sites-available/guardia-manager', 'w') as f: f.write(nginx_conf)
# 8. 서비스 시작
run('UFW 8090 오픈', 'ufw allow 8090/tcp && ufw allow 8002/tcp && echo ok')
run('systemd 등록 + 시작',
'systemctl daemon-reload && '
'systemctl enable guardia-manager && '
'systemctl restart guardia-manager && '
'sleep 5 && systemctl is-active guardia-manager')
run('Nginx 활성화',
'ln -sf /etc/nginx/sites-available/guardia-manager /etc/nginx/sites-enabled/ && '
'nginx -t && systemctl reload nginx && echo NGINX_OK')
# 9. 헬스체크
run('헬스체크', 'curl -s http://localhost:8002/health && echo "" && '
'curl -s -o /dev/null -w "Manager UI: HTTP %{http_code}" http://localhost:8090/')
sftp.close(); client.close()
print('\n\n=== 배포 완료 ===')
print('GUARDiA Manager: http://zioinfo.co.kr:8090')

13
frontend/index.html Normal file
View File

@ -0,0 +1,13 @@
<!doctype html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>GUARDiA Manager</title>
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

16
frontend/node_modules/.bin/baseline-browser-mapping generated vendored Normal file
View File

@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../baseline-browser-mapping/dist/cli.cjs" "$@"
else
exec node "$basedir/../baseline-browser-mapping/dist/cli.cjs" "$@"
fi

View File

@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\baseline-browser-mapping\dist\cli.cjs" %*

View File

@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../baseline-browser-mapping/dist/cli.cjs" $args
} else {
& "$basedir/node$exe" "$basedir/../baseline-browser-mapping/dist/cli.cjs" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../baseline-browser-mapping/dist/cli.cjs" $args
} else {
& "node$exe" "$basedir/../baseline-browser-mapping/dist/cli.cjs" $args
}
$ret=$LASTEXITCODE
}
exit $ret

16
frontend/node_modules/.bin/browserslist generated vendored Normal file
View File

@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../browserslist/cli.js" "$@"
else
exec node "$basedir/../browserslist/cli.js" "$@"
fi

17
frontend/node_modules/.bin/browserslist.cmd generated vendored Normal file
View File

@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\browserslist\cli.js" %*

28
frontend/node_modules/.bin/browserslist.ps1 generated vendored Normal file
View File

@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../browserslist/cli.js" $args
} else {
& "$basedir/node$exe" "$basedir/../browserslist/cli.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../browserslist/cli.js" $args
} else {
& "node$exe" "$basedir/../browserslist/cli.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret

16
frontend/node_modules/.bin/esbuild generated vendored Normal file
View File

@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../esbuild/bin/esbuild" "$@"
else
exec node "$basedir/../esbuild/bin/esbuild" "$@"
fi

17
frontend/node_modules/.bin/esbuild.cmd generated vendored Normal file
View File

@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\esbuild\bin\esbuild" %*

28
frontend/node_modules/.bin/esbuild.ps1 generated vendored Normal file
View File

@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../esbuild/bin/esbuild" $args
} else {
& "$basedir/node$exe" "$basedir/../esbuild/bin/esbuild" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../esbuild/bin/esbuild" $args
} else {
& "node$exe" "$basedir/../esbuild/bin/esbuild" $args
}
$ret=$LASTEXITCODE
}
exit $ret

16
frontend/node_modules/.bin/jsesc generated vendored Normal file
View File

@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../jsesc/bin/jsesc" "$@"
else
exec node "$basedir/../jsesc/bin/jsesc" "$@"
fi

17
frontend/node_modules/.bin/jsesc.cmd generated vendored Normal file
View File

@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\jsesc\bin\jsesc" %*

28
frontend/node_modules/.bin/jsesc.ps1 generated vendored Normal file
View File

@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../jsesc/bin/jsesc" $args
} else {
& "$basedir/node$exe" "$basedir/../jsesc/bin/jsesc" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../jsesc/bin/jsesc" $args
} else {
& "node$exe" "$basedir/../jsesc/bin/jsesc" $args
}
$ret=$LASTEXITCODE
}
exit $ret

16
frontend/node_modules/.bin/json5 generated vendored Normal file
View File

@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../json5/lib/cli.js" "$@"
else
exec node "$basedir/../json5/lib/cli.js" "$@"
fi

17
frontend/node_modules/.bin/json5.cmd generated vendored Normal file
View File

@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\json5\lib\cli.js" %*

28
frontend/node_modules/.bin/json5.ps1 generated vendored Normal file
View File

@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../json5/lib/cli.js" $args
} else {
& "$basedir/node$exe" "$basedir/../json5/lib/cli.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../json5/lib/cli.js" $args
} else {
& "node$exe" "$basedir/../json5/lib/cli.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret

16
frontend/node_modules/.bin/loose-envify generated vendored Normal file
View File

@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../loose-envify/cli.js" "$@"
else
exec node "$basedir/../loose-envify/cli.js" "$@"
fi

17
frontend/node_modules/.bin/loose-envify.cmd generated vendored Normal file
View File

@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\loose-envify\cli.js" %*

28
frontend/node_modules/.bin/loose-envify.ps1 generated vendored Normal file
View File

@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../loose-envify/cli.js" $args
} else {
& "$basedir/node$exe" "$basedir/../loose-envify/cli.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../loose-envify/cli.js" $args
} else {
& "node$exe" "$basedir/../loose-envify/cli.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret

16
frontend/node_modules/.bin/nanoid generated vendored Normal file
View File

@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../nanoid/bin/nanoid.cjs" "$@"
else
exec node "$basedir/../nanoid/bin/nanoid.cjs" "$@"
fi

17
frontend/node_modules/.bin/nanoid.cmd generated vendored Normal file
View File

@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\nanoid\bin\nanoid.cjs" %*

28
frontend/node_modules/.bin/nanoid.ps1 generated vendored Normal file
View File

@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../nanoid/bin/nanoid.cjs" $args
} else {
& "$basedir/node$exe" "$basedir/../nanoid/bin/nanoid.cjs" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../nanoid/bin/nanoid.cjs" $args
} else {
& "node$exe" "$basedir/../nanoid/bin/nanoid.cjs" $args
}
$ret=$LASTEXITCODE
}
exit $ret

16
frontend/node_modules/.bin/parser generated vendored Normal file
View File

@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../@babel/parser/bin/babel-parser.js" "$@"
else
exec node "$basedir/../@babel/parser/bin/babel-parser.js" "$@"
fi

17
frontend/node_modules/.bin/parser.cmd generated vendored Normal file
View File

@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\@babel\parser\bin\babel-parser.js" %*

28
frontend/node_modules/.bin/parser.ps1 generated vendored Normal file
View File

@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../@babel/parser/bin/babel-parser.js" $args
} else {
& "$basedir/node$exe" "$basedir/../@babel/parser/bin/babel-parser.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../@babel/parser/bin/babel-parser.js" $args
} else {
& "node$exe" "$basedir/../@babel/parser/bin/babel-parser.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret

16
frontend/node_modules/.bin/rollup generated vendored Normal file
View File

@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../rollup/dist/bin/rollup" "$@"
else
exec node "$basedir/../rollup/dist/bin/rollup" "$@"
fi

17
frontend/node_modules/.bin/rollup.cmd generated vendored Normal file
View File

@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\rollup\dist\bin\rollup" %*

28
frontend/node_modules/.bin/rollup.ps1 generated vendored Normal file
View File

@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../rollup/dist/bin/rollup" $args
} else {
& "$basedir/node$exe" "$basedir/../rollup/dist/bin/rollup" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../rollup/dist/bin/rollup" $args
} else {
& "node$exe" "$basedir/../rollup/dist/bin/rollup" $args
}
$ret=$LASTEXITCODE
}
exit $ret

16
frontend/node_modules/.bin/semver generated vendored Normal file
View File

@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../semver/bin/semver.js" "$@"
else
exec node "$basedir/../semver/bin/semver.js" "$@"
fi

17
frontend/node_modules/.bin/semver.cmd generated vendored Normal file
View File

@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\semver\bin\semver.js" %*

28
frontend/node_modules/.bin/semver.ps1 generated vendored Normal file
View File

@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../semver/bin/semver.js" $args
} else {
& "$basedir/node$exe" "$basedir/../semver/bin/semver.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../semver/bin/semver.js" $args
} else {
& "node$exe" "$basedir/../semver/bin/semver.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret

16
frontend/node_modules/.bin/tsc generated vendored Normal file
View File

@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../typescript/bin/tsc" "$@"
else
exec node "$basedir/../typescript/bin/tsc" "$@"
fi

17
frontend/node_modules/.bin/tsc.cmd generated vendored Normal file
View File

@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\typescript\bin\tsc" %*

28
frontend/node_modules/.bin/tsc.ps1 generated vendored Normal file
View File

@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../typescript/bin/tsc" $args
} else {
& "$basedir/node$exe" "$basedir/../typescript/bin/tsc" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../typescript/bin/tsc" $args
} else {
& "node$exe" "$basedir/../typescript/bin/tsc" $args
}
$ret=$LASTEXITCODE
}
exit $ret

16
frontend/node_modules/.bin/tsserver generated vendored Normal file
View File

@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../typescript/bin/tsserver" "$@"
else
exec node "$basedir/../typescript/bin/tsserver" "$@"
fi

17
frontend/node_modules/.bin/tsserver.cmd generated vendored Normal file
View File

@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\typescript\bin\tsserver" %*

28
frontend/node_modules/.bin/tsserver.ps1 generated vendored Normal file
View File

@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../typescript/bin/tsserver" $args
} else {
& "$basedir/node$exe" "$basedir/../typescript/bin/tsserver" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../typescript/bin/tsserver" $args
} else {
& "node$exe" "$basedir/../typescript/bin/tsserver" $args
}
$ret=$LASTEXITCODE
}
exit $ret

16
frontend/node_modules/.bin/update-browserslist-db generated vendored Normal file
View File

@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../update-browserslist-db/cli.js" "$@"
else
exec node "$basedir/../update-browserslist-db/cli.js" "$@"
fi

17
frontend/node_modules/.bin/update-browserslist-db.cmd generated vendored Normal file
View File

@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\update-browserslist-db\cli.js" %*

28
frontend/node_modules/.bin/update-browserslist-db.ps1 generated vendored Normal file
View File

@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../update-browserslist-db/cli.js" $args
} else {
& "$basedir/node$exe" "$basedir/../update-browserslist-db/cli.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../update-browserslist-db/cli.js" $args
} else {
& "node$exe" "$basedir/../update-browserslist-db/cli.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret

16
frontend/node_modules/.bin/vite generated vendored Normal file
View File

@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../vite/bin/vite.js" "$@"
else
exec node "$basedir/../vite/bin/vite.js" "$@"
fi

17
frontend/node_modules/.bin/vite.cmd generated vendored Normal file
View File

@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\vite\bin\vite.js" %*

28
frontend/node_modules/.bin/vite.ps1 generated vendored Normal file
View File

@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../vite/bin/vite.js" $args
} else {
& "$basedir/node$exe" "$basedir/../vite/bin/vite.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../vite/bin/vite.js" $args
} else {
& "node$exe" "$basedir/../vite/bin/vite.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret

1733
frontend/node_modules/.package-lock.json generated vendored Normal file

File diff suppressed because it is too large Load Diff

22
frontend/node_modules/@babel/code-frame/LICENSE generated vendored Normal file
View File

@ -0,0 +1,22 @@
MIT License
Copyright (c) 2014-present Sebastian McKenzie and other contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

19
frontend/node_modules/@babel/code-frame/README.md generated vendored Normal file
View File

@ -0,0 +1,19 @@
# @babel/code-frame
> Generate errors that contain a code frame that point to source locations.
See our website [@babel/code-frame](https://babeljs.io/docs/babel-code-frame) for more information.
## Install
Using npm:
```sh
npm install --save-dev @babel/code-frame
```
or using yarn:
```sh
yarn add @babel/code-frame --dev
```

217
frontend/node_modules/@babel/code-frame/lib/index.js generated vendored Normal file
View File

@ -0,0 +1,217 @@
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var picocolors = require('picocolors');
var jsTokens = require('js-tokens');
var helperValidatorIdentifier = require('@babel/helper-validator-identifier');
function isColorSupported() {
return (typeof process === "object" && (process.env.FORCE_COLOR === "0" || process.env.FORCE_COLOR === "false") ? false : picocolors.isColorSupported
);
}
const compose = (f, g) => v => f(g(v));
function buildDefs(colors) {
return {
keyword: colors.cyan,
capitalized: colors.yellow,
jsxIdentifier: colors.yellow,
punctuator: colors.yellow,
number: colors.magenta,
string: colors.green,
regex: colors.magenta,
comment: colors.gray,
invalid: compose(compose(colors.white, colors.bgRed), colors.bold),
gutter: colors.gray,
marker: compose(colors.red, colors.bold),
message: compose(colors.red, colors.bold),
reset: colors.reset
};
}
const defsOn = buildDefs(picocolors.createColors(true));
const defsOff = buildDefs(picocolors.createColors(false));
function getDefs(enabled) {
return enabled ? defsOn : defsOff;
}
const sometimesKeywords = new Set(["as", "async", "from", "get", "of", "set"]);
const NEWLINE$1 = /\r\n|[\n\r\u2028\u2029]/;
const BRACKET = /^[()[\]{}]$/;
let tokenize;
const JSX_TAG = /^[a-z][\w-]*$/i;
const getTokenType = function (token, offset, text) {
if (token.type === "name") {
const tokenValue = token.value;
if (helperValidatorIdentifier.isKeyword(tokenValue) || helperValidatorIdentifier.isStrictReservedWord(tokenValue, true) || sometimesKeywords.has(tokenValue)) {
return "keyword";
}
if (JSX_TAG.test(tokenValue) && (text[offset - 1] === "<" || text.slice(offset - 2, offset) === "</")) {
return "jsxIdentifier";
}
const firstChar = String.fromCodePoint(tokenValue.codePointAt(0));
if (firstChar !== firstChar.toLowerCase()) {
return "capitalized";
}
}
if (token.type === "punctuator" && BRACKET.test(token.value)) {
return "bracket";
}
if (token.type === "invalid" && (token.value === "@" || token.value === "#")) {
return "punctuator";
}
return token.type;
};
tokenize = function* (text) {
let match;
while (match = jsTokens.default.exec(text)) {
const token = jsTokens.matchToToken(match);
yield {
type: getTokenType(token, match.index, text),
value: token.value
};
}
};
function highlight(text) {
if (text === "") return "";
const defs = getDefs(true);
let highlighted = "";
for (const {
type,
value
} of tokenize(text)) {
if (type in defs) {
highlighted += value.split(NEWLINE$1).map(str => defs[type](str)).join("\n");
} else {
highlighted += value;
}
}
return highlighted;
}
let deprecationWarningShown = false;
const NEWLINE = /\r\n|[\n\r\u2028\u2029]/;
function getMarkerLines(loc, source, opts, startLineBaseZero) {
const startLoc = Object.assign({
column: 0,
line: -1
}, loc.start);
const endLoc = Object.assign({}, startLoc, loc.end);
const {
linesAbove = 2,
linesBelow = 3
} = opts || {};
const startLine = startLoc.line - startLineBaseZero;
const startColumn = startLoc.column;
const endLine = endLoc.line - startLineBaseZero;
const endColumn = endLoc.column;
let start = Math.max(startLine - (linesAbove + 1), 0);
let end = Math.min(source.length, endLine + linesBelow);
if (startLine === -1) {
start = 0;
}
if (endLine === -1) {
end = source.length;
}
const lineDiff = endLine - startLine;
const markerLines = {};
if (lineDiff) {
for (let i = 0; i <= lineDiff; i++) {
const lineNumber = i + startLine;
if (!startColumn) {
markerLines[lineNumber] = true;
} else if (i === 0) {
const sourceLength = source[lineNumber - 1].length;
markerLines[lineNumber] = [startColumn, sourceLength - startColumn + 1];
} else if (i === lineDiff) {
markerLines[lineNumber] = [0, endColumn];
} else {
const sourceLength = source[lineNumber - i].length;
markerLines[lineNumber] = [0, sourceLength];
}
}
} else {
if (startColumn === endColumn) {
if (startColumn) {
markerLines[startLine] = [startColumn, 0];
} else {
markerLines[startLine] = true;
}
} else {
markerLines[startLine] = [startColumn, endColumn - startColumn];
}
}
return {
start,
end,
markerLines
};
}
function codeFrameColumns(rawLines, loc, opts = {}) {
const shouldHighlight = opts.forceColor || isColorSupported() && opts.highlightCode;
const startLineBaseZero = (opts.startLine || 1) - 1;
const defs = getDefs(shouldHighlight);
const lines = rawLines.split(NEWLINE);
const {
start,
end,
markerLines
} = getMarkerLines(loc, lines, opts, startLineBaseZero);
const hasColumns = loc.start && typeof loc.start.column === "number";
const numberMaxWidth = String(end + startLineBaseZero).length;
const highlightedLines = shouldHighlight ? highlight(rawLines) : rawLines;
let frame = highlightedLines.split(NEWLINE, end).slice(start, end).map((line, index) => {
const number = start + 1 + index;
const paddedNumber = ` ${number + startLineBaseZero}`.slice(-numberMaxWidth);
const gutter = ` ${paddedNumber} |`;
const hasMarker = markerLines[number];
const lastMarkerLine = !markerLines[number + 1];
if (hasMarker) {
let markerLine = "";
if (Array.isArray(hasMarker)) {
const markerSpacing = line.slice(0, Math.max(hasMarker[0] - 1, 0)).replace(/[^\t]/g, " ");
const numberOfMarkers = hasMarker[1] || 1;
markerLine = ["\n ", defs.gutter(gutter.replace(/\d/g, " ")), " ", markerSpacing, defs.marker("^").repeat(numberOfMarkers)].join("");
if (lastMarkerLine && opts.message) {
markerLine += " " + defs.message(opts.message);
}
}
return [defs.marker(">"), defs.gutter(gutter), line.length > 0 ? ` ${line}` : "", markerLine].join("");
} else {
return ` ${defs.gutter(gutter)}${line.length > 0 ? ` ${line}` : ""}`;
}
}).join("\n");
if (opts.message && !hasColumns) {
frame = `${" ".repeat(numberMaxWidth + 1)}${opts.message}\n${frame}`;
}
if (shouldHighlight) {
return defs.reset(frame);
} else {
return frame;
}
}
function index (rawLines, lineNumber, colNumber, opts = {}) {
if (!deprecationWarningShown) {
deprecationWarningShown = true;
const message = "Passing lineNumber and colNumber is deprecated to @babel/code-frame. Please use `codeFrameColumns`.";
if (process.emitWarning) {
process.emitWarning(message, "DeprecationWarning");
} else {
const deprecationError = new Error(message);
deprecationError.name = "DeprecationWarning";
console.warn(new Error(message));
}
}
colNumber = Math.max(colNumber, 0);
const location = {
start: {
column: colNumber,
line: lineNumber
}
};
return codeFrameColumns(rawLines, location, opts);
}
exports.codeFrameColumns = codeFrameColumns;
exports.default = index;
exports.highlight = highlight;
//# sourceMappingURL=index.js.map

File diff suppressed because one or more lines are too long

32
frontend/node_modules/@babel/code-frame/package.json generated vendored Normal file
View File

@ -0,0 +1,32 @@
{
"name": "@babel/code-frame",
"version": "7.29.7",
"description": "Generate errors that contain a code frame that point to source locations.",
"author": "The Babel Team (https://babel.dev/team)",
"homepage": "https://babel.dev/docs/en/next/babel-code-frame",
"bugs": "https://github.com/babel/babel/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen",
"license": "MIT",
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "https://github.com/babel/babel.git",
"directory": "packages/babel-code-frame"
},
"main": "./lib/index.js",
"dependencies": {
"@babel/helper-validator-identifier": "^7.29.7",
"js-tokens": "^4.0.0",
"picocolors": "^1.1.1"
},
"devDependencies": {
"charcodes": "^0.2.0",
"import-meta-resolve": "^4.1.0",
"strip-ansi": "^4.0.0"
},
"engines": {
"node": ">=6.9.0"
},
"type": "commonjs"
}

22
frontend/node_modules/@babel/compat-data/LICENSE generated vendored Normal file
View File

@ -0,0 +1,22 @@
MIT License
Copyright (c) 2014-present Sebastian McKenzie and other contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

19
frontend/node_modules/@babel/compat-data/README.md generated vendored Normal file
View File

@ -0,0 +1,19 @@
# @babel/compat-data
> The compat-data to determine required Babel plugins
See our website [@babel/compat-data](https://babeljs.io/docs/babel-compat-data) for more information.
## Install
Using npm:
```sh
npm install --save @babel/compat-data
```
or using yarn:
```sh
yarn add @babel/compat-data
```

View File

@ -0,0 +1,2 @@
// Todo (Babel 8): remove this file as Babel 8 drop support of core-js 2
module.exports = require("./data/corejs2-built-ins.json");

View File

@ -0,0 +1,2 @@
// Todo (Babel 8): remove this file now that it is included in babel-plugin-polyfill-corejs3
module.exports = require("./data/corejs3-shipped-proposals.json");

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,5 @@
[
"esnext.promise.all-settled",
"esnext.string.match-all",
"esnext.global-this"
]

View File

@ -0,0 +1,18 @@
{
"es6.module": {
"chrome": "61",
"and_chr": "61",
"edge": "16",
"firefox": "60",
"and_ff": "60",
"node": "13.2.0",
"opera": "48",
"op_mob": "45",
"safari": "10.1",
"ios": "10.3",
"samsung": "8.2",
"android": "61",
"electron": "2.0",
"ios_saf": "10.3"
}
}

View File

@ -0,0 +1,38 @@
{
"transform-async-to-generator": [
"bugfix/transform-async-arrows-in-class"
],
"transform-parameters": [
"bugfix/transform-edge-default-parameters",
"bugfix/transform-safari-id-destructuring-collision-in-function-expression"
],
"transform-function-name": [
"bugfix/transform-edge-function-name"
],
"transform-block-scoping": [
"bugfix/transform-safari-block-shadowing",
"bugfix/transform-safari-for-shadowing"
],
"transform-destructuring": [
"bugfix/transform-safari-rest-destructuring-rhs-array"
],
"transform-template-literals": [
"bugfix/transform-tagged-template-caching"
],
"transform-optional-chaining": [
"bugfix/transform-v8-spread-parameters-in-optional-chaining"
],
"proposal-optional-chaining": [
"bugfix/transform-v8-spread-parameters-in-optional-chaining"
],
"transform-class-properties": [
"bugfix/transform-v8-static-class-fields-redefine-readonly",
"bugfix/transform-firefox-class-in-computed-class-key",
"bugfix/transform-safari-class-field-initializer-scope"
],
"proposal-class-properties": [
"bugfix/transform-v8-static-class-fields-redefine-readonly",
"bugfix/transform-firefox-class-in-computed-class-key",
"bugfix/transform-safari-class-field-initializer-scope"
]
}

View File

@ -0,0 +1,231 @@
{
"bugfix/transform-async-arrows-in-class": {
"chrome": "55",
"opera": "42",
"edge": "15",
"firefox": "52",
"safari": "11",
"node": "7.6",
"deno": "1",
"ios": "11",
"samsung": "6",
"opera_mobile": "42",
"electron": "1.6"
},
"bugfix/transform-edge-default-parameters": {
"chrome": "49",
"opera": "36",
"edge": "18",
"firefox": "52",
"safari": "10",
"node": "6",
"deno": "1",
"ios": "10",
"samsung": "5",
"opera_mobile": "36",
"electron": "0.37"
},
"bugfix/transform-edge-function-name": {
"chrome": "51",
"opera": "38",
"edge": "79",
"firefox": "53",
"safari": "10",
"node": "6.5",
"deno": "1",
"ios": "10",
"samsung": "5",
"rhino": "1.9",
"opera_mobile": "41",
"electron": "1.2"
},
"bugfix/transform-safari-block-shadowing": {
"chrome": "49",
"opera": "36",
"edge": "12",
"firefox": "44",
"safari": "11",
"node": "6",
"deno": "1",
"ie": "11",
"ios": "11",
"samsung": "5",
"opera_mobile": "36",
"electron": "0.37"
},
"bugfix/transform-safari-for-shadowing": {
"chrome": "49",
"opera": "36",
"edge": "12",
"firefox": "4",
"safari": "11",
"node": "6",
"deno": "1",
"ie": "11",
"ios": "11",
"samsung": "5",
"rhino": "1.7.13",
"opera_mobile": "36",
"electron": "0.37"
},
"bugfix/transform-safari-id-destructuring-collision-in-function-expression": {
"chrome": "49",
"opera": "36",
"edge": "14",
"firefox": "2",
"safari": "16.3",
"node": "6",
"deno": "1",
"ios": "16.3",
"samsung": "5",
"opera_mobile": "36",
"electron": "0.37"
},
"bugfix/transform-safari-rest-destructuring-rhs-array": {
"chrome": "49",
"opera": "36",
"edge": "14",
"firefox": "34",
"safari": "14.1",
"node": "6",
"deno": "1",
"ios": "14.5",
"samsung": "5",
"opera_mobile": "36",
"electron": "0.37"
},
"bugfix/transform-tagged-template-caching": {
"chrome": "41",
"opera": "28",
"edge": "12",
"firefox": "34",
"safari": "13",
"node": "4",
"deno": "1",
"ios": "13",
"samsung": "3.4",
"rhino": "1.7.14",
"opera_mobile": "28",
"electron": "0.21"
},
"bugfix/transform-v8-spread-parameters-in-optional-chaining": {
"chrome": "91",
"opera": "77",
"edge": "91",
"firefox": "74",
"safari": "13.1",
"node": "16.9",
"deno": "1.9",
"ios": "13.4",
"samsung": "16",
"opera_mobile": "64",
"electron": "13.0"
},
"transform-optional-chaining": {
"chrome": "80",
"opera": "67",
"edge": "80",
"firefox": "74",
"safari": "13.1",
"node": "14",
"deno": "1",
"ios": "13.4",
"samsung": "13",
"rhino": "1.8",
"opera_mobile": "57",
"electron": "8.0"
},
"proposal-optional-chaining": {
"chrome": "80",
"opera": "67",
"edge": "80",
"firefox": "74",
"safari": "13.1",
"node": "14",
"deno": "1",
"ios": "13.4",
"samsung": "13",
"rhino": "1.8",
"opera_mobile": "57",
"electron": "8.0"
},
"transform-parameters": {
"chrome": "49",
"opera": "36",
"edge": "15",
"firefox": "52",
"safari": "10",
"node": "6",
"deno": "1",
"ios": "10",
"samsung": "5",
"opera_mobile": "36",
"electron": "0.37"
},
"transform-async-to-generator": {
"chrome": "55",
"opera": "42",
"edge": "15",
"firefox": "52",
"safari": "10.1",
"node": "7.6",
"deno": "1",
"ios": "10.3",
"samsung": "6",
"opera_mobile": "42",
"electron": "1.6"
},
"transform-template-literals": {
"chrome": "41",
"opera": "28",
"edge": "13",
"firefox": "34",
"safari": "9",
"node": "4",
"deno": "1",
"ios": "9",
"samsung": "3.4",
"rhino": "1.9",
"opera_mobile": "28",
"electron": "0.21"
},
"transform-function-name": {
"chrome": "51",
"opera": "38",
"edge": "14",
"firefox": "53",
"safari": "10",
"node": "6.5",
"deno": "1",
"ios": "10",
"samsung": "5",
"opera_mobile": "41",
"electron": "1.2"
},
"transform-destructuring": {
"chrome": "51",
"opera": "38",
"edge": "15",
"firefox": "53",
"safari": "10",
"node": "6.5",
"deno": "1",
"ios": "10",
"samsung": "5",
"opera_mobile": "41",
"electron": "1.2"
},
"transform-block-scoping": {
"chrome": "50",
"opera": "37",
"edge": "14",
"firefox": "53",
"safari": "10",
"node": "6",
"deno": "1",
"ios": "10",
"samsung": "5",
"opera_mobile": "37",
"electron": "1.1"
}
}

View File

@ -0,0 +1,843 @@
{
"transform-explicit-resource-management": {
"chrome": "141",
"edge": "141",
"firefox": "141",
"node": "25",
"electron": "39.0"
},
"transform-duplicate-named-capturing-groups-regex": {
"chrome": "126",
"opera": "112",
"edge": "126",
"firefox": "129",
"safari": "17.4",
"node": "23",
"ios": "17.4",
"rhino": "1.9",
"electron": "31.0"
},
"transform-regexp-modifiers": {
"chrome": "125",
"opera": "111",
"edge": "125",
"firefox": "132",
"node": "23",
"samsung": "27",
"electron": "31.0"
},
"transform-unicode-sets-regex": {
"chrome": "112",
"opera": "98",
"edge": "112",
"firefox": "116",
"safari": "17",
"node": "20",
"deno": "1.32",
"ios": "17",
"samsung": "23",
"opera_mobile": "75",
"electron": "24.0"
},
"bugfix/transform-v8-static-class-fields-redefine-readonly": {
"chrome": "98",
"opera": "84",
"edge": "98",
"firefox": "75",
"safari": "15",
"node": "12",
"deno": "1.18",
"ios": "15",
"samsung": "11",
"opera_mobile": "52",
"electron": "17.0"
},
"bugfix/transform-firefox-class-in-computed-class-key": {
"chrome": "74",
"opera": "62",
"edge": "79",
"firefox": "126",
"safari": "16",
"node": "12",
"deno": "1",
"ios": "16",
"samsung": "11",
"opera_mobile": "53",
"electron": "6.0"
},
"bugfix/transform-safari-class-field-initializer-scope": {
"chrome": "74",
"opera": "62",
"edge": "79",
"firefox": "69",
"safari": "16",
"node": "12",
"deno": "1",
"ios": "16",
"samsung": "11",
"opera_mobile": "53",
"electron": "6.0"
},
"transform-class-static-block": {
"chrome": "94",
"opera": "80",
"edge": "94",
"firefox": "93",
"safari": "16.4",
"node": "16.11",
"deno": "1.14",
"ios": "16.4",
"samsung": "17",
"opera_mobile": "66",
"electron": "15.0"
},
"proposal-class-static-block": {
"chrome": "94",
"opera": "80",
"edge": "94",
"firefox": "93",
"safari": "16.4",
"node": "16.11",
"deno": "1.14",
"ios": "16.4",
"samsung": "17",
"opera_mobile": "66",
"electron": "15.0"
},
"transform-private-property-in-object": {
"chrome": "91",
"opera": "77",
"edge": "91",
"firefox": "90",
"safari": "15",
"node": "16.9",
"deno": "1.9",
"ios": "15",
"samsung": "16",
"opera_mobile": "64",
"electron": "13.0"
},
"proposal-private-property-in-object": {
"chrome": "91",
"opera": "77",
"edge": "91",
"firefox": "90",
"safari": "15",
"node": "16.9",
"deno": "1.9",
"ios": "15",
"samsung": "16",
"opera_mobile": "64",
"electron": "13.0"
},
"transform-class-properties": {
"chrome": "74",
"opera": "62",
"edge": "79",
"firefox": "90",
"safari": "14.1",
"node": "12",
"deno": "1",
"ios": "14.5",
"samsung": "11",
"opera_mobile": "53",
"electron": "6.0"
},
"proposal-class-properties": {
"chrome": "74",
"opera": "62",
"edge": "79",
"firefox": "90",
"safari": "14.1",
"node": "12",
"deno": "1",
"ios": "14.5",
"samsung": "11",
"opera_mobile": "53",
"electron": "6.0"
},
"transform-private-methods": {
"chrome": "84",
"opera": "70",
"edge": "84",
"firefox": "90",
"safari": "15",
"node": "14.6",
"deno": "1",
"ios": "15",
"samsung": "14",
"opera_mobile": "60",
"electron": "10.0"
},
"proposal-private-methods": {
"chrome": "84",
"opera": "70",
"edge": "84",
"firefox": "90",
"safari": "15",
"node": "14.6",
"deno": "1",
"ios": "15",
"samsung": "14",
"opera_mobile": "60",
"electron": "10.0"
},
"transform-numeric-separator": {
"chrome": "75",
"opera": "62",
"edge": "79",
"firefox": "70",
"safari": "13",
"node": "12.5",
"deno": "1",
"ios": "13",
"samsung": "11",
"rhino": "1.7.14",
"opera_mobile": "54",
"electron": "6.0"
},
"proposal-numeric-separator": {
"chrome": "75",
"opera": "62",
"edge": "79",
"firefox": "70",
"safari": "13",
"node": "12.5",
"deno": "1",
"ios": "13",
"samsung": "11",
"rhino": "1.7.14",
"opera_mobile": "54",
"electron": "6.0"
},
"transform-logical-assignment-operators": {
"chrome": "85",
"opera": "71",
"edge": "85",
"firefox": "79",
"safari": "14",
"node": "15",
"deno": "1.2",
"ios": "14",
"samsung": "14",
"opera_mobile": "60",
"electron": "10.0"
},
"proposal-logical-assignment-operators": {
"chrome": "85",
"opera": "71",
"edge": "85",
"firefox": "79",
"safari": "14",
"node": "15",
"deno": "1.2",
"ios": "14",
"samsung": "14",
"opera_mobile": "60",
"electron": "10.0"
},
"transform-nullish-coalescing-operator": {
"chrome": "80",
"opera": "67",
"edge": "80",
"firefox": "72",
"safari": "13.1",
"node": "14",
"deno": "1",
"ios": "13.4",
"samsung": "13",
"rhino": "1.8",
"opera_mobile": "57",
"electron": "8.0"
},
"proposal-nullish-coalescing-operator": {
"chrome": "80",
"opera": "67",
"edge": "80",
"firefox": "72",
"safari": "13.1",
"node": "14",
"deno": "1",
"ios": "13.4",
"samsung": "13",
"rhino": "1.8",
"opera_mobile": "57",
"electron": "8.0"
},
"transform-optional-chaining": {
"chrome": "91",
"opera": "77",
"edge": "91",
"firefox": "74",
"safari": "13.1",
"node": "16.9",
"deno": "1.9",
"ios": "13.4",
"samsung": "16",
"opera_mobile": "64",
"electron": "13.0"
},
"proposal-optional-chaining": {
"chrome": "91",
"opera": "77",
"edge": "91",
"firefox": "74",
"safari": "13.1",
"node": "16.9",
"deno": "1.9",
"ios": "13.4",
"samsung": "16",
"opera_mobile": "64",
"electron": "13.0"
},
"transform-json-strings": {
"chrome": "66",
"opera": "53",
"edge": "79",
"firefox": "62",
"safari": "12",
"node": "10",
"deno": "1",
"ios": "12",
"samsung": "9",
"rhino": "1.7.14",
"opera_mobile": "47",
"electron": "3.0"
},
"proposal-json-strings": {
"chrome": "66",
"opera": "53",
"edge": "79",
"firefox": "62",
"safari": "12",
"node": "10",
"deno": "1",
"ios": "12",
"samsung": "9",
"rhino": "1.7.14",
"opera_mobile": "47",
"electron": "3.0"
},
"transform-optional-catch-binding": {
"chrome": "66",
"opera": "53",
"edge": "79",
"firefox": "58",
"safari": "11.1",
"node": "10",
"deno": "1",
"ios": "11.3",
"samsung": "9",
"opera_mobile": "47",
"electron": "3.0"
},
"proposal-optional-catch-binding": {
"chrome": "66",
"opera": "53",
"edge": "79",
"firefox": "58",
"safari": "11.1",
"node": "10",
"deno": "1",
"ios": "11.3",
"samsung": "9",
"opera_mobile": "47",
"electron": "3.0"
},
"transform-parameters": {
"chrome": "49",
"opera": "36",
"edge": "18",
"firefox": "52",
"safari": "16.3",
"node": "6",
"deno": "1",
"ios": "16.3",
"samsung": "5",
"opera_mobile": "36",
"electron": "0.37"
},
"transform-async-generator-functions": {
"chrome": "63",
"opera": "50",
"edge": "79",
"firefox": "57",
"safari": "12",
"node": "10",
"deno": "1",
"ios": "12",
"samsung": "8",
"opera_mobile": "46",
"electron": "3.0"
},
"proposal-async-generator-functions": {
"chrome": "63",
"opera": "50",
"edge": "79",
"firefox": "57",
"safari": "12",
"node": "10",
"deno": "1",
"ios": "12",
"samsung": "8",
"opera_mobile": "46",
"electron": "3.0"
},
"transform-object-rest-spread": {
"chrome": "60",
"opera": "47",
"edge": "79",
"firefox": "55",
"safari": "11.1",
"node": "8.3",
"deno": "1",
"ios": "11.3",
"samsung": "8",
"opera_mobile": "44",
"electron": "2.0"
},
"proposal-object-rest-spread": {
"chrome": "60",
"opera": "47",
"edge": "79",
"firefox": "55",
"safari": "11.1",
"node": "8.3",
"deno": "1",
"ios": "11.3",
"samsung": "8",
"opera_mobile": "44",
"electron": "2.0"
},
"transform-dotall-regex": {
"chrome": "62",
"opera": "49",
"edge": "79",
"firefox": "78",
"safari": "11.1",
"node": "8.10",
"deno": "1",
"ios": "11.3",
"samsung": "8",
"rhino": "1.7.15",
"opera_mobile": "46",
"electron": "3.0"
},
"transform-unicode-property-regex": {
"chrome": "64",
"opera": "51",
"edge": "79",
"firefox": "78",
"safari": "11.1",
"node": "10",
"deno": "1",
"ios": "11.3",
"samsung": "9",
"rhino": "1.9",
"opera_mobile": "47",
"electron": "3.0"
},
"proposal-unicode-property-regex": {
"chrome": "64",
"opera": "51",
"edge": "79",
"firefox": "78",
"safari": "11.1",
"node": "10",
"deno": "1",
"ios": "11.3",
"samsung": "9",
"rhino": "1.9",
"opera_mobile": "47",
"electron": "3.0"
},
"transform-named-capturing-groups-regex": {
"chrome": "64",
"opera": "51",
"edge": "79",
"firefox": "78",
"safari": "11.1",
"node": "10",
"deno": "1",
"ios": "11.3",
"samsung": "9",
"rhino": "1.9",
"opera_mobile": "47",
"electron": "3.0"
},
"transform-async-to-generator": {
"chrome": "55",
"opera": "42",
"edge": "15",
"firefox": "52",
"safari": "11",
"node": "7.6",
"deno": "1",
"ios": "11",
"samsung": "6",
"opera_mobile": "42",
"electron": "1.6"
},
"transform-exponentiation-operator": {
"chrome": "52",
"opera": "39",
"edge": "14",
"firefox": "52",
"safari": "10.1",
"node": "7",
"deno": "1",
"ios": "10.3",
"samsung": "6",
"rhino": "1.7.14",
"opera_mobile": "41",
"electron": "1.3"
},
"transform-template-literals": {
"chrome": "41",
"opera": "28",
"edge": "13",
"firefox": "34",
"safari": "13",
"node": "4",
"deno": "1",
"ios": "13",
"samsung": "3.4",
"rhino": "1.9",
"opera_mobile": "28",
"electron": "0.21"
},
"transform-literals": {
"chrome": "44",
"opera": "31",
"edge": "12",
"firefox": "53",
"safari": "9",
"node": "4",
"deno": "1",
"ios": "9",
"samsung": "4",
"rhino": "1.7.15",
"opera_mobile": "32",
"electron": "0.30"
},
"transform-function-name": {
"chrome": "51",
"opera": "38",
"edge": "79",
"firefox": "53",
"safari": "10",
"node": "6.5",
"deno": "1",
"ios": "10",
"samsung": "5",
"opera_mobile": "41",
"electron": "1.2"
},
"transform-arrow-functions": {
"chrome": "47",
"opera": "34",
"edge": "13",
"firefox": "43",
"safari": "10",
"node": "6",
"deno": "1",
"ios": "10",
"samsung": "5",
"rhino": "1.7.13",
"opera_mobile": "34",
"electron": "0.36"
},
"transform-block-scoped-functions": {
"chrome": "41",
"opera": "28",
"edge": "12",
"firefox": "46",
"safari": "10",
"node": "4",
"deno": "1",
"ie": "11",
"ios": "10",
"samsung": "3.4",
"opera_mobile": "28",
"electron": "0.21"
},
"transform-classes": {
"chrome": "46",
"opera": "33",
"edge": "13",
"firefox": "45",
"safari": "10",
"node": "5",
"deno": "1",
"ios": "10",
"samsung": "5",
"opera_mobile": "33",
"electron": "0.36"
},
"transform-object-super": {
"chrome": "46",
"opera": "33",
"edge": "13",
"firefox": "45",
"safari": "10",
"node": "5",
"deno": "1",
"ios": "10",
"samsung": "5",
"opera_mobile": "33",
"electron": "0.36"
},
"transform-shorthand-properties": {
"chrome": "43",
"opera": "30",
"edge": "12",
"firefox": "33",
"safari": "9",
"node": "4",
"deno": "1",
"ios": "9",
"samsung": "4",
"rhino": "1.7.14",
"opera_mobile": "30",
"electron": "0.27"
},
"transform-duplicate-keys": {
"chrome": "42",
"opera": "29",
"edge": "12",
"firefox": "34",
"safari": "9",
"node": "4",
"deno": "1",
"ios": "9",
"samsung": "3.4",
"opera_mobile": "29",
"electron": "0.25"
},
"transform-computed-properties": {
"chrome": "44",
"opera": "31",
"edge": "12",
"firefox": "34",
"safari": "7.1",
"node": "4",
"deno": "1",
"ios": "8",
"samsung": "4",
"rhino": "1.8",
"opera_mobile": "32",
"electron": "0.30"
},
"transform-for-of": {
"chrome": "51",
"opera": "38",
"edge": "15",
"firefox": "53",
"safari": "10",
"node": "6.5",
"deno": "1",
"ios": "10",
"samsung": "5",
"opera_mobile": "41",
"electron": "1.2"
},
"transform-sticky-regex": {
"chrome": "49",
"opera": "36",
"edge": "13",
"firefox": "3",
"safari": "10",
"node": "6",
"deno": "1",
"ios": "10",
"samsung": "5",
"rhino": "1.7.15",
"opera_mobile": "36",
"electron": "0.37"
},
"transform-unicode-escapes": {
"chrome": "44",
"opera": "31",
"edge": "12",
"firefox": "53",
"safari": "9",
"node": "4",
"deno": "1",
"ios": "9",
"samsung": "4",
"rhino": "1.7.15",
"opera_mobile": "32",
"electron": "0.30"
},
"transform-unicode-regex": {
"chrome": "50",
"opera": "37",
"edge": "13",
"firefox": "46",
"safari": "12",
"node": "6",
"deno": "1",
"ios": "12",
"samsung": "5",
"opera_mobile": "37",
"electron": "1.1"
},
"transform-spread": {
"chrome": "46",
"opera": "33",
"edge": "13",
"firefox": "45",
"safari": "10",
"node": "5",
"deno": "1",
"ios": "10",
"samsung": "5",
"opera_mobile": "33",
"electron": "0.36"
},
"transform-destructuring": {
"chrome": "51",
"opera": "38",
"edge": "15",
"firefox": "53",
"safari": "14.1",
"node": "6.5",
"deno": "1",
"ios": "14.5",
"samsung": "5",
"opera_mobile": "41",
"electron": "1.2"
},
"transform-block-scoping": {
"chrome": "50",
"opera": "37",
"edge": "14",
"firefox": "53",
"safari": "11",
"node": "6",
"deno": "1",
"ios": "11",
"samsung": "5",
"opera_mobile": "37",
"electron": "1.1"
},
"transform-typeof-symbol": {
"chrome": "48",
"opera": "35",
"edge": "12",
"firefox": "36",
"safari": "9",
"node": "6",
"deno": "1",
"ios": "9",
"samsung": "5",
"rhino": "1.8",
"opera_mobile": "35",
"electron": "0.37"
},
"transform-new-target": {
"chrome": "46",
"opera": "33",
"edge": "14",
"firefox": "41",
"safari": "10",
"node": "5",
"deno": "1",
"ios": "10",
"samsung": "5",
"opera_mobile": "33",
"electron": "0.36"
},
"transform-regenerator": {
"chrome": "50",
"opera": "37",
"edge": "13",
"firefox": "53",
"safari": "10",
"node": "6",
"deno": "1",
"ios": "10",
"samsung": "5",
"opera_mobile": "37",
"electron": "1.1"
},
"transform-member-expression-literals": {
"chrome": "7",
"opera": "12",
"edge": "12",
"firefox": "2",
"safari": "5.1",
"node": "0.4",
"deno": "1",
"ie": "9",
"android": "4",
"ios": "6",
"phantom": "1.9",
"samsung": "1",
"rhino": "1.7.13",
"opera_mobile": "12",
"electron": "0.20"
},
"transform-property-literals": {
"chrome": "7",
"opera": "12",
"edge": "12",
"firefox": "2",
"safari": "5.1",
"node": "0.4",
"deno": "1",
"ie": "9",
"android": "4",
"ios": "6",
"phantom": "1.9",
"samsung": "1",
"rhino": "1.7.13",
"opera_mobile": "12",
"electron": "0.20"
},
"transform-reserved-words": {
"chrome": "13",
"opera": "10.50",
"edge": "12",
"firefox": "2",
"safari": "3.1",
"node": "0.6",
"deno": "1",
"ie": "9",
"android": "4.4",
"ios": "6",
"phantom": "1.9",
"samsung": "1",
"rhino": "1.7.13",
"opera_mobile": "10.1",
"electron": "0.20"
},
"transform-export-namespace-from": {
"chrome": "72",
"deno": "1.0",
"edge": "79",
"firefox": "80",
"node": "13.2.0",
"opera": "60",
"opera_mobile": "51",
"safari": "14.1",
"ios": "14.5",
"samsung": "11.0",
"android": "72",
"electron": "5.0"
},
"proposal-export-namespace-from": {
"chrome": "72",
"deno": "1.0",
"edge": "79",
"firefox": "80",
"node": "13.2.0",
"opera": "60",
"opera_mobile": "51",
"safari": "14.1",
"ios": "14.5",
"samsung": "11.0",
"android": "72",
"electron": "5.0"
}
}

View File

@ -0,0 +1,2 @@
// Todo (Babel 8): remove this file, in Babel 8 users import the .json directly
module.exports = require("./data/native-modules.json");

View File

@ -0,0 +1,2 @@
// Todo (Babel 8): remove this file, in Babel 8 users import the .json directly
module.exports = require("./data/overlapping-plugins.json");

40
frontend/node_modules/@babel/compat-data/package.json generated vendored Normal file
View File

@ -0,0 +1,40 @@
{
"name": "@babel/compat-data",
"version": "7.29.7",
"author": "The Babel Team (https://babel.dev/team)",
"license": "MIT",
"description": "The compat-data to determine required Babel plugins",
"repository": {
"type": "git",
"url": "https://github.com/babel/babel.git",
"directory": "packages/babel-compat-data"
},
"publishConfig": {
"access": "public"
},
"exports": {
"./plugins": "./plugins.js",
"./native-modules": "./native-modules.js",
"./corejs2-built-ins": "./corejs2-built-ins.js",
"./corejs3-shipped-proposals": "./corejs3-shipped-proposals.js",
"./overlapping-plugins": "./overlapping-plugins.js",
"./plugin-bugfixes": "./plugin-bugfixes.js"
},
"scripts": {
"build-data": "./scripts/download-compat-table.sh && node ./scripts/build-data.mjs && node ./scripts/build-modules-support.mjs && node ./scripts/build-bugfixes-targets.mjs"
},
"keywords": [
"babel",
"compat-table",
"compat-data"
],
"devDependencies": {
"@mdn/browser-compat-data": "^6.0.8",
"core-js-compat": "^3.48.0",
"electron-to-chromium": "^1.5.278"
},
"engines": {
"node": ">=6.9.0"
},
"type": "commonjs"
}

View File

@ -0,0 +1,2 @@
// Todo (Babel 8): remove this file, in Babel 8 users import the .json directly
module.exports = require("./data/plugin-bugfixes.json");

2
frontend/node_modules/@babel/compat-data/plugins.js generated vendored Normal file
View File

@ -0,0 +1,2 @@
// Todo (Babel 8): remove this file, in Babel 8 users import the .json directly
module.exports = require("./data/plugins.json");

22
frontend/node_modules/@babel/core/LICENSE generated vendored Normal file
View File

@ -0,0 +1,22 @@
MIT License
Copyright (c) 2014-present Sebastian McKenzie and other contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

19
frontend/node_modules/@babel/core/README.md generated vendored Normal file
View File

@ -0,0 +1,19 @@
# @babel/core
> Babel compiler core.
See our website [@babel/core](https://babeljs.io/docs/babel-core) for more information or the [issues](https://github.com/babel/babel/issues?utf8=%E2%9C%93&q=is%3Aissue+label%3A%22pkg%3A%20core%22+is%3Aopen) associated with this package.
## Install
Using npm:
```sh
npm install --save-dev @babel/core
```
or using yarn:
```sh
yarn add @babel/core --dev
```

View File

@ -0,0 +1,5 @@
"use strict";
0 && 0;
//# sourceMappingURL=cache-contexts.js.map

View File

@ -0,0 +1 @@
{"version":3,"names":[],"sources":["../../src/config/cache-contexts.ts"],"sourcesContent":["import type { ConfigContext } from \"./config-chain.ts\";\nimport type {\n CallerMetadata,\n TargetsListOrObject,\n} from \"./validation/options.ts\";\n\nexport type { ConfigContext as FullConfig };\n\nexport type FullPreset = {\n targets: TargetsListOrObject;\n} & ConfigContext;\nexport type FullPlugin = {\n assumptions: Record<string, boolean>;\n} & FullPreset;\n\n// Context not including filename since it is used in places that cannot\n// process 'ignore'/'only' and other filename-based logic.\nexport type SimpleConfig = {\n envName: string;\n caller: CallerMetadata | undefined;\n};\nexport type SimplePreset = {\n targets: TargetsListOrObject;\n} & SimpleConfig;\nexport type SimplePlugin = {\n assumptions: Record<string, boolean>;\n} & SimplePreset;\n"],"mappings":"","ignoreList":[]}

261
frontend/node_modules/@babel/core/lib/config/caching.js generated vendored Normal file
View File

@ -0,0 +1,261 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.assertSimpleType = assertSimpleType;
exports.makeStrongCache = makeStrongCache;
exports.makeStrongCacheSync = makeStrongCacheSync;
exports.makeWeakCache = makeWeakCache;
exports.makeWeakCacheSync = makeWeakCacheSync;
function _gensync() {
const data = require("gensync");
_gensync = function () {
return data;
};
return data;
}
var _async = require("../gensync-utils/async.js");
var _util = require("./util.js");
const synchronize = gen => {
return _gensync()(gen).sync;
};
function* genTrue() {
return true;
}
function makeWeakCache(handler) {
return makeCachedFunction(WeakMap, handler);
}
function makeWeakCacheSync(handler) {
return synchronize(makeWeakCache(handler));
}
function makeStrongCache(handler) {
return makeCachedFunction(Map, handler);
}
function makeStrongCacheSync(handler) {
return synchronize(makeStrongCache(handler));
}
function makeCachedFunction(CallCache, handler) {
const callCacheSync = new CallCache();
const callCacheAsync = new CallCache();
const futureCache = new CallCache();
return function* cachedFunction(arg, data) {
const asyncContext = yield* (0, _async.isAsync)();
const callCache = asyncContext ? callCacheAsync : callCacheSync;
const cached = yield* getCachedValueOrWait(asyncContext, callCache, futureCache, arg, data);
if (cached.valid) return cached.value;
const cache = new CacheConfigurator(data);
const handlerResult = handler(arg, cache);
let finishLock;
let value;
if ((0, _util.isIterableIterator)(handlerResult)) {
value = yield* (0, _async.onFirstPause)(handlerResult, () => {
finishLock = setupAsyncLocks(cache, futureCache, arg);
});
} else {
value = handlerResult;
}
updateFunctionCache(callCache, cache, arg, value);
if (finishLock) {
futureCache.delete(arg);
finishLock.release(value);
}
return value;
};
}
function* getCachedValue(cache, arg, data) {
const cachedValue = cache.get(arg);
if (cachedValue) {
for (const {
value,
valid
} of cachedValue) {
if (yield* valid(data)) return {
valid: true,
value
};
}
}
return {
valid: false,
value: null
};
}
function* getCachedValueOrWait(asyncContext, callCache, futureCache, arg, data) {
const cached = yield* getCachedValue(callCache, arg, data);
if (cached.valid) {
return cached;
}
if (asyncContext) {
const cached = yield* getCachedValue(futureCache, arg, data);
if (cached.valid) {
const value = yield* (0, _async.waitFor)(cached.value.promise);
return {
valid: true,
value
};
}
}
return {
valid: false,
value: null
};
}
function setupAsyncLocks(config, futureCache, arg) {
const finishLock = new Lock();
updateFunctionCache(futureCache, config, arg, finishLock);
return finishLock;
}
function updateFunctionCache(cache, config, arg, value) {
if (!config.configured()) config.forever();
let cachedValue = cache.get(arg);
config.deactivate();
switch (config.mode()) {
case "forever":
cachedValue = [{
value,
valid: genTrue
}];
cache.set(arg, cachedValue);
break;
case "invalidate":
cachedValue = [{
value,
valid: config.validator()
}];
cache.set(arg, cachedValue);
break;
case "valid":
if (cachedValue) {
cachedValue.push({
value,
valid: config.validator()
});
} else {
cachedValue = [{
value,
valid: config.validator()
}];
cache.set(arg, cachedValue);
}
}
}
class CacheConfigurator {
constructor(data) {
this._active = true;
this._never = false;
this._forever = false;
this._invalidate = false;
this._configured = false;
this._pairs = [];
this._data = void 0;
this._data = data;
}
simple() {
return makeSimpleConfigurator(this);
}
mode() {
if (this._never) return "never";
if (this._forever) return "forever";
if (this._invalidate) return "invalidate";
return "valid";
}
forever() {
if (!this._active) {
throw new Error("Cannot change caching after evaluation has completed.");
}
if (this._never) {
throw new Error("Caching has already been configured with .never()");
}
this._forever = true;
this._configured = true;
}
never() {
if (!this._active) {
throw new Error("Cannot change caching after evaluation has completed.");
}
if (this._forever) {
throw new Error("Caching has already been configured with .forever()");
}
this._never = true;
this._configured = true;
}
using(handler) {
if (!this._active) {
throw new Error("Cannot change caching after evaluation has completed.");
}
if (this._never || this._forever) {
throw new Error("Caching has already been configured with .never or .forever()");
}
this._configured = true;
const key = handler(this._data);
const fn = (0, _async.maybeAsync)(handler, `You appear to be using an async cache handler, but Babel has been called synchronously`);
if ((0, _async.isThenable)(key)) {
return key.then(key => {
this._pairs.push([key, fn]);
return key;
});
}
this._pairs.push([key, fn]);
return key;
}
invalidate(handler) {
this._invalidate = true;
return this.using(handler);
}
validator() {
const pairs = this._pairs;
return function* (data) {
for (const [key, fn] of pairs) {
if (key !== (yield* fn(data))) return false;
}
return true;
};
}
deactivate() {
this._active = false;
}
configured() {
return this._configured;
}
}
function makeSimpleConfigurator(cache) {
function cacheFn(val) {
if (typeof val === "boolean") {
if (val) cache.forever();else cache.never();
return;
}
return cache.using(() => assertSimpleType(val()));
}
cacheFn.forever = () => cache.forever();
cacheFn.never = () => cache.never();
cacheFn.using = cb => cache.using(() => assertSimpleType(cb()));
cacheFn.invalidate = cb => cache.invalidate(() => assertSimpleType(cb()));
return cacheFn;
}
function assertSimpleType(value) {
if ((0, _async.isThenable)(value)) {
throw new Error(`You appear to be using an async cache handler, ` + `which your current version of Babel does not support. ` + `We may add support for this in the future, ` + `but if you're on the most recent version of @babel/core and still ` + `seeing this error, then you'll need to synchronously handle your caching logic.`);
}
if (value != null && typeof value !== "string" && typeof value !== "boolean" && typeof value !== "number") {
throw new Error("Cache keys must be either string, boolean, number, null, or undefined.");
}
return value;
}
class Lock {
constructor() {
this.released = false;
this.promise = void 0;
this._resolve = void 0;
this.promise = new Promise(resolve => {
this._resolve = resolve;
});
}
release(value) {
this.released = true;
this._resolve(value);
}
}
0 && 0;
//# sourceMappingURL=caching.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,469 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.buildPresetChain = buildPresetChain;
exports.buildPresetChainWalker = void 0;
exports.buildRootChain = buildRootChain;
function _path() {
const data = require("path");
_path = function () {
return data;
};
return data;
}
function _debug() {
const data = require("debug");
_debug = function () {
return data;
};
return data;
}
var _options = require("./validation/options.js");
var _patternToRegex = require("./pattern-to-regex.js");
var _printer = require("./printer.js");
var _rewriteStackTrace = require("../errors/rewrite-stack-trace.js");
var _configError = require("../errors/config-error.js");
var _index = require("./files/index.js");
var _caching = require("./caching.js");
var _configDescriptors = require("./config-descriptors.js");
const debug = _debug()("babel:config:config-chain");
function* buildPresetChain(arg, context) {
const chain = yield* buildPresetChainWalker(arg, context);
if (!chain) return null;
return {
plugins: dedupDescriptors(chain.plugins),
presets: dedupDescriptors(chain.presets),
options: chain.options.map(o => createConfigChainOptions(o)),
files: new Set()
};
}
const buildPresetChainWalker = exports.buildPresetChainWalker = makeChainWalker({
root: preset => loadPresetDescriptors(preset),
env: (preset, envName) => loadPresetEnvDescriptors(preset)(envName),
overrides: (preset, index) => loadPresetOverridesDescriptors(preset)(index),
overridesEnv: (preset, index, envName) => loadPresetOverridesEnvDescriptors(preset)(index)(envName),
createLogger: () => () => {}
});
const loadPresetDescriptors = (0, _caching.makeWeakCacheSync)(preset => buildRootDescriptors(preset, preset.alias, _configDescriptors.createUncachedDescriptors));
const loadPresetEnvDescriptors = (0, _caching.makeWeakCacheSync)(preset => (0, _caching.makeStrongCacheSync)(envName => buildEnvDescriptors(preset, preset.alias, _configDescriptors.createUncachedDescriptors, envName)));
const loadPresetOverridesDescriptors = (0, _caching.makeWeakCacheSync)(preset => (0, _caching.makeStrongCacheSync)(index => buildOverrideDescriptors(preset, preset.alias, _configDescriptors.createUncachedDescriptors, index)));
const loadPresetOverridesEnvDescriptors = (0, _caching.makeWeakCacheSync)(preset => (0, _caching.makeStrongCacheSync)(index => (0, _caching.makeStrongCacheSync)(envName => buildOverrideEnvDescriptors(preset, preset.alias, _configDescriptors.createUncachedDescriptors, index, envName))));
function* buildRootChain(opts, context) {
let configReport, babelRcReport;
const programmaticLogger = new _printer.ConfigPrinter();
const programmaticChain = yield* loadProgrammaticChain({
options: opts,
dirname: context.cwd
}, context, undefined, programmaticLogger);
if (!programmaticChain) return null;
const programmaticReport = yield* programmaticLogger.output();
let configFile;
if (typeof opts.configFile === "string") {
configFile = yield* (0, _index.loadConfig)(opts.configFile, context.cwd, context.envName, context.caller);
} else if (opts.configFile !== false) {
configFile = yield* (0, _index.findRootConfig)(context.root, context.envName, context.caller);
}
let {
babelrc,
babelrcRoots
} = opts;
let babelrcRootsDirectory = context.cwd;
const configFileChain = emptyChain();
const configFileLogger = new _printer.ConfigPrinter();
if (configFile) {
const validatedFile = validateConfigFile(configFile);
const result = yield* loadFileChain(validatedFile, context, undefined, configFileLogger);
if (!result) return null;
configReport = yield* configFileLogger.output();
if (babelrc === undefined) {
babelrc = validatedFile.options.babelrc;
}
if (babelrcRoots === undefined) {
babelrcRootsDirectory = validatedFile.dirname;
babelrcRoots = validatedFile.options.babelrcRoots;
}
mergeChain(configFileChain, result);
}
let ignoreFile, babelrcFile;
let isIgnored = false;
const fileChain = emptyChain();
if ((babelrc === true || babelrc === undefined) && typeof context.filename === "string") {
const pkgData = yield* (0, _index.findPackageData)(context.filename);
if (pkgData && babelrcLoadEnabled(context, pkgData, babelrcRoots, babelrcRootsDirectory)) {
({
ignore: ignoreFile,
config: babelrcFile
} = yield* (0, _index.findRelativeConfig)(pkgData, context.envName, context.caller));
if (ignoreFile) {
fileChain.files.add(ignoreFile.filepath);
}
if (ignoreFile && shouldIgnore(context, ignoreFile.ignore, null, ignoreFile.dirname)) {
isIgnored = true;
}
if (babelrcFile && !isIgnored) {
const validatedFile = validateBabelrcFile(babelrcFile);
const babelrcLogger = new _printer.ConfigPrinter();
const result = yield* loadFileChain(validatedFile, context, undefined, babelrcLogger);
if (!result) {
isIgnored = true;
} else {
babelRcReport = yield* babelrcLogger.output();
mergeChain(fileChain, result);
}
}
if (babelrcFile && isIgnored) {
fileChain.files.add(babelrcFile.filepath);
}
}
}
if (context.showConfig) {
console.log(`Babel configs on "${context.filename}" (ascending priority):\n` + [configReport, babelRcReport, programmaticReport].filter(x => !!x).join("\n\n") + "\n-----End Babel configs-----");
}
const chain = mergeChain(mergeChain(mergeChain(emptyChain(), configFileChain), fileChain), programmaticChain);
return {
plugins: isIgnored ? [] : dedupDescriptors(chain.plugins),
presets: isIgnored ? [] : dedupDescriptors(chain.presets),
options: isIgnored ? [] : chain.options.map(o => createConfigChainOptions(o)),
fileHandling: isIgnored ? "ignored" : "transpile",
ignore: ignoreFile || undefined,
babelrc: babelrcFile || undefined,
config: configFile || undefined,
files: chain.files
};
}
function babelrcLoadEnabled(context, pkgData, babelrcRoots, babelrcRootsDirectory) {
if (typeof babelrcRoots === "boolean") return babelrcRoots;
const absoluteRoot = context.root;
if (babelrcRoots === undefined) {
return pkgData.directories.includes(absoluteRoot);
}
let babelrcPatterns = babelrcRoots;
if (!Array.isArray(babelrcPatterns)) {
babelrcPatterns = [babelrcPatterns];
}
babelrcPatterns = babelrcPatterns.map(pat => {
return typeof pat === "string" ? _path().resolve(babelrcRootsDirectory, pat) : pat;
});
if (babelrcPatterns.length === 1 && babelrcPatterns[0] === absoluteRoot) {
return pkgData.directories.includes(absoluteRoot);
}
return babelrcPatterns.some(pat => {
if (typeof pat === "string") {
pat = (0, _patternToRegex.default)(pat, babelrcRootsDirectory);
}
return pkgData.directories.some(directory => {
return matchPattern(pat, babelrcRootsDirectory, directory, context);
});
});
}
const validateConfigFile = (0, _caching.makeWeakCacheSync)(file => ({
filepath: file.filepath,
dirname: file.dirname,
options: (0, _options.validate)("configfile", file.options, file.filepath)
}));
const validateBabelrcFile = (0, _caching.makeWeakCacheSync)(file => ({
filepath: file.filepath,
dirname: file.dirname,
options: (0, _options.validate)("babelrcfile", file.options, file.filepath)
}));
const validateExtendFile = (0, _caching.makeWeakCacheSync)(file => ({
filepath: file.filepath,
dirname: file.dirname,
options: (0, _options.validate)("extendsfile", file.options, file.filepath)
}));
const loadProgrammaticChain = makeChainWalker({
root: input => buildRootDescriptors(input, "base", _configDescriptors.createCachedDescriptors),
env: (input, envName) => buildEnvDescriptors(input, "base", _configDescriptors.createCachedDescriptors, envName),
overrides: (input, index) => buildOverrideDescriptors(input, "base", _configDescriptors.createCachedDescriptors, index),
overridesEnv: (input, index, envName) => buildOverrideEnvDescriptors(input, "base", _configDescriptors.createCachedDescriptors, index, envName),
createLogger: (input, context, baseLogger) => buildProgrammaticLogger(input, context, baseLogger)
});
const loadFileChainWalker = makeChainWalker({
root: file => loadFileDescriptors(file),
env: (file, envName) => loadFileEnvDescriptors(file)(envName),
overrides: (file, index) => loadFileOverridesDescriptors(file)(index),
overridesEnv: (file, index, envName) => loadFileOverridesEnvDescriptors(file)(index)(envName),
createLogger: (file, context, baseLogger) => buildFileLogger(file.filepath, context, baseLogger)
});
function* loadFileChain(input, context, files, baseLogger) {
const chain = yield* loadFileChainWalker(input, context, files, baseLogger);
chain == null || chain.files.add(input.filepath);
return chain;
}
const loadFileDescriptors = (0, _caching.makeWeakCacheSync)(file => buildRootDescriptors(file, file.filepath, _configDescriptors.createUncachedDescriptors));
const loadFileEnvDescriptors = (0, _caching.makeWeakCacheSync)(file => (0, _caching.makeStrongCacheSync)(envName => buildEnvDescriptors(file, file.filepath, _configDescriptors.createUncachedDescriptors, envName)));
const loadFileOverridesDescriptors = (0, _caching.makeWeakCacheSync)(file => (0, _caching.makeStrongCacheSync)(index => buildOverrideDescriptors(file, file.filepath, _configDescriptors.createUncachedDescriptors, index)));
const loadFileOverridesEnvDescriptors = (0, _caching.makeWeakCacheSync)(file => (0, _caching.makeStrongCacheSync)(index => (0, _caching.makeStrongCacheSync)(envName => buildOverrideEnvDescriptors(file, file.filepath, _configDescriptors.createUncachedDescriptors, index, envName))));
function buildFileLogger(filepath, context, baseLogger) {
if (!baseLogger) {
return () => {};
}
return baseLogger.configure(context.showConfig, _printer.ChainFormatter.Config, {
filepath
});
}
function buildRootDescriptors({
dirname,
options
}, alias, descriptors) {
return descriptors(dirname, options, alias);
}
function buildProgrammaticLogger(_, context, baseLogger) {
var _context$caller;
if (!baseLogger) {
return () => {};
}
return baseLogger.configure(context.showConfig, _printer.ChainFormatter.Programmatic, {
callerName: (_context$caller = context.caller) == null ? void 0 : _context$caller.name
});
}
function buildEnvDescriptors({
dirname,
options
}, alias, descriptors, envName) {
var _options$env;
const opts = (_options$env = options.env) == null ? void 0 : _options$env[envName];
return opts ? descriptors(dirname, opts, `${alias}.env["${envName}"]`) : null;
}
function buildOverrideDescriptors({
dirname,
options
}, alias, descriptors, index) {
var _options$overrides;
const opts = (_options$overrides = options.overrides) == null ? void 0 : _options$overrides[index];
if (!opts) throw new Error("Assertion failure - missing override");
return descriptors(dirname, opts, `${alias}.overrides[${index}]`);
}
function buildOverrideEnvDescriptors({
dirname,
options
}, alias, descriptors, index, envName) {
var _options$overrides2, _override$env;
const override = (_options$overrides2 = options.overrides) == null ? void 0 : _options$overrides2[index];
if (!override) throw new Error("Assertion failure - missing override");
const opts = (_override$env = override.env) == null ? void 0 : _override$env[envName];
return opts ? descriptors(dirname, opts, `${alias}.overrides[${index}].env["${envName}"]`) : null;
}
function makeChainWalker({
root,
env,
overrides,
overridesEnv,
createLogger
}) {
return function* chainWalker(input, context, files = new Set(), baseLogger) {
const {
dirname
} = input;
const flattenedConfigs = [];
const rootOpts = root(input);
if (configIsApplicable(rootOpts, dirname, context, input.filepath)) {
flattenedConfigs.push({
config: rootOpts,
envName: undefined,
index: undefined
});
const envOpts = env(input, context.envName);
if (envOpts && configIsApplicable(envOpts, dirname, context, input.filepath)) {
flattenedConfigs.push({
config: envOpts,
envName: context.envName,
index: undefined
});
}
(rootOpts.options.overrides || []).forEach((_, index) => {
const overrideOps = overrides(input, index);
if (configIsApplicable(overrideOps, dirname, context, input.filepath)) {
flattenedConfigs.push({
config: overrideOps,
index,
envName: undefined
});
const overrideEnvOpts = overridesEnv(input, index, context.envName);
if (overrideEnvOpts && configIsApplicable(overrideEnvOpts, dirname, context, input.filepath)) {
flattenedConfigs.push({
config: overrideEnvOpts,
index,
envName: context.envName
});
}
}
});
}
if (flattenedConfigs.some(({
config: {
options: {
ignore,
only
}
}
}) => shouldIgnore(context, ignore, only, dirname))) {
return null;
}
const chain = emptyChain();
const logger = createLogger(input, context, baseLogger);
for (const {
config,
index,
envName
} of flattenedConfigs) {
if (!(yield* mergeExtendsChain(chain, config.options, dirname, context, files, baseLogger))) {
return null;
}
logger(config, index, envName);
yield* mergeChainOpts(chain, config);
}
return chain;
};
}
function* mergeExtendsChain(chain, opts, dirname, context, files, baseLogger) {
if (opts.extends === undefined) return true;
const file = yield* (0, _index.loadConfig)(opts.extends, dirname, context.envName, context.caller);
if (files.has(file)) {
throw new Error(`Configuration cycle detected loading ${file.filepath}.\n` + `File already loaded following the config chain:\n` + Array.from(files, file => ` - ${file.filepath}`).join("\n"));
}
files.add(file);
const fileChain = yield* loadFileChain(validateExtendFile(file), context, files, baseLogger);
files.delete(file);
if (!fileChain) return false;
mergeChain(chain, fileChain);
return true;
}
function mergeChain(target, source) {
target.options.push(...source.options);
target.plugins.push(...source.plugins);
target.presets.push(...source.presets);
for (const file of source.files) {
target.files.add(file);
}
return target;
}
function* mergeChainOpts(target, {
options,
plugins,
presets
}) {
target.options.push(options);
target.plugins.push(...(yield* plugins()));
target.presets.push(...(yield* presets()));
return target;
}
function emptyChain() {
return {
options: [],
presets: [],
plugins: [],
files: new Set()
};
}
function createConfigChainOptions(opts) {
const options = Object.assign({}, opts);
delete options.extends;
delete options.env;
delete options.overrides;
delete options.plugins;
delete options.presets;
delete options.passPerPreset;
delete options.ignore;
delete options.only;
delete options.test;
delete options.include;
delete options.exclude;
if (hasOwnProperty.call(options, "sourceMap")) {
options.sourceMaps = options.sourceMap;
delete options.sourceMap;
}
return options;
}
function dedupDescriptors(items) {
const map = new Map();
const descriptors = [];
for (const item of items) {
if (typeof item.value === "function") {
const fnKey = item.value;
let nameMap = map.get(fnKey);
if (!nameMap) {
nameMap = new Map();
map.set(fnKey, nameMap);
}
let desc = nameMap.get(item.name);
if (!desc) {
desc = {
value: item
};
descriptors.push(desc);
if (!item.ownPass) nameMap.set(item.name, desc);
} else {
desc.value = item;
}
} else {
descriptors.push({
value: item
});
}
}
return descriptors.reduce((acc, desc) => {
acc.push(desc.value);
return acc;
}, []);
}
function configIsApplicable({
options
}, dirname, context, configName) {
return (options.test === undefined || configFieldIsApplicable(context, options.test, dirname, configName)) && (options.include === undefined || configFieldIsApplicable(context, options.include, dirname, configName)) && (options.exclude === undefined || !configFieldIsApplicable(context, options.exclude, dirname, configName));
}
function configFieldIsApplicable(context, test, dirname, configName) {
const patterns = Array.isArray(test) ? test : [test];
return matchesPatterns(context, patterns, dirname, configName);
}
function ignoreListReplacer(_key, value) {
if (value instanceof RegExp) {
return String(value);
}
return value;
}
function shouldIgnore(context, ignore, only, dirname) {
if (ignore && matchesPatterns(context, ignore, dirname)) {
var _context$filename;
const message = `No config is applied to "${(_context$filename = context.filename) != null ? _context$filename : "(unknown)"}" because it matches one of \`ignore: ${JSON.stringify(ignore, ignoreListReplacer)}\` from "${dirname}"`;
debug(message);
if (context.showConfig) {
console.log(message);
}
return true;
}
if (only && !matchesPatterns(context, only, dirname)) {
var _context$filename2;
const message = `No config is applied to "${(_context$filename2 = context.filename) != null ? _context$filename2 : "(unknown)"}" because it fails to match one of \`only: ${JSON.stringify(only, ignoreListReplacer)}\` from "${dirname}"`;
debug(message);
if (context.showConfig) {
console.log(message);
}
return true;
}
return false;
}
function matchesPatterns(context, patterns, dirname, configName) {
return patterns.some(pattern => matchPattern(pattern, dirname, context.filename, context, configName));
}
function matchPattern(pattern, dirname, pathToTest, context, configName) {
if (typeof pattern === "function") {
return !!(0, _rewriteStackTrace.endHiddenCallStack)(pattern)(pathToTest, {
dirname,
envName: context.envName,
caller: context.caller
});
}
if (typeof pathToTest !== "string") {
throw new _configError.default(`Configuration contains string/RegExp pattern, but no filename was passed to Babel`, configName);
}
if (typeof pattern === "string") {
pattern = (0, _patternToRegex.default)(pattern, dirname);
}
return pattern.test(pathToTest);
}
0 && 0;
//# sourceMappingURL=config-chain.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,190 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.createCachedDescriptors = createCachedDescriptors;
exports.createDescriptor = createDescriptor;
exports.createUncachedDescriptors = createUncachedDescriptors;
function _gensync() {
const data = require("gensync");
_gensync = function () {
return data;
};
return data;
}
var _functional = require("../gensync-utils/functional.js");
var _index = require("./files/index.js");
var _item = require("./item.js");
var _caching = require("./caching.js");
var _resolveTargets = require("./resolve-targets.js");
function isEqualDescriptor(a, b) {
var _a$file, _b$file, _a$file2, _b$file2;
return a.name === b.name && a.value === b.value && a.options === b.options && a.dirname === b.dirname && a.alias === b.alias && a.ownPass === b.ownPass && ((_a$file = a.file) == null ? void 0 : _a$file.request) === ((_b$file = b.file) == null ? void 0 : _b$file.request) && ((_a$file2 = a.file) == null ? void 0 : _a$file2.resolved) === ((_b$file2 = b.file) == null ? void 0 : _b$file2.resolved);
}
function* handlerOf(value) {
return value;
}
function optionsWithResolvedBrowserslistConfigFile(options, dirname) {
if (typeof options.browserslistConfigFile === "string") {
options.browserslistConfigFile = (0, _resolveTargets.resolveBrowserslistConfigFile)(options.browserslistConfigFile, dirname);
}
return options;
}
function createCachedDescriptors(dirname, options, alias) {
const {
plugins,
presets,
passPerPreset
} = options;
return {
options: optionsWithResolvedBrowserslistConfigFile(options, dirname),
plugins: plugins ? () => createCachedPluginDescriptors(plugins, dirname)(alias) : () => handlerOf([]),
presets: presets ? () => createCachedPresetDescriptors(presets, dirname)(alias)(!!passPerPreset) : () => handlerOf([])
};
}
function createUncachedDescriptors(dirname, options, alias) {
return {
options: optionsWithResolvedBrowserslistConfigFile(options, dirname),
plugins: (0, _functional.once)(() => createPluginDescriptors(options.plugins || [], dirname, alias)),
presets: (0, _functional.once)(() => createPresetDescriptors(options.presets || [], dirname, alias, !!options.passPerPreset))
};
}
const PRESET_DESCRIPTOR_CACHE = new WeakMap();
const createCachedPresetDescriptors = (0, _caching.makeWeakCacheSync)((items, cache) => {
const dirname = cache.using(dir => dir);
return (0, _caching.makeStrongCacheSync)(alias => (0, _caching.makeStrongCache)(function* (passPerPreset) {
const descriptors = yield* createPresetDescriptors(items, dirname, alias, passPerPreset);
return descriptors.map(desc => loadCachedDescriptor(PRESET_DESCRIPTOR_CACHE, desc));
}));
});
const PLUGIN_DESCRIPTOR_CACHE = new WeakMap();
const createCachedPluginDescriptors = (0, _caching.makeWeakCacheSync)((items, cache) => {
const dirname = cache.using(dir => dir);
return (0, _caching.makeStrongCache)(function* (alias) {
const descriptors = yield* createPluginDescriptors(items, dirname, alias);
return descriptors.map(desc => loadCachedDescriptor(PLUGIN_DESCRIPTOR_CACHE, desc));
});
});
const DEFAULT_OPTIONS = {};
function loadCachedDescriptor(cache, desc) {
const {
value,
options = DEFAULT_OPTIONS
} = desc;
if (options === false) return desc;
let cacheByOptions = cache.get(value);
if (!cacheByOptions) {
cacheByOptions = new WeakMap();
cache.set(value, cacheByOptions);
}
let possibilities = cacheByOptions.get(options);
if (!possibilities) {
possibilities = [];
cacheByOptions.set(options, possibilities);
}
if (!possibilities.includes(desc)) {
const matches = possibilities.filter(possibility => isEqualDescriptor(possibility, desc));
if (matches.length > 0) {
return matches[0];
}
possibilities.push(desc);
}
return desc;
}
function* createPresetDescriptors(items, dirname, alias, passPerPreset) {
return yield* createDescriptors("preset", items, dirname, alias, passPerPreset);
}
function* createPluginDescriptors(items, dirname, alias) {
return yield* createDescriptors("plugin", items, dirname, alias);
}
function* createDescriptors(type, items, dirname, alias, ownPass) {
const descriptors = yield* _gensync().all(items.map((item, index) => createDescriptor(item, dirname, {
type,
alias: `${alias}$${index}`,
ownPass: !!ownPass
})));
assertNoDuplicates(descriptors);
return descriptors;
}
function* createDescriptor(pair, dirname, {
type,
alias,
ownPass
}) {
const desc = (0, _item.getItemDescriptor)(pair);
if (desc) {
return desc;
}
let name;
let options;
let value = pair;
if (Array.isArray(value)) {
if (value.length === 3) {
[value, options, name] = value;
} else {
[value, options] = value;
}
}
let file = undefined;
let filepath = null;
if (typeof value === "string") {
if (typeof type !== "string") {
throw new Error("To resolve a string-based item, the type of item must be given");
}
const resolver = type === "plugin" ? _index.loadPlugin : _index.loadPreset;
const request = value;
({
filepath,
value
} = yield* resolver(value, dirname));
file = {
request,
resolved: filepath
};
}
if (!value) {
throw new Error(`Unexpected falsy value: ${String(value)}`);
}
if (typeof value === "object" && value.__esModule) {
if (value.default) {
value = value.default;
} else {
throw new Error("Must export a default export when using ES6 modules.");
}
}
if (typeof value !== "object" && typeof value !== "function") {
throw new Error(`Unsupported format: ${typeof value}. Expected an object or a function.`);
}
if (filepath !== null && typeof value === "object" && value) {
throw new Error(`Plugin/Preset files are not allowed to export objects, only functions. In ${filepath}`);
}
return {
name,
alias: filepath || alias,
value,
options,
dirname,
ownPass,
file
};
}
function assertNoDuplicates(items) {
const map = new Map();
for (const item of items) {
if (typeof item.value !== "function") continue;
let nameMap = map.get(item.value);
if (!nameMap) {
nameMap = new Set();
map.set(item.value, nameMap);
}
if (nameMap.has(item.name)) {
const conflicts = items.filter(i => i.value === item.value);
throw new Error([`Duplicate plugin/preset detected.`, `If you'd like to use two separate instances of a plugin,`, `they need separate names, e.g.`, ``, ` plugins: [`, ` ['some-plugin', {}],`, ` ['some-plugin', {}, 'some unique name'],`, ` ]`, ``, `Duplicates detected are:`, `${JSON.stringify(conflicts, null, 2)}`].join("\n"));
}
nameMap.add(item.name);
}
}
0 && 0;
//# sourceMappingURL=config-descriptors.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,290 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.ROOT_CONFIG_FILENAMES = void 0;
exports.findConfigUpwards = findConfigUpwards;
exports.findRelativeConfig = findRelativeConfig;
exports.findRootConfig = findRootConfig;
exports.loadConfig = loadConfig;
exports.resolveShowConfigPath = resolveShowConfigPath;
function _debug() {
const data = require("debug");
_debug = function () {
return data;
};
return data;
}
function _fs() {
const data = require("fs");
_fs = function () {
return data;
};
return data;
}
function _path() {
const data = require("path");
_path = function () {
return data;
};
return data;
}
function _json() {
const data = require("json5");
_json = function () {
return data;
};
return data;
}
function _gensync() {
const data = require("gensync");
_gensync = function () {
return data;
};
return data;
}
var _caching = require("../caching.js");
var _configApi = require("../helpers/config-api.js");
var _utils = require("./utils.js");
var _moduleTypes = require("./module-types.js");
var _patternToRegex = require("../pattern-to-regex.js");
var _configError = require("../../errors/config-error.js");
var fs = require("../../gensync-utils/fs.js");
require("module");
var _rewriteStackTrace = require("../../errors/rewrite-stack-trace.js");
var _async = require("../../gensync-utils/async.js");
const debug = _debug()("babel:config:loading:files:configuration");
const ROOT_CONFIG_FILENAMES = exports.ROOT_CONFIG_FILENAMES = ["babel.config.js", "babel.config.cjs", "babel.config.mjs", "babel.config.json", "babel.config.cts", "babel.config.ts", "babel.config.mts"];
const RELATIVE_CONFIG_FILENAMES = [".babelrc", ".babelrc.js", ".babelrc.cjs", ".babelrc.mjs", ".babelrc.json", ".babelrc.cts"];
const BABELIGNORE_FILENAME = ".babelignore";
const runConfig = (0, _caching.makeWeakCache)(function* runConfig(options, cache) {
yield* [];
return {
options: (0, _rewriteStackTrace.endHiddenCallStack)(options)((0, _configApi.makeConfigAPI)(cache)),
cacheNeedsConfiguration: !cache.configured()
};
});
function* readConfigCode(filepath, data) {
if (!_fs().existsSync(filepath)) return null;
let options = yield* (0, _moduleTypes.default)(filepath, (yield* (0, _async.isAsync)()) ? "auto" : "require", "You appear to be using a native ECMAScript module configuration " + "file, which is only supported when running Babel asynchronously " + "or when using the Node.js `--experimental-require-module` flag.", "You appear to be using a configuration file that contains top-level " + "await, which is only supported when running Babel asynchronously.");
let cacheNeedsConfiguration = false;
if (typeof options === "function") {
({
options,
cacheNeedsConfiguration
} = yield* runConfig(options, data));
}
if (!options || typeof options !== "object" || Array.isArray(options)) {
throw new _configError.default(`Configuration should be an exported JavaScript object.`, filepath);
}
if (typeof options.then === "function") {
options.catch == null || options.catch(() => {});
throw new _configError.default(`You appear to be using an async configuration, ` + `which your current version of Babel does not support. ` + `We may add support for this in the future, ` + `but if you're on the most recent version of @babel/core and still ` + `seeing this error, then you'll need to synchronously return your config.`, filepath);
}
if (cacheNeedsConfiguration) throwConfigError(filepath);
return buildConfigFileObject(options, filepath);
}
const cfboaf = new WeakMap();
function buildConfigFileObject(options, filepath) {
let configFilesByFilepath = cfboaf.get(options);
if (!configFilesByFilepath) {
cfboaf.set(options, configFilesByFilepath = new Map());
}
let configFile = configFilesByFilepath.get(filepath);
if (!configFile) {
configFile = {
filepath,
dirname: _path().dirname(filepath),
options
};
configFilesByFilepath.set(filepath, configFile);
}
return configFile;
}
const packageToBabelConfig = (0, _caching.makeWeakCacheSync)(file => {
const babel = file.options.babel;
if (babel === undefined) return null;
if (typeof babel !== "object" || Array.isArray(babel) || babel === null) {
throw new _configError.default(`.babel property must be an object`, file.filepath);
}
return {
filepath: file.filepath,
dirname: file.dirname,
options: babel
};
});
const readConfigJSON5 = (0, _utils.makeStaticFileCache)((filepath, content) => {
let options;
try {
options = _json().parse(content);
} catch (err) {
throw new _configError.default(`Error while parsing config - ${err.message}`, filepath);
}
if (!options) throw new _configError.default(`No config detected`, filepath);
if (typeof options !== "object") {
throw new _configError.default(`Config returned typeof ${typeof options}`, filepath);
}
if (Array.isArray(options)) {
throw new _configError.default(`Expected config object but found array`, filepath);
}
delete options.$schema;
return {
filepath,
dirname: _path().dirname(filepath),
options
};
});
const readIgnoreConfig = (0, _utils.makeStaticFileCache)((filepath, content) => {
const ignoreDir = _path().dirname(filepath);
const ignorePatterns = content.split("\n").map(line => line.replace(/#.*$/, "").trim()).filter(Boolean);
for (const pattern of ignorePatterns) {
if (pattern.startsWith("!")) {
throw new _configError.default(`Negation of file paths is not supported.`, filepath);
}
}
return {
filepath,
dirname: _path().dirname(filepath),
ignore: ignorePatterns.map(pattern => (0, _patternToRegex.default)(pattern, ignoreDir))
};
});
function findConfigUpwards(rootDir) {
let dirname = rootDir;
for (;;) {
for (const filename of ROOT_CONFIG_FILENAMES) {
if (_fs().existsSync(_path().join(dirname, filename))) {
return dirname;
}
}
const nextDir = _path().dirname(dirname);
if (dirname === nextDir) break;
dirname = nextDir;
}
return null;
}
function* findRelativeConfig(packageData, envName, caller) {
let config = null;
let ignore = null;
const dirname = _path().dirname(packageData.filepath);
for (const loc of packageData.directories) {
if (!config) {
var _packageData$pkg;
config = yield* loadOneConfig(RELATIVE_CONFIG_FILENAMES, loc, envName, caller, ((_packageData$pkg = packageData.pkg) == null ? void 0 : _packageData$pkg.dirname) === loc ? packageToBabelConfig(packageData.pkg) : null);
}
if (!ignore) {
const ignoreLoc = _path().join(loc, BABELIGNORE_FILENAME);
ignore = yield* readIgnoreConfig(ignoreLoc);
if (ignore) {
debug("Found ignore %o from %o.", ignore.filepath, dirname);
}
}
}
return {
config,
ignore
};
}
function findRootConfig(dirname, envName, caller) {
return loadOneConfig(ROOT_CONFIG_FILENAMES, dirname, envName, caller);
}
function* loadOneConfig(names, dirname, envName, caller, previousConfig = null) {
const configs = yield* _gensync().all(names.map(filename => readConfig(_path().join(dirname, filename), envName, caller)));
const config = configs.reduce((previousConfig, config) => {
if (config && previousConfig) {
throw new _configError.default(`Multiple configuration files found. Please remove one:\n` + ` - ${_path().basename(previousConfig.filepath)}\n` + ` - ${config.filepath}\n` + `from ${dirname}`);
}
return config || previousConfig;
}, previousConfig);
if (config) {
debug("Found configuration %o from %o.", config.filepath, dirname);
}
return config;
}
function* loadConfig(name, dirname, envName, caller) {
const filepath = (((v, w) => (v = v.split("."), w = w.split("."), +v[0] > +w[0] || v[0] == w[0] && +v[1] >= +w[1]))(process.versions.node, "8.9") ? require.resolve : (r, {
paths: [b]
}, M = require("module")) => {
let f = M._findPath(r, M._nodeModulePaths(b).concat(b));
if (f) return f;
f = new Error(`Cannot resolve module '${r}'`);
f.code = "MODULE_NOT_FOUND";
throw f;
})(name, {
paths: [dirname]
});
const conf = yield* readConfig(filepath, envName, caller);
if (!conf) {
throw new _configError.default(`Config file contains no configuration data`, filepath);
}
debug("Loaded config %o from %o.", name, dirname);
return conf;
}
function readConfig(filepath, envName, caller) {
const ext = _path().extname(filepath);
switch (ext) {
case ".js":
case ".cjs":
case ".mjs":
case ".ts":
case ".cts":
case ".mts":
return readConfigCode(filepath, {
envName,
caller
});
default:
return readConfigJSON5(filepath);
}
}
function* resolveShowConfigPath(dirname) {
const targetPath = process.env.BABEL_SHOW_CONFIG_FOR;
if (targetPath != null) {
const absolutePath = _path().resolve(dirname, targetPath);
const stats = yield* fs.stat(absolutePath);
if (!stats.isFile()) {
throw new Error(`${absolutePath}: BABEL_SHOW_CONFIG_FOR must refer to a regular file, directories are not supported.`);
}
return absolutePath;
}
return null;
}
function throwConfigError(filepath) {
throw new _configError.default(`\
Caching was left unconfigured. Babel's plugins, presets, and .babelrc.js files can be configured
for various types of caching, using the first param of their handler functions:
module.exports = function(api) {
// The API exposes the following:
// Cache the returned value forever and don't call this function again.
api.cache(true);
// Don't cache at all. Not recommended because it will be very slow.
api.cache(false);
// Cached based on the value of some function. If this function returns a value different from
// a previously-encountered value, the plugins will re-evaluate.
var env = api.cache(() => process.env.NODE_ENV);
// If testing for a specific env, we recommend specifics to avoid instantiating a plugin for
// any possible NODE_ENV value that might come up during plugin execution.
var isProd = api.cache(() => process.env.NODE_ENV === "production");
// .cache(fn) will perform a linear search though instances to find the matching plugin based
// based on previous instantiated plugins. If you want to recreate the plugin and discard the
// previous instance whenever something changes, you may use:
var isProd = api.cache.invalidate(() => process.env.NODE_ENV === "production");
// Note, we also expose the following more-verbose versions of the above examples:
api.cache.forever(); // api.cache(true)
api.cache.never(); // api.cache(false)
api.cache.using(fn); // api.cache(fn)
// Return the value that will be cached.
return { };
};`, filepath);
}
0 && 0;
//# sourceMappingURL=configuration.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,6 @@
module.exports = function import_(filepath) {
return import(filepath);
};
0 && 0;
//# sourceMappingURL=import.cjs.map

View File

@ -0,0 +1 @@
{"version":3,"names":["module","exports","import_","filepath"],"sources":["../../../src/config/files/import.cjs"],"sourcesContent":["// We keep this in a separate file so that in older node versions, where\n// import() isn't supported, we can try/catch around the require() call\n// when loading this file.\n\nmodule.exports = function import_(filepath) {\n return import(filepath);\n};\n"],"mappings":"AAIAA,MAAM,CAACC,OAAO,GAAG,SAASC,OAAOA,CAACC,QAAQ,EAAE;EAC1C,OAAO,OAAOA,QAAQ,CAAC;AACzB,CAAC;AAAC","ignoreList":[]}

View File

@ -0,0 +1,58 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.ROOT_CONFIG_FILENAMES = void 0;
exports.findConfigUpwards = findConfigUpwards;
exports.findPackageData = findPackageData;
exports.findRelativeConfig = findRelativeConfig;
exports.findRootConfig = findRootConfig;
exports.loadConfig = loadConfig;
exports.loadPlugin = loadPlugin;
exports.loadPreset = loadPreset;
exports.resolvePlugin = resolvePlugin;
exports.resolvePreset = resolvePreset;
exports.resolveShowConfigPath = resolveShowConfigPath;
function findConfigUpwards(rootDir) {
return null;
}
function* findPackageData(filepath) {
return {
filepath,
directories: [],
pkg: null,
isPackage: false
};
}
function* findRelativeConfig(pkgData, envName, caller) {
return {
config: null,
ignore: null
};
}
function* findRootConfig(dirname, envName, caller) {
return null;
}
function* loadConfig(name, dirname, envName, caller) {
throw new Error(`Cannot load ${name} relative to ${dirname} in a browser`);
}
function* resolveShowConfigPath(dirname) {
return null;
}
const ROOT_CONFIG_FILENAMES = exports.ROOT_CONFIG_FILENAMES = [];
function resolvePlugin(name, dirname) {
return null;
}
function resolvePreset(name, dirname) {
return null;
}
function loadPlugin(name, dirname) {
throw new Error(`Cannot load plugin ${name} relative to ${dirname} in a browser`);
}
function loadPreset(name, dirname) {
throw new Error(`Cannot load preset ${name} relative to ${dirname} in a browser`);
}
0 && 0;
//# sourceMappingURL=index-browser.js.map

Some files were not shown because too many files have changed in this diff Show More