diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 00000000..d2b5d832
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,130 @@
+name: CI
+
+# 기본 브랜치는 dev 다(origin/HEAD -> origin/dev). 이전 lint.yml 은 트리거가
+# main/develop 이라 한 번도 실행되지 않았고, 그 사이 dev 에 eslint 에러와
+# 의존성 취약점이 그대로 쌓였다. 트리거를 실제 브랜치에 맞추고 레이어별
+# 테스트까지 함께 돌린다.
+on:
+ pull_request:
+ branches: [dev, main]
+ push:
+ branches: [dev, main]
+
+# 같은 브랜치에 연속 push 하면 이전 실행은 취소한다(러너 점유 방지).
+concurrency:
+ group: ci-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ frontend:
+ name: Frontend (lint · types · test · build)
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: actions/setup-node@v4
+ with:
+ node-version-file: frontend/.nvmrc
+ cache: npm
+ cache-dependency-path: frontend/package-lock.json
+
+ - name: Install
+ working-directory: ./frontend
+ run: npm ci
+
+ - name: Lint
+ working-directory: ./frontend
+ run: npm run lint
+
+ - name: Type check
+ working-directory: ./frontend
+ run: npm run type-check
+
+ - name: Test
+ working-directory: ./frontend
+ run: npm run test
+
+ # 타입체크가 통과해도 번들 단계에서 깨지는 경우(정적 자산·플러그인)를 잡는다.
+ - name: Build
+ working-directory: ./frontend
+ run: npm run build
+
+ backend:
+ name: Backend (Core, Spring)
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: actions/setup-java@v4
+ with:
+ distribution: temurin
+ java-version: '21'
+ cache: gradle
+
+ # checkstyle 은 build.gradle 에 플러그인이 없어 태스크 자체가 없다.
+ # 도입하는 PR 에서 이 잡에 단계를 추가한다.
+ - name: Test
+ working-directory: ./backend
+ run: ./gradlew test --no-daemon
+
+ - name: Upload test report
+ if: failure()
+ uses: actions/upload-artifact@v4
+ with:
+ name: backend-test-report
+ path: backend/build/reports/tests/test
+
+ ai:
+ name: AI (FastAPI/LangChain)
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: actions/setup-python@v5
+ with:
+ python-version: '3.13'
+
+ - uses: astral-sh/setup-uv@v5
+ with:
+ enable-cache: true
+
+ - name: Install
+ working-directory: ./ai
+ run: uv sync --extra dev
+
+ - name: Lint (flake8)
+ working-directory: ./ai
+ run: uv run flake8 .
+
+ - name: Format check (black)
+ working-directory: ./ai
+ run: uv run black --check .
+
+ - name: Test
+ working-directory: ./ai
+ run: uv run pytest -q
+
+ realtime:
+ name: RealTime (Go)
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ # Go 코드는 realtime/ 에 있다. 이전 워크플로는 ./backend 에서 실행해
+ # go.mod 가 없어 조용히 통과했고, realtime 은 검증된 적이 없다.
+ - uses: actions/setup-go@v5
+ with:
+ go-version-file: realtime/go.mod
+ cache-dependency-path: realtime/go.sum
+
+ - name: Build
+ working-directory: ./realtime
+ run: go build ./...
+
+ - name: Vet
+ working-directory: ./realtime
+ run: go vet ./...
+
+ - name: Test
+ working-directory: ./realtime
+ run: go test ./...
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
deleted file mode 100644
index 9f9772df..00000000
--- a/.github/workflows/lint.yml
+++ /dev/null
@@ -1,92 +0,0 @@
-name: Lint Check
-
-on:
- pull_request:
- branches:
- - main
- - develop
- push:
- branches:
- - main
- - develop
-
-jobs:
- lint:
- runs-on: ubuntu-latest
-
- steps:
- - name: Checkout code
- uses: actions/checkout@v4
-
- - name: Setup Node.js
- uses: actions/setup-node@v4
- with:
- node-version: '20'
- cache: 'npm'
-
- - name: Setup Python
- uses: actions/setup-python@v5
- with:
- python-version: '3.13'
-
- - name: Install uv
- uses: astral-sh/setup-uv@v5
-
- - name: Setup Java
- uses: actions/setup-java@v4
- with:
- distribution: 'temurin'
- java-version: '21'
-
- - name: Setup Go
- uses: actions/setup-go@v5
- with:
- go-version: '1.22'
-
- # Frontend (TypeScript)
- - name: Install frontend dependencies
- working-directory: ./frontend
- run: npm ci
-
- - name: Lint frontend
- working-directory: ./frontend
- run: npm run lint
-
- - name: Type check frontend
- working-directory: ./frontend
- run: npm run type-check
-
- # Backend (Java + Go)
- - name: Lint backend Java
- working-directory: ./backend
- run: |
- if [ -f "pom.xml" ]; then
- mvn checkstyle:check
- elif [ -f "build.gradle" ] || [ -f "build.gradle.kts" ]; then
- ./gradlew checkstyleMain checkstyleTest
- fi
-
- - name: Lint backend Go
- working-directory: ./backend
- run: |
- if [ -f "go.mod" ]; then
- go fmt ./...
- go vet ./...
- if ! command -v golangci-lint &> /dev/null; then
- curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin
- fi
- golangci-lint run
- fi
-
- # AI (Python)
- - name: Install AI dependencies
- working-directory: ./ai
- run: uv sync --extra dev
-
- - name: Lint AI (flake8)
- working-directory: ./ai
- run: uv run flake8 .
-
- - name: Check AI formatting (black)
- working-directory: ./ai
- run: uv run black --check .
diff --git a/ai/src/ai_server/api/voice_stream.py b/ai/src/ai_server/api/voice_stream.py
index 6dd1e49e..0e8e4656 100644
--- a/ai/src/ai_server/api/voice_stream.py
+++ b/ai/src/ai_server/api/voice_stream.py
@@ -86,7 +86,11 @@ async def pump_transcripts() -> None:
await asyncio.wait_for(pump, timeout=10.0)
except asyncio.TimeoutError:
pump.cancel()
- log.warn("voice_stream.pump.timeout", session_id=session_id, message_id=message_id)
+ log.warn(
+ "voice_stream.pump.timeout",
+ session_id=session_id,
+ message_id=message_id,
+ )
result = await session.result()
await session.close()
diff --git a/ai/src/ai_server/messaging/consumers/tts_consumer.py b/ai/src/ai_server/messaging/consumers/tts_consumer.py
index 6e1dcdf1..c76dceab 100644
--- a/ai/src/ai_server/messaging/consumers/tts_consumer.py
+++ b/ai/src/ai_server/messaging/consumers/tts_consumer.py
@@ -225,9 +225,7 @@ async def _synth(sentence: str) -> TtsResult:
ext = self._EXT_BY_CONTENT_TYPE.get(
res.content_type.split(";")[0].strip(), "mp3"
)
- seg_key = (
- f"interview/tts/{req.session_id}/{req.message_id}/seg-{seq}.{ext}"
- )
+ seg_key = f"interview/tts/{req.session_id}/{req.message_id}/seg-{seq}.{ext}"
try:
await self._storage.put_bytes(
seg_key, res.audio_bytes, content_type=res.content_type
diff --git a/ai/src/ai_server/model/messages/tts.py b/ai/src/ai_server/model/messages/tts.py
index bb12f0eb..213fa557 100644
--- a/ai/src/ai_server/model/messages/tts.py
+++ b/ai/src/ai_server/model/messages/tts.py
@@ -7,10 +7,11 @@
class GenerateTtsRequest(BaseModel):
"""Core 가 질문 메시지 commit 후 발행. AI 가 text 를 TTS 합성."""
+
model_config = camel_config()
session_id: int
- message_id: int # interview_messages.id (INTERVIEWER 질문)
+ message_id: int # interview_messages.id (INTERVIEWER 질문)
text: str
mode: Literal["PERSONALITY", "TECHNICAL", "INTEGRATED"]
job_category: Literal["FRONTEND", "BACKEND", "INFRA", "DBA"]
@@ -18,11 +19,12 @@ class GenerateTtsRequest(BaseModel):
class TtsCallbackPayload(BaseModel):
"""AI → Core. 합성 결과(S3 키) 또는 실패. Core TtsCallbackPayload 와 호환."""
+
model_config = camel_config()
session_id: int
message_id: int
status: Literal["SUCCEEDED", "FAILED"]
- audio_key: str | None = None # camelCase 직렬화 → audioKey
+ audio_key: str | None = None # camelCase 직렬화 → audioKey
duration_sec: float | None = None
error_code: str | None = None
diff --git a/ai/src/ai_server/model/messages/voice.py b/ai/src/ai_server/model/messages/voice.py
index 3853c72b..95e96ef4 100644
--- a/ai/src/ai_server/model/messages/voice.py
+++ b/ai/src/ai_server/model/messages/voice.py
@@ -9,10 +9,11 @@
class AnalyzeVoiceRequest(BaseModel):
"""Core 가 음성 답변 업로드 commit 후 발행."""
+
model_config = camel_config()
session_id: int
- message_id: int # interview_messages.id (placeholder, STT 후 content 채움)
+ message_id: int # interview_messages.id (placeholder, STT 후 content 채움)
parent_question_message_id: int | None = None
audio_s3_key: str
content_type: str
@@ -23,6 +24,7 @@ class AnalyzeVoiceRequest(BaseModel):
class VoiceCallbackPayload(BaseModel):
"""AI → Core. STT 결과 + 음성 지표."""
+
model_config = camel_config()
session_id: int
@@ -32,4 +34,4 @@ class VoiceCallbackPayload(BaseModel):
silence_duration_sec: float | None = None
filler_word_counts: dict[str, int] = Field(default_factory=dict)
pronunciation_accuracy: float | None = None
- error_code: str | None = None # 실패 시 코드 (Core 가 message FAILED 마킹)
+ error_code: str | None = None # 실패 시 코드 (Core 가 message FAILED 마킹)
diff --git a/ai/src/ai_server/observability/llm_logging_callback.py b/ai/src/ai_server/observability/llm_logging_callback.py
index 7ccefdbf..4ae28975 100644
--- a/ai/src/ai_server/observability/llm_logging_callback.py
+++ b/ai/src/ai_server/observability/llm_logging_callback.py
@@ -22,16 +22,22 @@ class CoreAiLogCallback(AsyncCallbackHandler):
metadata 에서 토큰 수를 추출한다.
"""
- def __init__(self, *, core_client: CoreClient, request_type: str, default_model: str) -> None:
+ def __init__(
+ self, *, core_client: CoreClient, request_type: str, default_model: str
+ ) -> None:
self._core = core_client
self._request_type = request_type
self._default_model = default_model
self._start_at: dict[UUID, float] = {}
- async def on_llm_start(self, serialized, prompts, *, run_id: UUID, **kwargs: Any) -> None:
+ async def on_llm_start(
+ self, serialized, prompts, *, run_id: UUID, **kwargs: Any
+ ) -> None:
self._start_at[run_id] = time.perf_counter()
- async def on_llm_end(self, response: LLMResult, *, run_id: UUID, **kwargs: Any) -> None:
+ async def on_llm_end(
+ self, response: LLMResult, *, run_id: UUID, **kwargs: Any
+ ) -> None:
latency_ms = self._latency_ms(run_id)
in_tok, out_tok, model = _extract_usage(response, self._default_model)
await self._fire(
@@ -44,7 +50,9 @@ async def on_llm_end(self, response: LLMResult, *, run_id: UUID, **kwargs: Any)
error_message=None,
)
- async def on_llm_error(self, error: BaseException, *, run_id: UUID, **kwargs: Any) -> None:
+ async def on_llm_error(
+ self, error: BaseException, *, run_id: UUID, **kwargs: Any
+ ) -> None:
latency_ms = self._latency_ms(run_id)
await self._fire(
request_type=self._request_type,
@@ -69,10 +77,13 @@ async def _do() -> None:
await self._core.record_ai_log(**kwargs)
except Exception as exc:
log.warn("core.ai_log.background_failed", error=str(exc), **kwargs)
+
asyncio.create_task(_do())
-def _extract_usage(response: LLMResult, default_model: str) -> tuple[int | None, int | None, str]:
+def _extract_usage(
+ response: LLMResult, default_model: str
+) -> tuple[int | None, int | None, str]:
in_tok = out_tok = None
model = default_model
try:
@@ -90,8 +101,16 @@ def _extract_usage(response: LLMResult, default_model: str) -> tuple[int | None,
info = getattr(gen, "generation_info", None) or {}
usage = info.get("usage_metadata") or info.get("token_usage") or {}
if isinstance(usage, dict):
- in_tok = in_tok or usage.get("input_tokens") or usage.get("prompt_tokens")
- out_tok = out_tok or usage.get("output_tokens") or usage.get("completion_tokens")
+ in_tok = (
+ in_tok
+ or usage.get("input_tokens")
+ or usage.get("prompt_tokens")
+ )
+ out_tok = (
+ out_tok
+ or usage.get("output_tokens")
+ or usage.get("completion_tokens")
+ )
except Exception as exc:
log.debug("ai_log.usage_extract_failed", error=str(exc))
return in_tok, out_tok, model
diff --git a/ai/src/ai_server/voice/stt/deepgram_live.py b/ai/src/ai_server/voice/stt/deepgram_live.py
index e901ae0b..23195830 100644
--- a/ai/src/ai_server/voice/stt/deepgram_live.py
+++ b/ai/src/ai_server/voice/stt/deepgram_live.py
@@ -10,7 +10,11 @@
import websockets
from ai_server.voice.stt.base import TranscriptionResult, TranscriptionSegment
-from ai_server.voice.stt.live import LiveSttProvider, LiveSttSession, LiveTranscriptEvent
+from ai_server.voice.stt.live import (
+ LiveSttProvider,
+ LiveSttSession,
+ LiveTranscriptEvent,
+)
from ai_server.voice.stt.sanitize import (
sanitize_transcription,
strip_stt_hallucinations,
@@ -20,8 +24,16 @@
class _DeepgramLiveSession(LiveSttSession):
- def __init__(self, *, api_key: str, url: str, model: str, language: str,
- endpointing_ms: int, content_type: str) -> None:
+ def __init__(
+ self,
+ *,
+ api_key: str,
+ url: str,
+ model: str,
+ language: str,
+ endpointing_ms: int,
+ content_type: str,
+ ) -> None:
self._api_key = api_key
self._url = url
self._model = model
@@ -63,7 +75,7 @@ async def _recv_loop(self) -> None:
msg = json.loads(raw)
mtype = msg.get("type")
if mtype == "Results":
- alt = (((msg.get("channel") or {}).get("alternatives") or [{}])[0])
+ alt = ((msg.get("channel") or {}).get("alternatives") or [{}])[0]
text = str(alt.get("transcript") or "")
is_final = bool(msg.get("is_final"))
speech_final = bool(msg.get("speech_final"))
@@ -135,8 +147,9 @@ async def close(self) -> None:
class DeepgramLiveSttProvider(LiveSttProvider):
model_name = "deepgram-live"
- def __init__(self, *, api_key: str, url: str, model: str, language: str,
- endpointing_ms: int) -> None:
+ def __init__(
+ self, *, api_key: str, url: str, model: str, language: str, endpointing_ms: int
+ ) -> None:
if not api_key:
raise ValueError("Deepgram API key 누락")
self._api_key = api_key
@@ -145,7 +158,9 @@ def __init__(self, *, api_key: str, url: str, model: str, language: str,
self._language = language
self._endpointing_ms = endpointing_ms
- def open_session(self, *, content_type: str, language: str | None) -> LiveSttSession:
+ def open_session(
+ self, *, content_type: str, language: str | None
+ ) -> LiveSttSession:
return _DeepgramLiveSession(
api_key=self._api_key,
url=self._url,
diff --git a/ai/src/ai_server/voice/stt/live.py b/ai/src/ai_server/voice/stt/live.py
index d77c202a..e2a9de75 100644
--- a/ai/src/ai_server/voice/stt/live.py
+++ b/ai/src/ai_server/voice/stt/live.py
@@ -10,8 +10,8 @@
@dataclass(frozen=True)
class LiveTranscriptEvent:
text: str
- is_final: bool # 발화(utterance) 확정 조각인지
- speech_final: bool # 발화 끝(턴 종료 신호)
+ is_final: bool # 발화(utterance) 확정 조각인지
+ speech_final: bool # 발화 끝(턴 종료 신호)
class LiveSttSession(ABC):
@@ -42,4 +42,6 @@ class LiveSttProvider(ABC):
model_name: str = "live-stt"
@abstractmethod
- def open_session(self, *, content_type: str, language: str | None) -> LiveSttSession: ...
+ def open_session(
+ self, *, content_type: str, language: str | None
+ ) -> LiveSttSession: ...
diff --git a/ai/src/ai_server/voice/stt/mock_live.py b/ai/src/ai_server/voice/stt/mock_live.py
index d6550952..16d22026 100644
--- a/ai/src/ai_server/voice/stt/mock_live.py
+++ b/ai/src/ai_server/voice/stt/mock_live.py
@@ -4,7 +4,11 @@
from collections.abc import AsyncIterator
from ai_server.voice.stt.base import TranscriptionResult, TranscriptionSegment
-from ai_server.voice.stt.live import LiveSttProvider, LiveSttSession, LiveTranscriptEvent
+from ai_server.voice.stt.live import (
+ LiveSttProvider,
+ LiveSttSession,
+ LiveTranscriptEvent,
+)
class _MockLiveSession(LiveSttSession):
@@ -21,7 +25,9 @@ async def push(self, chunk: bytes) -> None:
# 청크마다 부분 transcript 한 조각 방출 (스크립트 순서대로).
idx = min(self._pushes, len(self._script) - 1)
await self._queue.put(
- LiveTranscriptEvent(text=self._script[idx], is_final=False, speech_final=False)
+ LiveTranscriptEvent(
+ text=self._script[idx], is_final=False, speech_final=False
+ )
)
self._pushes += 1
@@ -63,5 +69,7 @@ class MockLiveSttProvider(LiveSttProvider):
def __init__(self, script: list[str] | None = None) -> None:
self._script = script or ["부분", "부분 transcript", "부분 transcript 완성"]
- def open_session(self, *, content_type: str, language: str | None) -> LiveSttSession:
+ def open_session(
+ self, *, content_type: str, language: str | None
+ ) -> LiveSttSession:
return _MockLiveSession(self._script)
diff --git a/ai/src/ai_server/voice/tts/mock.py b/ai/src/ai_server/voice/tts/mock.py
index 638b9e35..f39b3a7e 100644
--- a/ai/src/ai_server/voice/tts/mock.py
+++ b/ai/src/ai_server/voice/tts/mock.py
@@ -10,4 +10,6 @@ async def synthesize(self, text: str, *, voice: str) -> TtsResult:
# 텍스트 길이에 비례한 가짜 오디오 바이트 + 추정 길이(분당 ~300자 가정).
payload = ("MOCKMP3:" + text).encode("utf-8")
duration = max(0.5, round(len(text) / 5.0, 2))
- return TtsResult(audio_bytes=payload, duration_sec=duration, content_type="audio/mpeg")
+ return TtsResult(
+ audio_bytes=payload, duration_sec=duration, content_type="audio/mpeg"
+ )
diff --git a/ai/src/ai_server/voice/tts/openai_tts.py b/ai/src/ai_server/voice/tts/openai_tts.py
index 02e4b328..36d2e07f 100644
--- a/ai/src/ai_server/voice/tts/openai_tts.py
+++ b/ai/src/ai_server/voice/tts/openai_tts.py
@@ -47,4 +47,6 @@ async def synthesize(self, text: str, *, voice: str) -> TtsResult:
if not audio:
raise TtsError("TTS_EMPTY_AUDIO", "empty audio body")
# OpenAI 는 duration 을 주지 않음 → None (Core completeTts 가 null 허용).
- return TtsResult(audio_bytes=audio, duration_sec=None, content_type="audio/mpeg")
+ return TtsResult(
+ audio_bytes=audio, duration_sec=None, content_type="audio/mpeg"
+ )
diff --git a/ai/tests/test_deepgram_stt.py b/ai/tests/test_deepgram_stt.py
index 5abd70fc..d9ee9d2e 100644
--- a/ai/tests/test_deepgram_stt.py
+++ b/ai/tests/test_deepgram_stt.py
@@ -34,14 +34,26 @@ async def test_deepgram_returns_segments_and_logprob_from_utterances():
}
],
"utterances": [
- {"start": 0.0, "end": 2.5, "transcript": "안녕하세요", "confidence": 0.95},
- {"start": 3.0, "end": 6.0, "transcript": "백엔드 지원자입니다", "confidence": 0.88},
+ {
+ "start": 0.0,
+ "end": 2.5,
+ "transcript": "안녕하세요",
+ "confidence": 0.95,
+ },
+ {
+ "start": 3.0,
+ "end": 6.0,
+ "transcript": "백엔드 지원자입니다",
+ "confidence": 0.88,
+ },
],
},
}
async with _client(status=200, body=response) as client:
provider = DeepgramSttProvider(api_key="k", client=client)
- result = await provider.transcribe(audio_bytes=b"fake", content_type="audio/webm")
+ result = await provider.transcribe(
+ audio_bytes=b"fake", content_type="audio/webm"
+ )
assert result.text.startswith("안녕하세요")
assert result.duration_sec == 6.0
@@ -66,7 +78,9 @@ async def test_deepgram_fallback_segment_when_no_utterances():
}
async with _client(status=200, body=response) as client:
provider = DeepgramSttProvider(api_key="k", client=client)
- result = await provider.transcribe(audio_bytes=b"fake", content_type="audio/webm")
+ result = await provider.transcribe(
+ audio_bytes=b"fake", content_type="audio/webm"
+ )
assert result.text == "한 줄 답변"
assert len(result.segments) == 1
diff --git a/ai/tests/test_embedding_step.py b/ai/tests/test_embedding_step.py
index b52acac3..9e3f1406 100644
--- a/ai/tests/test_embedding_step.py
+++ b/ai/tests/test_embedding_step.py
@@ -8,7 +8,9 @@
def test_contextualize_prefixes_summary_and_heading() -> None:
- out = _contextualize("본문 내용", heading_path="주요 경험 > 결제", summary="백엔드 지원자")
+ out = _contextualize(
+ "본문 내용", heading_path="주요 경험 > 결제", summary="백엔드 지원자"
+ )
assert out.startswith("[백엔드 지원자 > 주요 경험 > 결제]")
assert out.endswith("본문 내용")
diff --git a/ai/tests/test_envelope.py b/ai/tests/test_envelope.py
index 69c7ae7b..b3f01aac 100644
--- a/ai/tests/test_envelope.py
+++ b/ai/tests/test_envelope.py
@@ -1,5 +1,4 @@
import json
-from datetime import datetime, timezone
from uuid import uuid4
import pytest
diff --git a/ai/tests/test_feedback_highlights.py b/ai/tests/test_feedback_highlights.py
index 4565aa2a..fcc5ba19 100644
--- a/ai/tests/test_feedback_highlights.py
+++ b/ai/tests/test_feedback_highlights.py
@@ -18,9 +18,19 @@ def test_keeps_only_verbatim_substrings():
def test_dedup_and_cap():
body = "가가 나나 다다 라라 마마 바바 사사 가가"
out = _filter_highlights(
- ["가가", "가가", "나나", "다다", "라라", "마마", "바바", "사사"], body, None, cap=6
+ ["가가", "가가", "나나", "다다", "라라", "마마", "바바", "사사"],
+ body,
+ None,
+ cap=6,
)
- assert out == ["가가", "나나", "다다", "라라", "마마", "바바"] # 중복 제거 + 6개 상한
+ assert out == [
+ "가가",
+ "나나",
+ "다다",
+ "라라",
+ "마마",
+ "바바",
+ ] # 중복 제거 + 6개 상한
def test_empty_when_no_match_or_no_body():
diff --git a/ai/tests/test_feedback_rag_query.py b/ai/tests/test_feedback_rag_query.py
index 992c55b6..b0956b0a 100644
--- a/ai/tests/test_feedback_rag_query.py
+++ b/ai/tests/test_feedback_rag_query.py
@@ -4,14 +4,22 @@
from ai_server.model.messages.feedback import FeedbackMessageItem
-def _msg(mid: int, role: str, content: str, seq: int | None = None) -> FeedbackMessageItem:
- return FeedbackMessageItem(id=mid, sequence_number=seq or mid, role=role, content=content)
+def _msg(
+ mid: int, role: str, content: str, seq: int | None = None
+) -> FeedbackMessageItem:
+ return FeedbackMessageItem(
+ id=mid, sequence_number=seq or mid, role=role, content=content
+ )
def test_joins_substantive_answers_excludes_short_confirmations() -> None:
msgs = [
_msg(1, "INTERVIEWER", "인덱스를 어떻게 튜닝했나요?"),
- _msg(2, "INTERVIEWEE", "복합 인덱스를 user_id, created_at 으로 잡아 풀스캔을 제거했습니다."),
+ _msg(
+ 2,
+ "INTERVIEWEE",
+ "복합 인덱스를 user_id, created_at 으로 잡아 풀스캔을 제거했습니다.",
+ ),
_msg(3, "INTERVIEWER", "그럼 커버링 인덱스였나요?"),
_msg(4, "INTERVIEWEE", "네 맞습니다."), # 짧은 확인 → 제외
_msg(5, "INTERVIEWER", "캐시는요?"),
diff --git a/ai/tests/test_followup_consumer.py b/ai/tests/test_followup_consumer.py
index d75ddf93..f72c9b17 100644
--- a/ai/tests/test_followup_consumer.py
+++ b/ai/tests/test_followup_consumer.py
@@ -12,6 +12,7 @@
from ai_server.model.messages.followup import (
AnswerEvaluation,
FollowupCallbackPayload,
+ GenerateFollowupRequest,
)
@@ -555,9 +556,7 @@ async def test_callback_includes_answer_message_id():
# ---------------------------------------------------------------------------
-def _make_req() -> "GenerateFollowupRequest":
- from ai_server.model.messages.followup import GenerateFollowupRequest
-
+def _make_req() -> GenerateFollowupRequest:
return GenerateFollowupRequest(
session_id=99,
parent_message_id=501,
diff --git a/ai/tests/test_followup_parse.py b/ai/tests/test_followup_parse.py
index 6c3bac96..25b734cd 100644
--- a/ai/tests/test_followup_parse.py
+++ b/ai/tests/test_followup_parse.py
@@ -26,7 +26,9 @@ def test_extract_question_only_helper():
assert extract_question_span("NORMAL안녕") == "안녕"
assert (
- extract_question_span("NORMAL안녕{}")
+ extract_question_span(
+ "NORMAL안녕{}"
+ )
== "안녕"
)
@@ -40,7 +42,9 @@ def test_streaming_keeps_lt_in_body():
== "a < b 인지 확인하세요"
)
assert (
- extract_question_span("NORMAL제네릭 List 를 설명하세요")
+ extract_question_span(
+ "NORMAL제네릭 List 를 설명하세요"
+ )
== "제네릭 List 를 설명하세요"
)
@@ -49,8 +53,14 @@ def test_streaming_strips_partial_closing_or_meta_tag():
# 청크 경계로 잘려 들어온 닫는/메타 태그 partial 만 제거한다.
from ai_server.chain.followup_generation_chain import extract_question_span
- assert extract_question_span("NORMAL질문입니다?NORMAL끝났나요?NORMAL질문입니다?NORMAL끝났나요?DONT_KNOW모르면 이렇게{}"]
+ pieces = [
+ "DONT_KNOW모르면 이렇게{}"
+ ]
prompt = ChatPromptTemplate.from_messages([("human", "{answer_text}")])
gen = StreamingFollowupGenerator(prompt=prompt, llm=_FakeStreamLLM(pieces))
seen = []
diff --git a/ai/tests/test_mock_live.py b/ai/tests/test_mock_live.py
index 303c3481..d7ce92d8 100644
--- a/ai/tests/test_mock_live.py
+++ b/ai/tests/test_mock_live.py
@@ -5,7 +5,9 @@
@pytest.mark.asyncio
async def test_mock_live_emits_partials_then_final():
- provider = MockLiveSttProvider(script=["안녕", "안녕하세요", "안녕하세요 반갑습니다"])
+ provider = MockLiveSttProvider(
+ script=["안녕", "안녕하세요", "안녕하세요 반갑습니다"]
+ )
session = provider.open_session(content_type="audio/webm", language="ko")
await session.start()
# 오디오 청크 3개 투입
diff --git a/ai/tests/test_settings.py b/ai/tests/test_settings.py
index 6baef0c6..84937cf8 100644
--- a/ai/tests/test_settings.py
+++ b/ai/tests/test_settings.py
@@ -1,5 +1,3 @@
-import os
-
import pytest
from ai_server.config.settings import Settings
diff --git a/ai/tests/test_short_confirmation.py b/ai/tests/test_short_confirmation.py
index 07180e18..f938ac31 100644
--- a/ai/tests/test_short_confirmation.py
+++ b/ai/tests/test_short_confirmation.py
@@ -41,15 +41,34 @@ def test_keeps_substantive_answers(text: str) -> None:
def test_coaching_pairs_skip_short_confirmation() -> None:
msgs = [
- FeedbackMessageItem(id=1, sequence_number=1, role="INTERVIEWER",
- content="그럼 인덱스를 직접 만드셨나요?", category="TECH_CHOICE"),
- FeedbackMessageItem(id=2, sequence_number=2, role="INTERVIEWEE",
- content="네 맞습니다.", parent_message_id=1),
- FeedbackMessageItem(id=3, sequence_number=3, role="INTERVIEWER",
- content="어떤 컬럼으로 복합 인덱스를 구성했나요?", category="TECH_CHOICE"),
- FeedbackMessageItem(id=4, sequence_number=4, role="INTERVIEWEE",
- content="조회 조건인 user_id 와 created_at 으로 복합 인덱스를 잡았습니다.",
- parent_message_id=3),
+ FeedbackMessageItem(
+ id=1,
+ sequence_number=1,
+ role="INTERVIEWER",
+ content="그럼 인덱스를 직접 만드셨나요?",
+ category="TECH_CHOICE",
+ ),
+ FeedbackMessageItem(
+ id=2,
+ sequence_number=2,
+ role="INTERVIEWEE",
+ content="네 맞습니다.",
+ parent_message_id=1,
+ ),
+ FeedbackMessageItem(
+ id=3,
+ sequence_number=3,
+ role="INTERVIEWER",
+ content="어떤 컬럼으로 복합 인덱스를 구성했나요?",
+ category="TECH_CHOICE",
+ ),
+ FeedbackMessageItem(
+ id=4,
+ sequence_number=4,
+ role="INTERVIEWEE",
+ content="조회 조건인 user_id 와 created_at 으로 복합 인덱스를 잡았습니다.",
+ parent_message_id=3,
+ ),
]
pairs = _collect_coachable_pairs(msgs)
answer_ids = [a.id for _, a in pairs]
diff --git a/ai/tests/test_stt_sanitize.py b/ai/tests/test_stt_sanitize.py
index 557fe539..f796bf57 100644
--- a/ai/tests/test_stt_sanitize.py
+++ b/ai/tests/test_stt_sanitize.py
@@ -56,7 +56,9 @@ def test_sanitize_transcription_drops_hallucination_segment() -> None:
language="ko",
duration_sec=30.0,
segments=[
- TranscriptionSegment(start_sec=0.0, end_sec=4.0, text="타임아웃을 잡았습니다."),
+ TranscriptionSegment(
+ start_sec=0.0, end_sec=4.0, text="타임아웃을 잡았습니다."
+ ),
TranscriptionSegment(
start_sec=29.0, end_sec=30.0, text="MBC 뉴스 김재경입니다."
),
@@ -69,5 +71,7 @@ def test_sanitize_transcription_drops_hallucination_segment() -> None:
assert len(cleaned.segments) == 1
assert cleaned.segments[0].text == "타임아웃을 잡았습니다."
# 환각이 없는 결과는 동일 객체를 그대로 반환(불필요한 재할당 방지).
- clean_in = TranscriptionResult(text="정상 답변입니다.", language="ko", duration_sec=2.0, segments=[])
+ clean_in = TranscriptionResult(
+ text="정상 답변입니다.", language="ko", duration_sec=2.0, segments=[]
+ )
assert sanitize_transcription(clean_in) is clean_in
diff --git a/ai/tests/test_tts_consumer.py b/ai/tests/test_tts_consumer.py
index 53319647..1b13d44e 100644
--- a/ai/tests/test_tts_consumer.py
+++ b/ai/tests/test_tts_consumer.py
@@ -50,7 +50,9 @@ class _RecordingNotifier:
def __init__(self):
self.audio: list[dict] = []
- async def emit_audio(self, *, session_id, message_id, seq, ext, duration_sec, trace_id):
+ async def emit_audio(
+ self, *, session_id, message_id, seq, ext, duration_sec, trace_id
+ ):
self.audio.append(
{"session_id": session_id, "message_id": message_id, "seq": seq, "ext": ext}
)
diff --git a/ai/tests/test_voice_metrics.py b/ai/tests/test_voice_metrics.py
index caf2bcd1..c9b761f8 100644
--- a/ai/tests/test_voice_metrics.py
+++ b/ai/tests/test_voice_metrics.py
@@ -14,8 +14,12 @@ def test_word_count_and_wpm():
language="ko",
duration_sec=60.0,
segments=[
- TranscriptionSegment(start_sec=0.0, end_sec=60.0, text="저는 백엔드 개발자 입니다",
- avg_logprob=-0.2),
+ TranscriptionSegment(
+ start_sec=0.0,
+ end_sec=60.0,
+ text="저는 백엔드 개발자 입니다",
+ avg_logprob=-0.2,
+ ),
],
)
m = analyze(result, filler_pattern=FILLER_PATTERN)
diff --git a/ai/tests/test_voice_stream_endpoint.py b/ai/tests/test_voice_stream_endpoint.py
index 28f76a43..39d191e2 100644
--- a/ai/tests/test_voice_stream_endpoint.py
+++ b/ai/tests/test_voice_stream_endpoint.py
@@ -14,7 +14,7 @@ async def publish(self, **kwargs):
class _Settings:
- core_internal_api_key = "" # 빈 값이면 인증 skip
+ core_internal_api_key = "" # 빈 값이면 인증 skip
voice_filler_pattern = r"(음+|어+|그+)"
ai_callback_routing_voice = "callback.voice"
@@ -31,7 +31,9 @@ def _app(publisher):
def test_stream_publishes_callback_voice_on_stop():
publisher = _FakePublisher()
client = TestClient(_app(publisher))
- with client.websocket_connect("/internal/voice/stream?sessionId=7&messageId=42") as ws:
+ with client.websocket_connect(
+ "/internal/voice/stream?sessionId=7&messageId=42"
+ ) as ws:
ws.send_bytes(b"chunk1")
ws.send_bytes(b"chunk2")
# 부분 자막 수신
diff --git a/docs/git-conventions.md b/docs/git-conventions.md
index 5b979fa5..b0480db3 100644
--- a/docs/git-conventions.md
+++ b/docs/git-conventions.md
@@ -1,22 +1,27 @@
# Git 컨벤션
-> 브랜치 전략, 커밋 메시지, PR 규약. 본 컨벤션은 `.github/PULL_REQUEST_TEMPLATE.md` 와 `.github/workflows/lint.yml` 의 전제와 일치한다.
+> 브랜치 전략, 커밋 메시지, PR 규약. 본 컨벤션은 `.github/pull_request_template.md` 와 `.github/workflows/ci.yml` 의 전제와 일치한다.
---
## 1. 브랜치 전략
### 1.1 브랜치 종류
+
+**기본 브랜치는 `dev` 다** (`origin/HEAD -> origin/dev`). 모든 작업 브랜치는 `dev` 로 PR 을 낸다.
+`develop` 브랜치는 존재하지 않는다 — 과거 문서/워크플로가 이 이름을 전제해 CI 가 실행되지 않았으므로,
+새 워크플로를 만들 때 트리거 브랜치는 반드시 `dev` (필요 시 `main` 추가) 로 둔다.
+
| 브랜치 | 용도 | 머지 대상 |
|--------|------|-----------|
+| `dev` | 통합 개발 + 자동 배포 기준 (기본 브랜치) | main |
| `main` | 배포 가능한 최신 상태 | (보호) |
-| `develop` | 통합 개발 (옵션 — 본 프로젝트 기준 직접 main 사용 가능) | main |
-| `feature/*` | 신규 기능 | main (또는 develop) |
-| `fix/*` | 버그 수정 | main |
-| `hotfix/*` | 긴급 운영 수정 | main + cherry-pick to develop |
-| `chore/*` | 비기능 변경 (lint, deps, doc) | main |
-| `refactor/*` | 동작 변경 없는 구조 개선 | main |
-| `docs/*` | 문서만 | main |
+| `feature/*` | 신규 기능 | dev |
+| `fix/*` | 버그 수정 | dev |
+| `hotfix/*` | 긴급 운영 수정 | dev (+ 필요 시 main cherry-pick) |
+| `chore/*` | 비기능 변경 (lint, deps, doc) | dev |
+| `refactor/*` | 동작 변경 없는 구조 개선 | dev |
+| `docs/*` | 문서만 | dev |
### 1.2 명명
```
@@ -31,7 +36,7 @@ chore/add-renovate-config
### 1.3 브랜치 수명
- feature: ≤ 1주 (장기 시 재분기 검토)
-- 매일 main → feature 병합으로 충돌 최소화 (`git rebase main` 권장)
+- 매일 dev → feature 병합으로 충돌 최소화 (`git rebase dev` 권장)
---
@@ -95,7 +100,7 @@ docs(architecture): RabbitMQ 토폴로지 다이어그램 추가
커밋 메시지와 동일 포맷. squash merge 시 그대로 commit이 됨.
### 3.3 본문 (PR 템플릿)
-`.github/PULL_REQUEST_TEMPLATE.md` 가 자동 적용. 다음 섹션 권장:
+`.github/pull_request_template.md` 가 자동 적용. 다음 섹션 권장:
```markdown
## 작업 내용
@@ -127,20 +132,20 @@ docs(architecture): RabbitMQ 토폴로지 다이어그램 추가
### 3.5 머지 정책
- **Squash merge** 기본 (히스토리 깔끔)
- 단, 의미 있는 커밋이 여러 개라면 일반 merge도 허용
-- main 직접 푸시 금지
-- 1명 이상 approve + CI green 필수
+- dev 직접 푸시 금지
+- 1명 이상 approve + CI green 필수 (`ci.yml` 의 frontend·backend·ai·realtime 4개 잡)
---
## 4. 충돌 해결
-- 본인 브랜치에서 `git rebase main` (merge 대신)
+- 본인 브랜치에서 `git rebase dev` (merge 대신)
- 충돌 발생 시 양쪽 변경의 의도를 확인 (단순 텍스트 머지 X)
- DB 마이그레이션 충돌: 버전 번호 재할당 + 순서 정합성 확인
---
-## 5. 보호 정책 (main)
+## 5. 보호 정책 (dev · main)
- 직접 push 금지
- PR + CI green + 1 approve 필수
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 732a3eb8..f8c399b5 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -102,13 +102,13 @@
"license": "MIT"
},
"node_modules/@babel/code-frame": {
- "version": "7.29.0",
- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
- "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz",
+ "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-validator-identifier": "^7.28.5",
+ "@babel/helper-validator-identifier": "^7.29.7",
"js-tokens": "^4.0.0",
"picocolors": "^1.1.1"
},
@@ -117,9 +117,9 @@
}
},
"node_modules/@babel/compat-data": {
- "version": "7.29.0",
- "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz",
- "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz",
+ "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -127,21 +127,21 @@
}
},
"node_modules/@babel/core": {
- "version": "7.29.0",
- "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz",
- "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz",
+ "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/code-frame": "^7.29.0",
- "@babel/generator": "^7.29.0",
- "@babel/helper-compilation-targets": "^7.28.6",
- "@babel/helper-module-transforms": "^7.28.6",
- "@babel/helpers": "^7.28.6",
- "@babel/parser": "^7.29.0",
- "@babel/template": "^7.28.6",
- "@babel/traverse": "^7.29.0",
- "@babel/types": "^7.29.0",
+ "@babel/code-frame": "^7.29.7",
+ "@babel/generator": "^7.29.7",
+ "@babel/helper-compilation-targets": "^7.29.7",
+ "@babel/helper-module-transforms": "^7.29.7",
+ "@babel/helpers": "^7.29.7",
+ "@babel/parser": "^7.29.7",
+ "@babel/template": "^7.29.7",
+ "@babel/traverse": "^7.29.7",
+ "@babel/types": "^7.29.7",
"@jridgewell/remapping": "^2.3.5",
"convert-source-map": "^2.0.0",
"debug": "^4.1.0",
@@ -158,14 +158,14 @@
}
},
"node_modules/@babel/generator": {
- "version": "7.29.1",
- "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz",
- "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==",
+ "version": "7.29.8",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz",
+ "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/parser": "^7.29.0",
- "@babel/types": "^7.29.0",
+ "@babel/parser": "^7.29.8",
+ "@babel/types": "^7.29.8",
"@jridgewell/gen-mapping": "^0.3.12",
"@jridgewell/trace-mapping": "^0.3.28",
"jsesc": "^3.0.2"
@@ -175,14 +175,14 @@
}
},
"node_modules/@babel/helper-compilation-targets": {
- "version": "7.28.6",
- "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz",
- "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz",
+ "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/compat-data": "^7.28.6",
- "@babel/helper-validator-option": "^7.27.1",
+ "@babel/compat-data": "^7.29.7",
+ "@babel/helper-validator-option": "^7.29.7",
"browserslist": "^4.24.0",
"lru-cache": "^5.1.1",
"semver": "^6.3.1"
@@ -192,9 +192,9 @@
}
},
"node_modules/@babel/helper-globals": {
- "version": "7.28.0",
- "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
- "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz",
+ "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -202,29 +202,29 @@
}
},
"node_modules/@babel/helper-module-imports": {
- "version": "7.28.6",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz",
- "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz",
+ "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/traverse": "^7.28.6",
- "@babel/types": "^7.28.6"
+ "@babel/traverse": "^7.29.7",
+ "@babel/types": "^7.29.7"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-module-transforms": {
- "version": "7.28.6",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz",
- "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz",
+ "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-module-imports": "^7.28.6",
- "@babel/helper-validator-identifier": "^7.28.5",
- "@babel/traverse": "^7.28.6"
+ "@babel/helper-module-imports": "^7.29.7",
+ "@babel/helper-validator-identifier": "^7.29.7",
+ "@babel/traverse": "^7.29.7"
},
"engines": {
"node": ">=6.9.0"
@@ -234,9 +234,9 @@
}
},
"node_modules/@babel/helper-string-parser": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
- "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
+ "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -244,9 +244,9 @@
}
},
"node_modules/@babel/helper-validator-identifier": {
- "version": "7.28.5",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
- "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
+ "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -254,9 +254,9 @@
}
},
"node_modules/@babel/helper-validator-option": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
- "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz",
+ "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -264,27 +264,27 @@
}
},
"node_modules/@babel/helpers": {
- "version": "7.29.2",
- "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz",
- "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz",
+ "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/template": "^7.28.6",
- "@babel/types": "^7.29.0"
+ "@babel/template": "^7.29.7",
+ "@babel/types": "^7.29.7"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/parser": {
- "version": "7.29.2",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz",
- "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==",
+ "version": "7.29.8",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz",
+ "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/types": "^7.29.0"
+ "@babel/types": "^7.29.8"
},
"bin": {
"parser": "bin/babel-parser.js"
@@ -303,33 +303,33 @@
}
},
"node_modules/@babel/template": {
- "version": "7.28.6",
- "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
- "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz",
+ "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/code-frame": "^7.28.6",
- "@babel/parser": "^7.28.6",
- "@babel/types": "^7.28.6"
+ "@babel/code-frame": "^7.29.7",
+ "@babel/parser": "^7.29.7",
+ "@babel/types": "^7.29.7"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/traverse": {
- "version": "7.29.0",
- "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz",
- "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==",
+ "version": "7.29.8",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz",
+ "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/code-frame": "^7.29.0",
- "@babel/generator": "^7.29.0",
- "@babel/helper-globals": "^7.28.0",
- "@babel/parser": "^7.29.0",
- "@babel/template": "^7.28.6",
- "@babel/types": "^7.29.0",
+ "@babel/code-frame": "^7.29.7",
+ "@babel/generator": "^7.29.8",
+ "@babel/helper-globals": "^7.29.7",
+ "@babel/parser": "^7.29.8",
+ "@babel/template": "^7.29.7",
+ "@babel/types": "^7.29.8",
"debug": "^4.3.1"
},
"engines": {
@@ -337,14 +337,14 @@
}
},
"node_modules/@babel/types": {
- "version": "7.29.0",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
- "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
+ "version": "7.29.8",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz",
+ "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-string-parser": "^7.27.1",
- "@babel/helper-validator-identifier": "^7.28.5"
+ "@babel/helper-string-parser": "^7.29.7",
+ "@babel/helper-validator-identifier": "^7.29.7"
},
"engines": {
"node": ">=6.9.0"
@@ -503,37 +503,6 @@
"node": ">=20.19.0"
}
},
- "node_modules/@emnapi/core": {
- "version": "1.9.1",
- "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.1.tgz",
- "integrity": "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==",
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "@emnapi/wasi-threads": "1.2.0",
- "tslib": "^2.4.0"
- }
- },
- "node_modules/@emnapi/runtime": {
- "version": "1.9.1",
- "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.1.tgz",
- "integrity": "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==",
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "tslib": "^2.4.0"
- }
- },
- "node_modules/@emnapi/wasi-threads": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz",
- "integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==",
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "tslib": "^2.4.0"
- }
- },
"node_modules/@eslint-community/eslint-utils": {
"version": "4.9.1",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
@@ -806,26 +775,10 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
- "node_modules/@napi-rs/wasm-runtime": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz",
- "integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==",
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "@emnapi/core": "^1.7.1",
- "@emnapi/runtime": "^1.7.1",
- "@tybys/wasm-util": "^0.10.1"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/Brooooooklyn"
- }
- },
"node_modules/@oxc-project/types": {
- "version": "0.122.0",
- "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.122.0.tgz",
- "integrity": "sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA==",
+ "version": "0.144.0",
+ "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.144.0.tgz",
+ "integrity": "sha512-nuhZIOLuI6TFQ32I/WnUx+SCPY7SdSKwgnFHydAuoS1+Z4BRcaP+RRJmGzl9lw+0OFF7UmaESf7KQRXaNLHypg==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/Boshen"
@@ -863,9 +816,9 @@
"license": "MIT"
},
"node_modules/@redocly/openapi-core": {
- "version": "1.34.15",
- "resolved": "https://registry.npmjs.org/@redocly/openapi-core/-/openapi-core-1.34.15.tgz",
- "integrity": "sha512-HAwCnNyKcs5XGQqms+9t7OdAPM/5TDstmhF+0i7tdCFato2QKuYIlyWETwkXd8c5zbltr1oB+6y9NTeQLr2d6Q==",
+ "version": "1.34.19",
+ "resolved": "https://registry.npmjs.org/@redocly/openapi-core/-/openapi-core-1.34.19.tgz",
+ "integrity": "sha512-o/0VgsBXgwcY1lyeqcVtSGdTQAPnVggo0fbFVPlxl5XVDKUcVH0OLRqt3CbkwByT5FU305E0iE0O7MzThjDblw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -874,7 +827,7 @@
"colorette": "1.4.0",
"https-proxy-agent": "7.0.6",
"js-levenshtein": "1.1.6",
- "js-yaml": "4.1.1",
+ "js-yaml": "4.3.1",
"minimatch": "5.1.9",
"pluralize": "8.0.0",
"yaml-ast-parser": "0.0.43"
@@ -895,9 +848,9 @@
}
},
"node_modules/@redocly/openapi-core/node_modules/brace-expansion": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz",
- "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==",
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz",
+ "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -932,9 +885,9 @@
}
},
"node_modules/@rolldown/binding-android-arm64": {
- "version": "1.0.0-rc.12",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.12.tgz",
- "integrity": "sha512-pv1y2Fv0JybcykuiiD3qBOBdz6RteYojRFY1d+b95WVuzx211CRh+ytI/+9iVyWQ6koTh5dawe4S/yRfOFjgaA==",
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.4.tgz",
+ "integrity": "sha512-jHC2cnyKz5xU2fhECtFl8OZ83cYNt13GZQD+0uMJ/X3o+ijmd56okHhTUwxVSHPx1IRVIJEZ1/1pPzeLCU6XKA==",
"cpu": [
"arm64"
],
@@ -948,9 +901,9 @@
}
},
"node_modules/@rolldown/binding-darwin-arm64": {
- "version": "1.0.0-rc.12",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.12.tgz",
- "integrity": "sha512-cFYr6zTG/3PXXF3pUO+umXxt1wkRK/0AYT8lDwuqvRC+LuKYWSAQAQZjCWDQpAH172ZV6ieYrNnFzVVcnSflAg==",
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.4.tgz",
+ "integrity": "sha512-Dc5mPD8F5F/FS8i01syd7FTF6yB2fVthH/TRkjwJkzUK6EpoxHtqvZQP5Zwq80/5z19TWYHIg1KOHboCgVx/aQ==",
"cpu": [
"arm64"
],
@@ -964,9 +917,9 @@
}
},
"node_modules/@rolldown/binding-darwin-x64": {
- "version": "1.0.0-rc.12",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.12.tgz",
- "integrity": "sha512-ZCsYknnHzeXYps0lGBz8JrF37GpE9bFVefrlmDrAQhOEi4IOIlcoU1+FwHEtyXGx2VkYAvhu7dyBf75EJQffBw==",
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.4.tgz",
+ "integrity": "sha512-fpDm4oBo6SqLvWUYCmFhdde3U9KH2fRNNMeAnAPAIwxRL345xutL0EtEUcuoxsoazdJGv/MuDBQHlCDrtbvqOg==",
"cpu": [
"x64"
],
@@ -980,9 +933,9 @@
}
},
"node_modules/@rolldown/binding-freebsd-x64": {
- "version": "1.0.0-rc.12",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.12.tgz",
- "integrity": "sha512-dMLeprcVsyJsKolRXyoTH3NL6qtsT0Y2xeuEA8WQJquWFXkEC4bcu1rLZZSnZRMtAqwtrF/Ib9Ddtpa/Gkge9Q==",
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.4.tgz",
+ "integrity": "sha512-rSJoreDE/HoIzoaib6MTp5jQtCTdMHKIvItAKT/ImS6Y6Ww76oUaeMyp4Vc/fAgd/ehji068IxetHXAnqUwN9A==",
"cpu": [
"x64"
],
@@ -996,9 +949,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
- "version": "1.0.0-rc.12",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.12.tgz",
- "integrity": "sha512-YqWjAgGC/9M1lz3GR1r1rP79nMgo3mQiiA+Hfo+pvKFK1fAJ1bCi0ZQVh8noOqNacuY1qIcfyVfP6HoyBRZ85Q==",
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.4.tgz",
+ "integrity": "sha512-/jm8OGHgn7oGaJu3i/qZI9spUGcJ+y/lk43ttQ/iO1tOd9NissG6o97bighBCiL+BKRngmcDuR6ikfwYdJmVuQ==",
"cpu": [
"arm"
],
@@ -1012,9 +965,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm64-gnu": {
- "version": "1.0.0-rc.12",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.12.tgz",
- "integrity": "sha512-/I5AS4cIroLpslsmzXfwbe5OmWvSsrFuEw3mwvbQ1kDxJ822hFHIx+vsN/TAzNVyepI/j/GSzrtCIwQPeKCLIg==",
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.4.tgz",
+ "integrity": "sha512-tIP06BeD9EqvECBrPZ+sqdPlYrT+aYaAiu1wYziVx5elRK/ftm33JxVDy2bXGbr6J0CrtirCkR87/X5a2euEng==",
"cpu": [
"arm64"
],
@@ -1028,9 +981,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm64-musl": {
- "version": "1.0.0-rc.12",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.12.tgz",
- "integrity": "sha512-V6/wZztnBqlx5hJQqNWwFdxIKN0m38p8Jas+VoSfgH54HSj9tKTt1dZvG6JRHcjh6D7TvrJPWFGaY9UBVOaWPw==",
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.4.tgz",
+ "integrity": "sha512-Ql1Q0EQqVThvn9VAVlwNzsUvbSFtCMGjLpRRi4pk5i7NZZ4n5ISiLMjHYtus4VQ2PvkSw24zyaCVsiS+sXPj1w==",
"cpu": [
"arm64"
],
@@ -1044,9 +997,9 @@
}
},
"node_modules/@rolldown/binding-linux-ppc64-gnu": {
- "version": "1.0.0-rc.12",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.12.tgz",
- "integrity": "sha512-AP3E9BpcUYliZCxa3w5Kwj9OtEVDYK6sVoUzy4vTOJsjPOgdaJZKFmN4oOlX0Wp0RPV2ETfmIra9x1xuayFB7g==",
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.4.tgz",
+ "integrity": "sha512-GjbjXD4XXfN19D0LZNbmiCBUoDiRACsYHr0yaIbbn8aFsXjHZifcYqu/W5Er5X2X990WjHXFrxarn5chzItorQ==",
"cpu": [
"ppc64"
],
@@ -1060,9 +1013,9 @@
}
},
"node_modules/@rolldown/binding-linux-s390x-gnu": {
- "version": "1.0.0-rc.12",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.12.tgz",
- "integrity": "sha512-nWwpvUSPkoFmZo0kQazZYOrT7J5DGOJ/+QHHzjvNlooDZED8oH82Yg67HvehPPLAg5fUff7TfWFHQS8IV1n3og==",
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.4.tgz",
+ "integrity": "sha512-p5WR0NOwaRmJ/B1b6IjEFLLivwEsf3PrdBIhRbhTCQisbo2SvHHpG4ELB/+FgQNnB88LTOF86upmJmbvZdQ2lw==",
"cpu": [
"s390x"
],
@@ -1076,9 +1029,9 @@
}
},
"node_modules/@rolldown/binding-linux-x64-gnu": {
- "version": "1.0.0-rc.12",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.12.tgz",
- "integrity": "sha512-RNrafz5bcwRy+O9e6P8Z/OCAJW/A+qtBczIqVYwTs14pf4iV1/+eKEjdOUta93q2TsT/FI0XYDP3TCky38LMAg==",
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.4.tgz",
+ "integrity": "sha512-4/GyVjmhR+Tc6HLJvwc1sOhPqAZtySiSMesOZyX6JQ5XBxoTDEMKQzvo07NIK6nTon/SivlZqvhzvuVBNQhObQ==",
"cpu": [
"x64"
],
@@ -1092,9 +1045,9 @@
}
},
"node_modules/@rolldown/binding-linux-x64-musl": {
- "version": "1.0.0-rc.12",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.12.tgz",
- "integrity": "sha512-Jpw/0iwoKWx3LJ2rc1yjFrj+T7iHZn2JDg1Yny1ma0luviFS4mhAIcd1LFNxK3EYu3DHWCps0ydXQ5i/rrJ2ig==",
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.4.tgz",
+ "integrity": "sha512-l9eeLsCNvPpmSXUej0etw/J1eqV0Jj1D5G/xG6YTijmE6dkv6E2QezgWbTfQk63v952DPqrjOCoiqxq7Bw0YUQ==",
"cpu": [
"x64"
],
@@ -1108,9 +1061,9 @@
}
},
"node_modules/@rolldown/binding-openharmony-arm64": {
- "version": "1.0.0-rc.12",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.12.tgz",
- "integrity": "sha512-vRugONE4yMfVn0+7lUKdKvN4D5YusEiPilaoO2sgUWpCvrncvWgPMzK00ZFFJuiPgLwgFNP5eSiUlv2tfc+lpA==",
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.4.tgz",
+ "integrity": "sha512-e0F355MSTMm3+UOqtV3L24gFUp2N5m1f8L/7d56deik6va+AXdrt9F8LbzGpeWGWRbZEDq4m8NVnJDeBtf9DZg==",
"cpu": [
"arm64"
],
@@ -1123,26 +1076,10 @@
"node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/@rolldown/binding-wasm32-wasi": {
- "version": "1.0.0-rc.12",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.12.tgz",
- "integrity": "sha512-ykGiLr/6kkiHc0XnBfmFJuCjr5ZYKKofkx+chJWDjitX+KsJuAmrzWhwyOMSHzPhzOHOy7u9HlFoa5MoAOJ/Zg==",
- "cpu": [
- "wasm32"
- ],
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "@napi-rs/wasm-runtime": "^1.1.1"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
"node_modules/@rolldown/binding-win32-arm64-msvc": {
- "version": "1.0.0-rc.12",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.12.tgz",
- "integrity": "sha512-5eOND4duWkwx1AzCxadcOrNeighiLwMInEADT0YM7xeEOOFcovWZCq8dadXgcRHSf3Ulh1kFo/qvzoFiCLOL1Q==",
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.4.tgz",
+ "integrity": "sha512-AWLi0uBRYh6QlE7OKhiz+phZC0qwtij2QZmhmOdsLdFn64m7oMpooE9ICE3lhm9xMb4SpDo2WbHcxX1iFLFtqw==",
"cpu": [
"arm64"
],
@@ -1156,9 +1093,9 @@
}
},
"node_modules/@rolldown/binding-win32-x64-msvc": {
- "version": "1.0.0-rc.12",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.12.tgz",
- "integrity": "sha512-PyqoipaswDLAZtot351MLhrlrh6lcZPo2LSYE+VDxbVk24LVKAGOuE4hb8xZQmrPAuEtTZW8E6D2zc5EUZX4Lw==",
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.4.tgz",
+ "integrity": "sha512-UwSDJOg3dqCAejWdxclJjCsh3Qq4vLYMDxmyHqo1btz3stK2VqgwNd3mm5tuIwzSlGIQ/1H9Hr+Zn09mrezNqQ==",
"cpu": [
"x64"
],
@@ -1873,16 +1810,16 @@
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
- "version": "5.0.5",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
- "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
+ "version": "5.0.9",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz",
+ "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^4.0.2"
},
"engines": {
- "node": "18 || 20 || >=22"
+ "node": "20 || >=22"
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
@@ -2231,13 +2168,13 @@
"license": "MIT"
},
"node_modules/axios": {
- "version": "1.16.1",
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.1.tgz",
- "integrity": "sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A==",
+ "version": "1.19.0",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.19.0.tgz",
+ "integrity": "sha512-ht/iuYZXEjFxLH/Hkezgd7m6JKlHHXEUSneaDz8uZe1Gj5QZtCnpyDsckvAiEnT89OEbCLmnte4R4sn7P0EKFw==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.16.0",
- "form-data": "^4.0.5",
+ "form-data": "^4.0.6",
"https-proxy-agent": "^5.0.1",
"proxy-from-env": "^2.1.0"
}
@@ -2259,9 +2196,9 @@
}
},
"node_modules/baseline-browser-mapping": {
- "version": "2.10.11",
- "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.11.tgz",
- "integrity": "sha512-DAKrHphkJyiGuau/cFieRYhcTFeK/lBuD++C7cZ6KZHbMhBrisoi+EvhQ5RZrIfV5qwsW8kgQ07JIC+MDJRAhg==",
+ "version": "2.11.15",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.15.tgz",
+ "integrity": "sha512-FwMjJJ7HnyZpWe+oWxegG0fezZyBZUagI5LZEoO3GCbtbKNwRfMH9Ue5d5v01PNePBy1QSfPSDTTeVL0Hb9EzA==",
"dev": true,
"license": "Apache-2.0",
"bin": {
@@ -2282,9 +2219,9 @@
}
},
"node_modules/brace-expansion": {
- "version": "1.1.13",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz",
- "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==",
+ "version": "1.1.18",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz",
+ "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2293,9 +2230,9 @@
}
},
"node_modules/browserslist": {
- "version": "4.28.1",
- "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz",
- "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==",
+ "version": "4.28.8",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.8.tgz",
+ "integrity": "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==",
"dev": true,
"funding": [
{
@@ -2313,11 +2250,11 @@
],
"license": "MIT",
"dependencies": {
- "baseline-browser-mapping": "^2.9.0",
- "caniuse-lite": "^1.0.30001759",
- "electron-to-chromium": "^1.5.263",
- "node-releases": "^2.0.27",
- "update-browserslist-db": "^1.2.0"
+ "baseline-browser-mapping": "^2.11.12",
+ "caniuse-lite": "^1.0.30001809",
+ "electron-to-chromium": "^1.5.402",
+ "node-releases": "^2.0.53",
+ "update-browserslist-db": "^1.3.0"
},
"bin": {
"browserslist": "cli.js"
@@ -2350,9 +2287,9 @@
}
},
"node_modules/caniuse-lite": {
- "version": "1.0.30001781",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001781.tgz",
- "integrity": "sha512-RdwNCyMsNBftLjW6w01z8bKEvT6e/5tpPVEgtn22TiLGlstHOVecsX2KHFkD5e/vRnIE4EGzpuIODb3mtswtkw==",
+ "version": "1.0.30001809",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001809.tgz",
+ "integrity": "sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ==",
"dev": true,
"funding": [
{
@@ -2636,9 +2573,9 @@
"peer": true
},
"node_modules/dompurify": {
- "version": "3.4.7",
- "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.7.tgz",
- "integrity": "sha512-2jBxDJY4RR06tQNy4w5FlFH7kfxsQZlufd0sbv+chfHCxeJwrFw2baUDsSwvBISD4K4RDbd0PTfy3uNXsR6siA==",
+ "version": "3.4.13",
+ "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.13.tgz",
+ "integrity": "sha512-2vmYIoqjze2d+kakP8S/nS5shfsl587kzwEjcGlTdiksUVgFHnFCsLYDVj/JNqJVOQZGSYBTmuycv0PodwmnMQ==",
"license": "(MPL-2.0 OR Apache-2.0)",
"optional": true,
"optionalDependencies": {
@@ -2660,9 +2597,9 @@
}
},
"node_modules/electron-to-chromium": {
- "version": "1.5.328",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.328.tgz",
- "integrity": "sha512-QNQ5l45DzYytThO21403XN3FvK0hOkWDG8viNf6jqS42msJ8I4tGDSpBCgvDRRPnkffafiwAym2X2eHeGD2V0w==",
+ "version": "1.5.409",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.409.tgz",
+ "integrity": "sha512-ChI4N44d0B4A6C8prnNjMOaGgE59fUyEVYcRYm2XEXIjMbbvF5i9UL1cblDbpGqiU0uS8FE8UcKxqZqTXdmzbQ==",
"dev": true,
"license": "ISC"
},
@@ -2718,9 +2655,9 @@
"license": "MIT"
},
"node_modules/es-object-atoms": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
- "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz",
+ "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0"
@@ -3114,16 +3051,16 @@
}
},
"node_modules/form-data": {
- "version": "4.0.5",
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
- "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz",
+ "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
- "hasown": "^2.0.2",
- "mime-types": "^2.1.12"
+ "hasown": "^2.0.4",
+ "mime-types": "^2.1.35"
},
"engines": {
"node": ">= 6"
@@ -3281,9 +3218,9 @@
}
},
"node_modules/hasown": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz",
- "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==",
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
+ "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
@@ -3492,10 +3429,20 @@
"license": "MIT"
},
"node_modules/js-yaml": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
- "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz",
+ "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==",
"dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/puzrin"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/nodeca"
+ }
+ ],
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1"
@@ -4012,9 +3959,9 @@
"license": "MIT"
},
"node_modules/nanoid": {
- "version": "3.3.11",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
- "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "version": "3.3.18",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz",
+ "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==",
"funding": [
{
"type": "github",
@@ -4037,11 +3984,14 @@
"license": "MIT"
},
"node_modules/node-releases": {
- "version": "2.0.36",
- "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz",
- "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==",
+ "version": "2.0.53",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.53.tgz",
+ "integrity": "sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
},
"node_modules/obug": {
"version": "2.1.1",
@@ -4229,9 +4179,9 @@
"license": "ISC"
},
"node_modules/picomatch": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
- "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz",
+ "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
"license": "MIT",
"engines": {
"node": ">=12"
@@ -4251,9 +4201,9 @@
}
},
"node_modules/postcss": {
- "version": "8.5.8",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz",
- "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==",
+ "version": "8.5.26",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz",
+ "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==",
"funding": [
{
"type": "opencollective",
@@ -4270,7 +4220,7 @@
],
"license": "MIT",
"dependencies": {
- "nanoid": "^3.3.11",
+ "nanoid": "^3.3.17",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
@@ -4393,9 +4343,9 @@
"peer": true
},
"node_modules/react-router": {
- "version": "7.15.0",
- "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.15.0.tgz",
- "integrity": "sha512-HW9vYwuM8f4yx66Izy8xfrzCM+SBJluoZcCbww9A1TySax11S5Vgw6fi3ZjMONw9J4gQwngL7PzkyIpJJpJ7RQ==",
+ "version": "7.18.2",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.2.tgz",
+ "integrity": "sha512-aUVMjFm3GAPTTZL7oYr5E7ETiqfQCHRLH+B+5afnICvf0r7kkK4eR6SMuwbSTJw/7t+12khT/Kahij49fqOCIg==",
"license": "MIT",
"dependencies": {
"cookie": "^1.0.1",
@@ -4415,12 +4365,12 @@
}
},
"node_modules/react-router-dom": {
- "version": "7.15.0",
- "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.15.0.tgz",
- "integrity": "sha512-VcrVg64Fo8nwBvDscajG8gRTLIuTC6N50nb22l2HOOV4PTOHgoGp8mUjy9wLiHYoYTSYI36tUnXZgasSRFZorQ==",
+ "version": "7.18.2",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.18.2.tgz",
+ "integrity": "sha512-AIKJ/jgGlFb3EbfCXk5Gzshiwt+l3mqbCrNjmEWMMjqQxNJ3svBa6bgzFyCC2Sw3RA0VWF1kg3uQf2OFhxb8hw==",
"license": "MIT",
"dependencies": {
- "react-router": "7.15.0"
+ "react-router": "7.18.2"
},
"engines": {
"node": ">=20.0.0"
@@ -4482,13 +4432,13 @@
}
},
"node_modules/rolldown": {
- "version": "1.0.0-rc.12",
- "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.12.tgz",
- "integrity": "sha512-yP4USLIMYrwpPHEFB5JGH1uxhcslv6/hL0OyvTuY+3qlOSJvZ7ntYnoWpehBxufkgN0cvXxppuTu5hHa/zPh+A==",
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.4.tgz",
+ "integrity": "sha512-rSr7irW0K7QRWzjdJXqZowkcRdDtjRduh43rBltnVKd0VFq839l1lJoDvGJb6gl7+4rTTCrPWu+YfujUL8Ug7w==",
"license": "MIT",
"dependencies": {
- "@oxc-project/types": "=0.122.0",
- "@rolldown/pluginutils": "1.0.0-rc.12"
+ "@oxc-project/types": "=0.144.0",
+ "@rolldown/pluginutils": "^1.0.0"
},
"bin": {
"rolldown": "bin/cli.mjs"
@@ -4497,27 +4447,26 @@
"node": "^20.19.0 || >=22.12.0"
},
"optionalDependencies": {
- "@rolldown/binding-android-arm64": "1.0.0-rc.12",
- "@rolldown/binding-darwin-arm64": "1.0.0-rc.12",
- "@rolldown/binding-darwin-x64": "1.0.0-rc.12",
- "@rolldown/binding-freebsd-x64": "1.0.0-rc.12",
- "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.12",
- "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.12",
- "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.12",
- "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.12",
- "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.12",
- "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.12",
- "@rolldown/binding-linux-x64-musl": "1.0.0-rc.12",
- "@rolldown/binding-openharmony-arm64": "1.0.0-rc.12",
- "@rolldown/binding-wasm32-wasi": "1.0.0-rc.12",
- "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.12",
- "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.12"
+ "@rolldown/binding-android-arm64": "1.2.4",
+ "@rolldown/binding-darwin-arm64": "1.2.4",
+ "@rolldown/binding-darwin-x64": "1.2.4",
+ "@rolldown/binding-freebsd-x64": "1.2.4",
+ "@rolldown/binding-linux-arm-gnueabihf": "1.2.4",
+ "@rolldown/binding-linux-arm64-gnu": "1.2.4",
+ "@rolldown/binding-linux-arm64-musl": "1.2.4",
+ "@rolldown/binding-linux-ppc64-gnu": "1.2.4",
+ "@rolldown/binding-linux-s390x-gnu": "1.2.4",
+ "@rolldown/binding-linux-x64-gnu": "1.2.4",
+ "@rolldown/binding-linux-x64-musl": "1.2.4",
+ "@rolldown/binding-openharmony-arm64": "1.2.4",
+ "@rolldown/binding-win32-arm64-msvc": "1.2.4",
+ "@rolldown/binding-win32-x64-msvc": "1.2.4"
}
},
"node_modules/rolldown/node_modules/@rolldown/pluginutils": {
- "version": "1.0.0-rc.12",
- "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.12.tgz",
- "integrity": "sha512-HHMwmarRKvoFsJorqYlFeFRzXZqCt2ETQlEDOb9aqssrnVBB1/+xgTGtuTrIk5vzLNX1MjMtTf7W9z3tsSbrxw==",
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz",
+ "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==",
"license": "MIT"
},
"node_modules/saxes": {
@@ -4720,13 +4669,13 @@
}
},
"node_modules/tinyglobby": {
- "version": "0.2.15",
- "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
- "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
+ "version": "0.2.17",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
+ "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
"license": "MIT",
"dependencies": {
"fdir": "^6.5.0",
- "picomatch": "^4.0.3"
+ "picomatch": "^4.0.4"
},
"engines": {
"node": ">=12.0.0"
@@ -4876,9 +4825,9 @@
}
},
"node_modules/undici": {
- "version": "7.27.0",
- "resolved": "https://registry.npmjs.org/undici/-/undici-7.27.0.tgz",
- "integrity": "sha512-+t2Z/GwkZQDtu00813aP66ygViGtPHKhhoFZpQKpKrE+9jIgES+Zw+mFNaDWOVRKiuJjuqKHzD3B1sfGg8+ZOQ==",
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz",
+ "integrity": "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -4893,9 +4842,9 @@
"license": "MIT"
},
"node_modules/update-browserslist-db": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
- "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.1.tgz",
+ "integrity": "sha512-ZZ61DsRsOnakl74HAmp3oSN4aXUmEWXf+i/yv0h7tIBfICc3VdrFErQKUUKPgu3AMsTUMbcongALEN4l6GSUrQ==",
"dev": true,
"funding": [
{
@@ -4950,16 +4899,16 @@
}
},
"node_modules/vite": {
- "version": "8.0.3",
- "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.3.tgz",
- "integrity": "sha512-B9ifbFudT1TFhfltfaIPgjo9Z3mDynBTJSUYxTjOQruf/zHH+ezCQKcoqO+h7a9Pw9Nm/OtlXAiGT1axBgwqrQ==",
+ "version": "8.2.1",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.1.tgz",
+ "integrity": "sha512-EU/eS7BH3XROHh2YnBefjM6DBKA6ZeMZEYQbj7NLWg5wHYlhB8B/Mayd5XsgWq+NFYccDOTemRpdETWR6Ka/lw==",
"license": "MIT",
"dependencies": {
- "lightningcss": "^1.32.0",
- "picomatch": "^4.0.4",
- "postcss": "^8.5.8",
- "rolldown": "1.0.0-rc.12",
- "tinyglobby": "^0.2.15"
+ "lightningcss": "^1.33.0",
+ "picomatch": "^4.0.5",
+ "postcss": "^8.5.25",
+ "rolldown": "~1.2.1",
+ "tinyglobby": "^0.2.17"
},
"bin": {
"vite": "bin/vite.js"
@@ -4975,8 +4924,8 @@
},
"peerDependencies": {
"@types/node": "^20.19.0 || >=22.12.0",
- "@vitejs/devtools": "^0.1.0",
- "esbuild": "^0.27.0",
+ "@vitejs/devtools": "^0.4.0",
+ "esbuild": "^0.27.0 || ^0.28.0",
"jiti": ">=1.21.0",
"less": "^4.0.0",
"sass": "^1.70.0",
@@ -5026,6 +4975,255 @@
}
}
},
+ "node_modules/vite/node_modules/lightningcss": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz",
+ "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==",
+ "license": "MPL-2.0",
+ "dependencies": {
+ "detect-libc": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "lightningcss-android-arm64": "1.33.0",
+ "lightningcss-darwin-arm64": "1.33.0",
+ "lightningcss-darwin-x64": "1.33.0",
+ "lightningcss-freebsd-x64": "1.33.0",
+ "lightningcss-linux-arm-gnueabihf": "1.33.0",
+ "lightningcss-linux-arm64-gnu": "1.33.0",
+ "lightningcss-linux-arm64-musl": "1.33.0",
+ "lightningcss-linux-x64-gnu": "1.33.0",
+ "lightningcss-linux-x64-musl": "1.33.0",
+ "lightningcss-win32-arm64-msvc": "1.33.0",
+ "lightningcss-win32-x64-msvc": "1.33.0"
+ }
+ },
+ "node_modules/vite/node_modules/lightningcss-android-arm64": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz",
+ "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/vite/node_modules/lightningcss-darwin-arm64": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz",
+ "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/vite/node_modules/lightningcss-darwin-x64": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz",
+ "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/vite/node_modules/lightningcss-freebsd-x64": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz",
+ "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/vite/node_modules/lightningcss-linux-arm-gnueabihf": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz",
+ "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/vite/node_modules/lightningcss-linux-arm64-gnu": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz",
+ "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/vite/node_modules/lightningcss-linux-arm64-musl": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz",
+ "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/vite/node_modules/lightningcss-linux-x64-gnu": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz",
+ "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/vite/node_modules/lightningcss-linux-x64-musl": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz",
+ "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/vite/node_modules/lightningcss-win32-arm64-msvc": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz",
+ "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/vite/node_modules/lightningcss-win32-x64-msvc": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz",
+ "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
"node_modules/vitest": {
"version": "4.1.8",
"resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.8.tgz",
diff --git a/frontend/package.json b/frontend/package.json
index ddd1484a..a121ff8c 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -7,6 +7,7 @@
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
+ "type-check": "tsc -b",
"preview": "vite preview",
"openapi": "openapi-typescript ../backend/openapi.json -o src/shared/api/generated.ts",
"test": "vitest run",
diff --git a/frontend/src/features/interview/lib/useTypewriter.ts b/frontend/src/features/interview/lib/useTypewriter.ts
index c6ee7509..792b2721 100644
--- a/frontend/src/features/interview/lib/useTypewriter.ts
+++ b/frontend/src/features/interview/lib/useTypewriter.ts
@@ -5,7 +5,11 @@ import { useEffect, useRef, useState } from 'react'
export function useTypewriter(fullText: string, enabled: boolean): string {
const [count, setCount] = useState(enabled ? 0 : fullText.length)
const targetRef = useRef(fullText)
- targetRef.current = fullText
+ // 렌더 중 ref 대입은 react-hooks/refs 위반이라 커밋 후 이펙트에서 갱신한다.
+ // 아래 타자기 이펙트보다 먼저 선언해, 같은 커밋에서 ref 가 항상 먼저 최신화되게 한다.
+ useEffect(() => {
+ targetRef.current = fullText
+ }, [fullText])
useEffect(() => {
if (!enabled) {
diff --git a/frontend/src/features/interview/model/useInterviewSocket.ts b/frontend/src/features/interview/model/useInterviewSocket.ts
index 97e5b7cb..52daaa3a 100644
--- a/frontend/src/features/interview/model/useInterviewSocket.ts
+++ b/frontend/src/features/interview/model/useInterviewSocket.ts
@@ -26,8 +26,13 @@ export function useInterviewSocket({
const socketRef = useRef(null)
const onEventRef = useRef(onEvent)
const onStatusRef = useRef(onStatusChange)
- onEventRef.current = onEvent
- onStatusRef.current = onStatusChange
+ // 콜백은 매 렌더 새로 만들어지므로 ref 로 최신 것을 넘긴다(연결 이펙트를 재실행시키지 않기 위해).
+ // 렌더 중 대입은 react-hooks/refs 위반이라 커밋 후 이펙트에서 갱신하고, 아래 연결 이펙트보다
+ // 먼저 선언해 같은 커밋에서 ref 가 항상 먼저 최신화되게 한다.
+ useEffect(() => {
+ onEventRef.current = onEvent
+ onStatusRef.current = onStatusChange
+ }, [onEvent, onStatusChange])
useEffect(() => {
if (!enabled || sessionId == null) return