diff --git a/backend/app/schemas/github_link.py b/backend/app/schemas/github_link.py index 68bc5050..66b6d6f2 100644 --- a/backend/app/schemas/github_link.py +++ b/backend/app/schemas/github_link.py @@ -1,6 +1,6 @@ """GitHub 連携 API 用の Pydantic スキーマ。""" -from typing import Dict, Optional +from typing import Dict, List, Optional from pydantic import BaseModel, Field @@ -24,8 +24,9 @@ class ContributionDay(BaseModel): class ContributionCalendar(BaseModel): - """直近1年のコントリビューションカレンダー(GitHub の緑の四角)。""" + """1年分のコントリビューションカレンダー(GitHub の緑の四角)。""" + year: int = Field(description="このカレンダーが対象とする西暦年") total_contributions: int = Field(description="期間内のコントリビューション総数") weeks: list[list[ContributionDay]] = Field( default_factory=list, @@ -42,21 +43,9 @@ class GitHubLinkResponse(BaseModel): default_factory=dict, description="言語ごとのバイト数(GitHub linguist ベース)", ) - detected_frameworks: Dict[str, int] = Field( - default_factory=dict, - description="依存関係から検出したフレームワーク名 → 使用リポジトリ数", - ) - detected_devtools: Dict[str, int] = Field( - default_factory=dict, - description="ルートファイルから検出した DevTools 名 → 使用リポジトリ数", - ) - detected_infras: Dict[str, int] = Field( - default_factory=dict, - description="ルートファイルから検出したインフラツール名 → 使用リポジトリ数", - ) - contribution_calendar: Optional[ContributionCalendar] = Field( - default=None, - description="直近1年のコントリビューションカレンダー(取得失敗時は None)", + contribution_calendars: List[ContributionCalendar] = Field( + default_factory=list, + description="年ごとのコントリビューションカレンダー(新しい年順。取得失敗時は空配列)", ) diff --git a/backend/app/services/intelligence/github/__init__.py b/backend/app/services/intelligence/github/__init__.py index ea6aafb8..c621f12f 100644 --- a/backend/app/services/intelligence/github/__init__.py +++ b/backend/app/services/intelligence/github/__init__.py @@ -2,5 +2,6 @@ GitHub 分析サブパッケージ。 api_client: GitHub REST API 呼び出し -repo_analyzer: 言語 ratio 算出・フレームワーク検出・依存関係パース +repo_analyzer: 言語 ratio 算出 +contributions: コントリビューションカレンダー取得(GraphQL) """ diff --git a/backend/app/services/intelligence/github/api_client.py b/backend/app/services/intelligence/github/api_client.py index 5a686f91..7c84c4b0 100644 --- a/backend/app/services/intelligence/github/api_client.py +++ b/backend/app/services/intelligence/github/api_client.py @@ -1,13 +1,13 @@ """ GitHub REST API 呼び出しを担うモジュール。 -リポジトリ一覧・言語情報・ファイル内容取得などの純粋な API 通信を行う。 +リポジトリ一覧・言語情報などの純粋な API 通信を行う。 """ import logging import re import time -from typing import Any, Dict, List, Optional +from typing import Any, Dict, List import httpx @@ -54,49 +54,6 @@ def _is_valid_owner_repo(owner: str, repo: str) -> bool: # これより小さいリポジトリはスキップ(バイト) _REPO_MIN_SIZE_BYTES = 1024 -# 特定のスキルを示すルートファイル/ディレクトリ -_INTERESTING_ROOT_FILES = { - "Dockerfile", - "docker-compose.yml", - "docker-compose.yaml", - "package.json", - "requirements.txt", - "pyproject.toml", - "pom.xml", - "go.mod", - "Makefile", - "Gemfile", - ".github", - "terraform", - ".terraform", - "infra", - "k8s", - "kubernetes", - "helm", - "cdk.json", - "pulumi.yaml", - "pulumi.yml", - "Jenkinsfile", - ".gitlab-ci.yml", - ".circleci", -} - -# モノレポ構成でよく使われるサブディレクトリ内の依存関係ファイル -# ルートに dep ファイルがない Python/Node プロジェクトのフォールバック探索パス -_SUBDIRECTORY_DEP_FILES = [ - "backend/requirements.txt", - "backend/pyproject.toml", - "server/requirements.txt", - "server/pyproject.toml", - "api/requirements.txt", - "api/pyproject.toml", - "src/requirements.txt", - "app/requirements.txt", - "frontend/package.json", - "client/package.json", - "web/package.json", -] - class GitHubUserNotFoundError(Exception): """GitHub ユーザーが見つからない場合の例外。""" @@ -206,101 +163,3 @@ async def fetch_languages( except httpx.HTTPError: logger.warning("Failed to fetch languages for %s/%s", owner, repo) return {} - - -async def fetch_root_files( - client: httpx.AsyncClient, - owner: str, - repo: str, -) -> List[str]: - """リポジトリのルートレベルの注目すべきファイル名/ディレクトリ名を取得する。""" - if not _is_valid_owner_repo(owner, repo): - logger.warning("不正な owner/repo をスキップ: %s/%s", owner, repo) - return [] - try: - resp = await client.get(f"/repos/{owner}/{repo}/contents/") - if resp.status_code in (403, 404): - return [] - resp.raise_for_status() - items: List[Any] = resp.json() - if not isinstance(items, list): - return [] - return [ - item["name"] - for item in items - if isinstance(item, dict) - and "name" in item - and item["name"] in _INTERESTING_ROOT_FILES - ] - except httpx.HTTPError: - logger.warning("Failed to fetch contents for %s/%s", owner, repo) - return [] - - -async def fetch_file_content( - client: httpx.AsyncClient, - owner: str, - repo: str, - path: str, -) -> Optional[str]: - """リポジトリから生のファイルコンテンツをダウンロードする。""" - if not _is_valid_owner_repo(owner, repo): - logger.warning("不正な owner/repo をスキップ: %s/%s", owner, repo) - return None - try: - resp = await client.get( - f"/repos/{owner}/{repo}/contents/{path}", - headers={"Accept": "application/vnd.github.raw+json"}, - ) - if resp.status_code in (403, 404): - return None - resp.raise_for_status() - return resp.text - except httpx.HTTPError: - logger.warning( - "Failed to fetch %s for %s/%s", - path, - owner, - repo, - ) - return None - - -async def fetch_subdirectory_dep_files( - client: httpx.AsyncClient, - owner: str, - repo: str, - root_files: List[str], -) -> List[str]: - """ルートに依存関係ファイルがないモノレポ向けに、サブディレクトリの dep ファイルパスを返す。 - - ルートにすでに Python/Node の dep ファイルがある場合はスキップする。 - 見つかったパスのリストを返す(ファイル名ではなく相対パス)。 - """ - _PYTHON_DEP_FILES = {"requirements.txt", "pyproject.toml"} - _NODE_DEP_FILES = {"package.json"} - - has_python_deps = bool(_PYTHON_DEP_FILES & set(root_files)) - has_node_deps = bool(_NODE_DEP_FILES & set(root_files)) - - if has_python_deps and has_node_deps: - return [] - - found: List[str] = [] - for path in _SUBDIRECTORY_DEP_FILES: - filename = path.split("/")[-1] - if filename in _PYTHON_DEP_FILES and has_python_deps: - continue - if filename in _NODE_DEP_FILES and has_node_deps: - continue - content = await fetch_file_content(client, owner, repo, path) - if content is not None: - found.append(path) - # Python か Node のどちらかで1ファイル見つかれば探索終了 - if filename in _PYTHON_DEP_FILES: - has_python_deps = True - elif filename in _NODE_DEP_FILES: - has_node_deps = True - if has_python_deps and has_node_deps: - break - return found diff --git a/backend/app/services/intelligence/github/contributions.py b/backend/app/services/intelligence/github/contributions.py index 24bb794b..ebc2cb45 100644 --- a/backend/app/services/intelligence/github/contributions.py +++ b/backend/app/services/intelligence/github/contributions.py @@ -5,8 +5,12 @@ GraphQL から取得する。`read:user` スコープのトークンで公開プロフィールの コントリビューション情報を読み取れる。 +``contributionYears`` で貢献のある全年を取得し、年ごとに ``from``/``to`` を指定して +カレンダーを取得する(GraphQL の contributionsCollection は1リクエスト最大1年のため)。 + このモジュールは連携パイプラインの**補助処理**であり、取得失敗時は -``logger.warning`` を残して ``None`` を返す(主処理のリポジトリ解析を巻き添えにしない)。 +``logger.warning`` を残して空配列(または該当年のスキップ)で継続する +(主処理のリポジトリ解析を巻き添えにしない)。 """ import logging @@ -30,10 +34,22 @@ "FOURTH_QUARTILE": 4, } -_CONTRIBUTION_QUERY = """ +# 貢献のある年の一覧を取得するクエリ。 +_YEARS_QUERY = """ query($login: String!) { user(login: $login) { contributionsCollection { + contributionYears + } + } +} +""" + +# 指定期間(最大1年)のカレンダーを取得するクエリ。 +_CALENDAR_QUERY = """ +query($login: String!, $from: DateTime!, $to: DateTime!) { + user(login: $login) { + contributionsCollection(from: $from, to: $to) { contributionCalendar { totalContributions weeks { @@ -50,37 +66,59 @@ """ -async def fetch_contribution_calendar( +async def fetch_all_contribution_calendars( username: str, token: str, -) -> Optional[ContributionCalendar]: - """GitHub のコントリビューションカレンダー(直近1年)を取得する。 +) -> tuple[bool, list[ContributionCalendar]]: + """貢献のある全年のコントリビューションカレンダーを新しい年順で取得する。 取得に失敗した場合(認証エラー・レート制限・ネットワーク障害・GraphQL エラー等)は - ``logger.warning`` を残して ``None`` を返す。連携自体は継続させる補助処理のため、 - 例外を送出しない。 + ``logger.warning`` を残して取得できた範囲(年単位)を返す。連携自体は継続させる + 補助処理のため、例外を送出しない。 Args: username: 対象 GitHub ユーザー名。 token: 認証用アクセストークン(復号済み)。 Returns: - ``ContributionCalendar`` または取得失敗時は ``None``。 + ``(success, calendars)`` のタプル。``success`` は年一覧の取得に成功したか。 + 認証エラー等で取得自体に失敗したときのみ ``False``。貢献年が無いだけの場合は + ``True`` で空配列を返す(呼び出し側が「失敗」と「貢献なし」を区別できるようにする)。 + ``calendars`` は年ごとの ``ContributionCalendar``(降順)。 """ headers = { "Authorization": f"Bearer {token}", "Accept": "application/vnd.github+json", } + async with httpx.AsyncClient(timeout=30.0) as client: + years = await _fetch_contribution_years(client, username, headers) + if years is None: + # 年一覧の取得自体に失敗(貢献ゼロとは区別する) + return False, [] + + calendars: list[ContributionCalendar] = [] + # 新しい年順(降順)で取得する + for year in sorted(years, reverse=True): + calendar = await _fetch_calendar_for_year(client, username, year, headers) + if calendar is not None: + calendars.append(calendar) + return True, calendars + + +async def _post_graphql( + client: httpx.AsyncClient, + headers: dict, + variables: dict, + query: str, + username: str, +) -> Optional[dict]: + """GraphQL を実行し ``data.user`` を返す。失敗時は warning を残して ``None``。""" try: - async with httpx.AsyncClient(timeout=30.0) as client: - resp = await client.post( - _GRAPHQL_ENDPOINT, - headers=headers, - json={ - "query": _CONTRIBUTION_QUERY, - "variables": {"login": username}, - }, - ) + resp = await client.post( + _GRAPHQL_ENDPOINT, + headers=headers, + json={"query": query, "variables": variables}, + ) except (httpx.TimeoutException, httpx.ConnectError, httpx.HTTPError) as exc: logger.warning( "コントリビューション取得でネットワーク障害 (username=%s): %s", @@ -115,26 +153,71 @@ async def fetch_contribution_calendar( if not user: logger.warning("コントリビューション対象ユーザーが見つからない (username=%s)", username) return None + return user + + +async def _fetch_contribution_years( + client: httpx.AsyncClient, + username: str, + headers: dict, +) -> Optional[list[int]]: + """貢献のある年の一覧を取得する。 + + 取得失敗(認証エラー・ネットワーク障害・応答破損)時は ``None`` を返し、 + 貢献年が 1 件も無い正常応答とは区別する。成功時は年のリスト(空もあり得る)。 + """ + user = await _post_graphql( + client, headers, {"login": username}, _YEARS_QUERY, username + ) + if not user: + return None + years = (user.get("contributionsCollection") or {}).get("contributionYears") + if not isinstance(years, list): + logger.warning("contributionYears が取得できない (username=%s)", username) + return None + return [y for y in years if isinstance(y, int)] + + +async def _fetch_calendar_for_year( + client: httpx.AsyncClient, + username: str, + year: int, + headers: dict, +) -> Optional[ContributionCalendar]: + """指定年のカレンダーを取得する。失敗時は warning を残して ``None``。""" + variables = { + "login": username, + "from": f"{year}-01-01T00:00:00Z", + "to": f"{year}-12-31T23:59:59Z", + } + user = await _post_graphql( + client, headers, variables, _CALENDAR_QUERY, username + ) + if not user: + return None calendar_raw = (user.get("contributionsCollection") or {}).get( "contributionCalendar" ) if not calendar_raw: - logger.warning("コントリビューションカレンダーが空 (username=%s)", username) + logger.warning( + "コントリビューションカレンダーが空 (username=%s, year=%s)", username, year + ) return None try: - return _parse_calendar(calendar_raw) + return _parse_calendar(calendar_raw, year) except Exception as exc: logger.warning( - "コントリビューションカレンダーの解析に失敗 (username=%s): %s", + "コントリビューションカレンダーの解析に失敗 (username=%s, year=%s): %s", username, + year, exc, ) return None -def _parse_calendar(calendar_raw: dict) -> ContributionCalendar: +def _parse_calendar(calendar_raw: dict, year: int) -> ContributionCalendar: """GraphQL の contributionCalendar を ``ContributionCalendar`` に変換する。 不正な day エントリ(date 欠落・型不一致など)は黙って読み飛ばし、 @@ -160,6 +243,7 @@ def _parse_calendar(calendar_raw: dict) -> ContributionCalendar: total = calendar_raw.get("totalContributions", 0) return ContributionCalendar( + year=year, total_contributions=total if isinstance(total, int) else 0, weeks=weeks, ) diff --git a/backend/app/services/intelligence/github/repo_analyzer.py b/backend/app/services/intelligence/github/repo_analyzer.py index efd35d27..e1248355 100644 --- a/backend/app/services/intelligence/github/repo_analyzer.py +++ b/backend/app/services/intelligence/github/repo_analyzer.py @@ -1,116 +1,10 @@ """ リポジトリ解析モジュール。 -言語 ratio 算出・フレームワーク検出・依存関係パース(純粋関数中心)を行う。 +言語 ratio 算出などの純粋関数を提供する。 """ -import json -import re -from typing import Dict, List - -# 依存関係ファイル名のセット -DEPENDENCY_FILES = { - "requirements.txt", - "pyproject.toml", - "package.json", - "pom.xml", - "go.mod", -} - -# 依存関係 → フレームワーク名のマッピング(フレームワーク・ライブラリのみ) -DEPENDENCY_TO_FRAMEWORK: Dict[str, str] = { - # Python 系 - "fastapi": "FastAPI", - "django": "Django", - "flask": "Flask", - "sqlalchemy": "SQLAlchemy", - "pandas": "Pandas", - "numpy": "NumPy", - "scikit-learn": "scikit-learn", - "sklearn": "scikit-learn", - "tensorflow": "TensorFlow", - "torch": "PyTorch", - "pytorch": "PyTorch", - "langchain": "LangChain", - "celery": "Celery", - "airflow": "Airflow", - "apache-airflow": "Airflow", - "mlflow": "MLflow", - "sentry-sdk": "Sentry", - "prometheus-client": "Prometheus", - # Node 系 - "react": "React", - "react-dom": "React", - "next": "Next.js", - "vue": "Vue", - "nuxt": "Nuxt.js", - "angular": "Angular", - "@angular/core": "Angular", - "svelte": "Svelte", - "express": "Express", - "@nestjs/core": "NestJS", - "gatsby": "Gatsby", - "remix": "Remix", - "astro": "Astro", - "react-native": "React Native", - "prisma": "PostgreSQL", - "mongoose": "MongoDB", - "redis": "Redis", - "ioredis": "Redis", - "graphql": "GraphQL", - "@apollo/server": "GraphQL", - # Java 系 (artifactId) - "spring-boot": "Spring Boot", - "spring-boot-starter": "Spring Boot", - "spring-boot-starter-web": "Spring Boot", - # Go 系 - "github.com/gin-gonic/gin": "Gin", - "github.com/labstack/echo": "Echo", - "github.com/gofiber/fiber": "Fiber", - # クラウド SDK(フレームワーク扱い) - "boto3": "AWS", -} - -# 依存関係 → インフラ・クラウドプロバイダー名のマッピング -INFRA_FROM_DEPENDENCIES: Dict[str, str] = { - "boto3": "AWS", - "botocore": "AWS", - "google-cloud-storage": "GCP", - "google-cloud-run": "GCP", - "google-cloud-bigquery": "GCP", - "google-cloud-pubsub": "GCP", - "azure-storage-blob": "Azure", - "azure-identity": "Azure", - "azure-mgmt-compute": "Azure", - "pulumi": "Pulumi", - "aws-cdk-lib": "AWS CDK", - "constructs": "AWS CDK", -} - -# ルートファイル → DevTools 名のマッピング -_DEVTOOL_ROOT_FILES: Dict[str, str] = { - "Dockerfile": "Docker", - "docker-compose.yml": "Docker Compose", - "docker-compose.yaml": "Docker Compose", - ".github": "GitHub Actions", - "Makefile": "Build Automation", - "Jenkinsfile": "Jenkins", - ".gitlab-ci.yml": "GitLab CI", - ".circleci": "CircleCI", -} - -# ルートファイル → インフラツール名のマッピング -_INFRA_ROOT_FILES: Dict[str, str] = { - "terraform": "Terraform", - ".terraform": "Terraform", - "infra": "Terraform", - "k8s": "Kubernetes", - "kubernetes": "Kubernetes", - "helm": "Helm", - "cdk.json": "AWS CDK", - "pulumi.yaml": "Pulumi", - "pulumi.yml": "Pulumi", -} +from typing import Dict def compute_language_ratios(languages: Dict[str, int]) -> Dict[str, float]: @@ -123,168 +17,3 @@ def compute_language_ratios(languages: Dict[str, int]) -> Dict[str, float]: if total == 0: return {} return {lang: count / total for lang, count in languages.items()} - - -def detect_devtools_from_root_files(root_files: List[str]) -> List[str]: - """ルートレベルのファイル/ディレクトリ名から DevTools を検出する。重複は除去する。""" - detected: List[str] = [] - seen: set = set() - for fname in root_files: - tool = _DEVTOOL_ROOT_FILES.get(fname) - if tool and tool not in seen: - seen.add(tool) - detected.append(tool) - return detected - - -def detect_infras_from_root_files(root_files: List[str]) -> List[str]: - """ルートレベルのファイル/ディレクトリ名からインフラツールを検出する。重複は除去する。""" - detected: List[str] = [] - seen: set = set() - for fname in root_files: - tool = _INFRA_ROOT_FILES.get(fname) - if tool and tool not in seen: - seen.add(tool) - detected.append(tool) - return detected - - -def detect_infras_from_dependencies(dependencies: List[str]) -> List[str]: - """依存関係名のリストから INFRA_FROM_DEPENDENCIES でインフラ名を検出する。 - - 重複を除去し、最初に出現した順序を保つ。 - """ - detected: List[str] = [] - seen: set = set() - for dep in dependencies: - infra = INFRA_FROM_DEPENDENCIES.get(dep.lower()) - if infra and infra not in seen: - seen.add(infra) - detected.append(infra) - return detected - - -def detect_from_dependencies(dependencies: List[str]) -> List[str]: - """依存関係名のリストから DEPENDENCY_TO_FRAMEWORK でフレームワーク名を検出する。 - - 重複を除去し、最初に出現した順序を保つ。 - """ - detected: List[str] = [] - seen: set = set() - for dep in dependencies: - framework = DEPENDENCY_TO_FRAMEWORK.get(dep.lower()) - if framework and framework not in seen: - seen.add(framework) - detected.append(framework) - return detected - - -def merge_frameworks(*framework_lists: List[str]) -> List[str]: - """複数のフレームワークリストを順序を保ったままマージし、重複を除去する。""" - merged: List[str] = [] - seen: set = set() - for fw_list in framework_lists: - for fw in fw_list: - if fw not in seen: - seen.add(fw) - merged.append(fw) - return merged - - -def parse_requirements_txt(content: str) -> List[str]: - """requirements.txt からパッケージ名を抽出する。""" - packages: List[str] = [] - for line in content.splitlines(): - line = line.strip() - if not line or line.startswith("#") or line.startswith("-"): - continue - # バージョン指定子を削除: pkg>=1.0, pkg==1.0, pkg[extra] - name = line.split(">=")[0].split("<=")[0].split("==")[0] - name = name.split("!=")[0].split("~=")[0].split(">")[0].split("<")[0] - name = name.split("[")[0].split(";")[0].strip() - if name: - packages.append(name.lower()) - return packages - - -def parse_pyproject_toml(content: str) -> List[str]: - """pyproject.toml から依存関係名を抽出する(単純な行ベース)。 - - 以下の形式に対応: - - PEP 621: dependencies = ["fastapi>=0.100"] - - Poetry 単純値: fastapi = "^0.100" - - Poetry インラインテーブル: fastapi = {extras = ["standard"], version = "^0.109.0"} - """ - packages: List[str] = [] - in_deps = False - for line in content.splitlines(): - stripped = line.strip() - if stripped in ( - "[project.dependencies]", - "[tool.poetry.dependencies]", - "dependencies = [", - ): - in_deps = True - continue - if in_deps: - # 新セクション開始: deps 解析を終了 - if stripped.startswith("["): - in_deps = False - continue - # PEP 621 配列の終端 - if stripped == "]": - in_deps = False - continue - # PEP 621 形式: "fastapi>=0.100" - if stripped.startswith('"') or stripped.startswith("'"): - name = stripped.strip("\"', ") - name = name.split(">=")[0].split("<=")[0].split("==")[0] - name = name.split("!=")[0].split("~=")[0].split(">")[0] - name = name.split("<")[0].split("[")[0].split(";")[0].strip() - if name: - packages.append(name.lower()) - continue - # Poetry 形式: name = "^1.0" または name = {extras = [...], version = "..."} - if "=" in stripped and not stripped.startswith("#"): - name = stripped.split("=")[0].strip().lower() - if name and name != "python": - packages.append(name) - return packages - - -def parse_package_json(content: str) -> List[str]: - """package.json から依存関係名を抽出する。""" - try: - data = json.loads(content) - except (json.JSONDecodeError, ValueError): - return [] - packages: List[str] = [] - for key in ("dependencies", "devDependencies"): - deps = data.get(key) - if isinstance(deps, dict): - packages.extend(dep.lower() for dep in deps) - return packages - - -def parse_pom_xml(content: str) -> List[str]: - """pom.xml から artifactId の値を抽出する(基本的な正規表現)。""" - return [m.lower() for m in re.findall(r"([^<]+)", content)] - - -def parse_go_mod(content: str) -> List[str]: - """go.mod の require ブロックからモジュールパスを抽出する。""" - modules: List[str] = [] - in_require = False - for line in content.splitlines(): - stripped = line.strip() - if stripped.startswith("require ("): - in_require = True - continue - if in_require: - if stripped == ")": - in_require = False - continue - parts = stripped.split() - if parts: - modules.append(parts[0].lower()) - return modules diff --git a/backend/app/services/intelligence/github_collector.py b/backend/app/services/intelligence/github_collector.py index 5710283c..c3dd50b3 100644 --- a/backend/app/services/intelligence/github_collector.py +++ b/backend/app/services/intelligence/github_collector.py @@ -2,7 +2,7 @@ GitHub データコレクター(オーケストレーション層)。 GitHub REST API を介してパブリックリポジトリのデータを取得します。 -実際の API 呼び出しは github.api_client、解析処理は github.repo_analyzer に委譲します。 +実際の API 呼び出しは github.api_client に委譲します。 """ import logging @@ -19,41 +19,8 @@ _REPO_MIN_SIZE_BYTES, GITHUB_API, GitHubUserNotFoundError, - fetch_file_content, fetch_languages, fetch_repos_raw, - fetch_root_files, - fetch_subdirectory_dep_files, -) -from .github.repo_analyzer import ( - DEPENDENCY_FILES as _DEPENDENCY_FILES, -) -from .github.repo_analyzer import ( - detect_devtools_from_root_files as _detect_devtools_from_root_files, -) -from .github.repo_analyzer import ( - detect_from_dependencies as _detect_from_dependencies, -) -from .github.repo_analyzer import ( - detect_infras_from_dependencies as _detect_infras_from_dependencies, -) -from .github.repo_analyzer import ( - detect_infras_from_root_files as _detect_infras_from_root_files, -) -from .github.repo_analyzer import ( - parse_go_mod as _parse_go_mod, -) -from .github.repo_analyzer import ( - parse_package_json as _parse_package_json, -) -from .github.repo_analyzer import ( - parse_pom_xml as _parse_pom_xml, -) -from .github.repo_analyzer import ( - parse_pyproject_toml as _parse_pyproject_toml, -) -from .github.repo_analyzer import ( - parse_requirements_txt as _parse_requirements_txt, ) logger = logging.getLogger(__name__) @@ -80,60 +47,7 @@ class RepoData: pushed_at: str # ISO 8601 形式 fork: bool stargazers_count: int - default_branch: str - dependencies: List[str] = field(default_factory=list) - root_files: List[str] = field(default_factory=list) - detected_frameworks: List[str] = field(default_factory=list) # 依存関係由来のフレームワーク - detected_devtools: List[str] = field(default_factory=list) # ルートファイル由来の DevTools - detected_infras: List[str] = field(default_factory=list) # ルートファイル由来のインフラツール - - -async def _parse_dependencies( - client: httpx.AsyncClient, - owner: str, - repo: str, - root_files: List[str], -) -> List[str]: - """依存関係ファイルを解析し、パッケージ名リストを返す。 - - ルートファイルにない場合はサブディレクトリ(backend/ 等)もフォールバック探索する。 - """ - deps: List[str] = [] - - # ルートの dep ファイルを解析 - for fname in root_files: - if fname not in _DEPENDENCY_FILES: - continue - content = await fetch_file_content(client, owner, repo, fname) - if not content: - continue - deps.extend(_parse_dep_content(fname, content)) - - # モノレポ等でルートに dep ファイルがない場合のフォールバック - subdir_paths = await fetch_subdirectory_dep_files(client, owner, repo, root_files) - for path in subdir_paths: - fname = path.split("/")[-1] - content = await fetch_file_content(client, owner, repo, path) - if not content: - continue - deps.extend(_parse_dep_content(fname, content)) - - return list(set(deps)) - - -def _parse_dep_content(filename: str, content: str) -> List[str]: - """ファイル名に応じたパーサーでパッケージ名リストを返す。""" - if filename == "requirements.txt": - return _parse_requirements_txt(content) - if filename == "pyproject.toml": - return _parse_pyproject_toml(content) - if filename == "package.json": - return _parse_package_json(content) - if filename == "pom.xml": - return _parse_pom_xml(content) - if filename == "go.mod": - return _parse_go_mod(content) - return [] + default_branch: str = field(default="main") def _passes_filter(raw: dict, include_forks: bool, cutoff_date_str: str) -> bool: @@ -181,7 +95,7 @@ async def collect_repos( ) as client: # 1. リポジトリリストの取得 raw_repos = await fetch_repos_raw(client, username, max_pages) - # 2. 各リポジトリについて、言語の内訳と構造を取得 + # 2. 各リポジトリについて、言語の内訳を取得 cutoff = datetime.now(timezone.utc).replace( year=datetime.now(timezone.utc).year - _REPO_MAX_AGE_YEARS, ) @@ -194,19 +108,6 @@ async def collect_repos( repo_name = raw["name"] languages = await fetch_languages(client, owner_login, repo_name) - root_files = await fetch_root_files(client, owner_login, repo_name) - dependencies = await _parse_dependencies( - client, owner_login, repo_name, root_files - ) - detected_frameworks = _detect_from_dependencies(dependencies) - detected_devtools = _detect_devtools_from_root_files(root_files) - # インフラ: ルートファイル由来 + 依存関係由来(AWS/GCP/Azure 等)をマージ - _infras_root = _detect_infras_from_root_files(root_files) - _infras_deps = _detect_infras_from_dependencies(dependencies) - _seen_infras: set = set(_infras_root) - detected_infras = _infras_root + [ - inf for inf in _infras_deps if inf not in _seen_infras - ] repos.append( RepoData( @@ -220,11 +121,6 @@ async def collect_repos( fork=raw.get("fork", False), stargazers_count=raw.get("stargazers_count", 0), default_branch=raw.get("default_branch", "main"), - dependencies=dependencies, - root_files=root_files, - detected_frameworks=detected_frameworks, - detected_devtools=detected_devtools, - detected_infras=detected_infras, ) ) diff --git a/backend/app/services/intelligence/github_link_service.py b/backend/app/services/intelligence/github_link_service.py index 3567edc8..8fec367d 100644 --- a/backend/app/services/intelligence/github_link_service.py +++ b/backend/app/services/intelligence/github_link_service.py @@ -17,7 +17,7 @@ from ..progress_service import set_progress from ..tasks.exceptions import NonRetryableError from ..tasks.handlers.base import SessionFactory -from .github.contributions import fetch_contribution_calendar +from .github.contributions import fetch_all_contribution_calendars from .github_collector import GitHubUserNotFoundError, collect_repos from .pipeline import aggregate_intelligence from .response_mapper import map_pipeline_result @@ -97,16 +97,21 @@ async def _on_repo_fetched(done: int, total: int) -> None: raise exc # コントリビューションカレンダー取得(補助処理: 失敗しても連携は継続) - calendar = None + calendars: list = [] + contribution_fetch_failed = False if token: - calendar = await fetch_contribution_calendar(payload["github_username"], token) + ok, calendars = await fetch_all_contribution_calendars( + payload["github_username"], token + ) + # 取得失敗のときだけ警告対象にする(貢献年が無いだけなら警告しない) + contribution_fetch_failed = not ok # ステップ 3: スキル抽出(集計関数で一括処理) await set_progress(task_id, 3, _TOTAL_STEPS, "スキル集計中...") result = aggregate_intelligence(payload["github_username"], repos) response = map_pipeline_result(result) - response.contribution_calendar = calendar + response.contribution_calendars = calendars result_dict = response.model_dump() # ── フェーズC: 結果書き戻し(新セッション)───────────────────────────── @@ -124,10 +129,11 @@ async def _on_repo_fetched(done: int, total: int) -> None: cache.status = "completed" cache.error_message = None # コントリビューション取得失敗は連携自体を失敗させず警告として残す - # (token が無い場合は取得を試行していないため警告は出さない) + # (token が無い場合は取得を試行していないため警告は出さない。 + # 貢献年がゼロで calendars が空でも、取得自体が成功なら警告しない) cache.warning_message = ( get_error("github_link.contribution_fetch_failed") - if token and calendar is None + if contribution_fetch_failed else None ) cache.completed_at = _now() diff --git a/backend/app/services/intelligence/pipeline.py b/backend/app/services/intelligence/pipeline.py index adf43fa9..cbf7fe38 100644 --- a/backend/app/services/intelligence/pipeline.py +++ b/backend/app/services/intelligence/pipeline.py @@ -29,9 +29,6 @@ class IntelligenceResult: unique_skills: int analyzed_at: str languages: Dict[str, int] = field(default_factory=dict) - detected_frameworks: Dict[str, int] = field(default_factory=dict) # フレームワーク名 → 使用リポジトリ数 - detected_devtools: Dict[str, int] = field(default_factory=dict) # DevTools 名 → 使用リポジトリ数 - detected_infras: Dict[str, int] = field(default_factory=dict) # インフラツール名 → 使用リポジトリ数 def aggregate_intelligence(username: str, repos: List[RepoData]) -> IntelligenceResult: @@ -45,18 +42,6 @@ def aggregate_intelligence(username: str, repos: List[RepoData]) -> Intelligence for lang, byte_count in repo.languages.items(): lang_totals[lang] += byte_count - # 全リポジトリのフレームワーク・DevTools・インフラをリポジトリ数でカウント - framework_counts: Dict[str, int] = defaultdict(int) - devtool_counts: Dict[str, int] = defaultdict(int) - infra_counts: Dict[str, int] = defaultdict(int) - for repo in repos: - for fw in repo.detected_frameworks: - framework_counts[fw] += 1 - for dt in repo.detected_devtools: - devtool_counts[dt] += 1 - for inf in repo.detected_infras: - infra_counts[inf] += 1 - extraction = extract_skills(repos) return IntelligenceResult( @@ -65,9 +50,6 @@ def aggregate_intelligence(username: str, repos: List[RepoData]) -> Intelligence unique_skills=len(extraction.unique_skills), analyzed_at=datetime.now().isoformat(), languages=dict(lang_totals), - detected_frameworks=dict(framework_counts), - detected_devtools=dict(devtool_counts), - detected_infras=dict(infra_counts), ) diff --git a/backend/app/services/intelligence/response_mapper.py b/backend/app/services/intelligence/response_mapper.py index 57407b56..806d027a 100644 --- a/backend/app/services/intelligence/response_mapper.py +++ b/backend/app/services/intelligence/response_mapper.py @@ -12,7 +12,4 @@ def map_pipeline_result(result: IntelligenceResult) -> GitHubLinkResponse: unique_skills=result.unique_skills, analyzed_at=result.analyzed_at, languages=result.languages, - detected_frameworks=result.detected_frameworks, - detected_devtools=result.detected_devtools, - detected_infras=result.detected_infras, ) diff --git a/backend/app/services/intelligence/skill_extractor.py b/backend/app/services/intelligence/skill_extractor.py index 68c16e62..fffa1633 100644 --- a/backend/app/services/intelligence/skill_extractor.py +++ b/backend/app/services/intelligence/skill_extractor.py @@ -5,8 +5,6 @@ 1. 言語検出 (GitHub の linguist) 2. リポジトリのトピック 3. 説明文のキーワードマッチング - 4. 依存関係分析 (requirements.txt, package.json など) - 5. ルートファイル / ディレクトリ検出 (Dockerfile, .github, terraform など) LLMは使用しません。 """ @@ -15,7 +13,6 @@ from dataclasses import dataclass, field from typing import List, Set -from .github.repo_analyzer import DEPENDENCY_TO_FRAMEWORK from .github_collector import RepoData from .skill_taxonomy import ( DESCRIPTION_KEYWORDS, @@ -115,16 +112,4 @@ def add(skill_name: str, source: str, *, language_bytes: int = 0) -> None: for skill_name in matched_skills: add(skill_name, "description") - # 4. 依存関係 (requirements.txt, package.json などから解析) - for dep in repo.dependencies: - add(DEPENDENCY_TO_FRAMEWORK.get(dep, ""), "dependency") - - # 5. ルートファイル / ディレクトリ検出(devtools・infra) - for tech in [*repo.detected_devtools, *repo.detected_infras]: - add(tech, "root_file") - - # 6. 依存関係由来のフレームワーク(merge_frameworks の代替) - for framework in repo.detected_frameworks: - add(framework, "dependency") - return skills diff --git a/backend/tests/test_contributions.py b/backend/tests/test_contributions.py index b07c4d9e..d62b6117 100644 --- a/backend/tests/test_contributions.py +++ b/backend/tests/test_contributions.py @@ -4,8 +4,9 @@ 対象モジュール: app.services.intelligence.github.contributions テスト方針: - httpx.AsyncClient をモック化し、実 GitHub API は叩かない - - 補助処理として失敗時に None を返す(例外を送出しない)ことを検証 + - 補助処理として失敗時に空配列を返す(例外を送出しない)ことを検証 - contributionLevel → 0–4 の正規化を検証 + - contributionYears から年ごとに取得し、新しい年順で返すことを検証 """ import asyncio @@ -14,7 +15,7 @@ import httpx from app.services.intelligence.github.contributions import ( _parse_calendar, - fetch_contribution_calendar, + fetch_all_contribution_calendars, ) @@ -27,8 +28,12 @@ def _run(coro): loop.close() -def _make_mock_client(*, status_code=200, payload=None, post_side_effect=None): - """POST レスポンスを返すモック AsyncClient を生成する。""" +def _make_mock_client(*, post_responses=None, post_side_effect=None): + """POST レスポンスを順番に返すモック AsyncClient を生成する。 + + post_responses は (status_code, payload) のタプル列。post を呼ぶたびに + 先頭から順に返す(年一覧 → 各年カレンダーの順で消費される)。 + """ mock_client = AsyncMock() mock_client.__aenter__ = AsyncMock(return_value=mock_client) mock_client.__aexit__ = AsyncMock(return_value=False) @@ -36,49 +41,49 @@ def _make_mock_client(*, status_code=200, payload=None, post_side_effect=None): if post_side_effect is not None: mock_client.post = AsyncMock(side_effect=post_side_effect) else: - mock_resp = MagicMock() - mock_resp.status_code = status_code - mock_resp.json = MagicMock(return_value=payload or {}) - mock_client.post = AsyncMock(return_value=mock_resp) + responses = [] + for status_code, payload in post_responses or []: + mock_resp = MagicMock() + mock_resp.status_code = status_code + mock_resp.json = MagicMock(return_value=payload or {}) + responses.append(mock_resp) + mock_client.post = AsyncMock(side_effect=responses) return mock_client -_CALENDAR_PAYLOAD = { - "data": { - "user": { - "contributionsCollection": { - "contributionCalendar": { - "totalContributions": 42, - "weeks": [ - { - "contributionDays": [ - { - "date": "2024-01-01", - "contributionCount": 0, - "contributionLevel": "NONE", - }, - { - "date": "2024-01-02", - "contributionCount": 5, - "contributionLevel": "SECOND_QUARTILE", - }, - ] - }, - { - "contributionDays": [ - { - "date": "2024-01-08", - "contributionCount": 12, - "contributionLevel": "FOURTH_QUARTILE", - }, - ] - }, - ], +def _years_payload(years): + return { + "data": {"user": {"contributionsCollection": {"contributionYears": years}}} + } + + +def _calendar_payload(total, weeks): + return { + "data": { + "user": { + "contributionsCollection": { + "contributionCalendar": { + "totalContributions": total, + "weeks": weeks, + } } } } } -} + + +_WEEKS_2024 = [ + { + "contributionDays": [ + {"date": "2024-01-01", "contributionCount": 0, "contributionLevel": "NONE"}, + { + "date": "2024-01-02", + "contributionCount": 5, + "contributionLevel": "SECOND_QUARTILE", + }, + ] + }, +] def _patch_client(mock_client): @@ -88,53 +93,72 @@ def _patch_client(mock_client): ) -class TestFetchContributionCalendar: - def test_success_parses_calendar(self): - """正常系: totalContributions と週ごとの level が正しく取得されること。""" - with _patch_client(_make_mock_client(payload=_CALENDAR_PAYLOAD)): - calendar = _run(fetch_contribution_calendar("gh-user", "token123")) - - assert calendar is not None - assert calendar.total_contributions == 42 - assert len(calendar.weeks) == 2 - # NONE → 0, SECOND_QUARTILE → 2, FOURTH_QUARTILE → 4 - assert [d.level for d in calendar.weeks[0]] == [0, 2] - assert calendar.weeks[0][1].count == 5 - assert calendar.weeks[1][0].level == 4 - assert calendar.weeks[1][0].date == "2024-01-08" - - def test_graphql_errors_returns_none(self): - """GraphQL errors を含む応答では None を返すこと。""" +class TestFetchAllContributionCalendars: + def test_returns_calendars_in_descending_year_order(self): + """貢献のある全年を新しい年順(降順)で返し、各 year が付与されること。""" + client = _make_mock_client( + post_responses=[ + (200, _years_payload([2023, 2024])), # 年一覧 + (200, _calendar_payload(42, _WEEKS_2024)), # 2024(降順で先に取得) + (200, _calendar_payload(10, [])), # 2023 + ] + ) + with _patch_client(client): + ok, calendars = _run(fetch_all_contribution_calendars("gh-user", "token123")) + + assert ok is True + assert [c.year for c in calendars] == [2024, 2023] + assert calendars[0].total_contributions == 42 + # NONE → 0, SECOND_QUARTILE → 2 + assert [d.level for d in calendars[0].weeks[0]] == [0, 2] + assert calendars[1].total_contributions == 10 + + def test_no_years_returns_success_empty(self): + """contributionYears が空(貢献ゼロ)なら success=True で空配列を返すこと。 + + 取得自体は成功しているため、呼び出し側で警告を立てさせない。 + """ + client = _make_mock_client(post_responses=[(200, _years_payload([]))]) + with _patch_client(client): + ok, calendars = _run(fetch_all_contribution_calendars("gh-user", "token123")) + assert ok is True + assert calendars == [] + + def test_years_query_graphql_error_returns_failure(self): + """年一覧取得が GraphQL エラーなら success=False・空配列を返すこと(例外を投げない)。""" payload = {"data": {"user": None}, "errors": [{"message": "Bad creds"}]} - with _patch_client(_make_mock_client(payload=payload)): - calendar = _run(fetch_contribution_calendar("gh-user", "token123")) - assert calendar is None - - def test_user_none_returns_none(self): - """user が null の応答では None を返すこと。""" - payload = {"data": {"user": None}} - with _patch_client(_make_mock_client(payload=payload)): - calendar = _run(fetch_contribution_calendar("gh-user", "token123")) - assert calendar is None - - def test_non_200_returns_none(self): - """HTTP 401 等では None を返すこと(例外を投げない)。""" - with _patch_client(_make_mock_client(status_code=401, payload={})): - calendar = _run(fetch_contribution_calendar("gh-user", "token123")) - assert calendar is None - - def test_network_error_returns_none(self): - """ネットワーク障害でも例外を投げず None を返すこと(補助処理)。""" - with _patch_client( - _make_mock_client(post_side_effect=httpx.ConnectError("boom")) - ): - calendar = _run(fetch_contribution_calendar("gh-user", "token123")) - assert calendar is None + client = _make_mock_client(post_responses=[(200, payload)]) + with _patch_client(client): + ok, calendars = _run(fetch_all_contribution_calendars("gh-user", "token123")) + assert ok is False + assert calendars == [] + + def test_failed_year_is_skipped(self): + """一部の年の取得に失敗しても、成功した年だけを返すこと(success=True)。""" + client = _make_mock_client( + post_responses=[ + (200, _years_payload([2023, 2024])), + (200, _calendar_payload(42, _WEEKS_2024)), # 2024 成功 + (500, {}), # 2023 は失敗 → スキップ + ] + ) + with _patch_client(client): + ok, calendars = _run(fetch_all_contribution_calendars("gh-user", "token123")) + assert ok is True + assert [c.year for c in calendars] == [2024] + + def test_network_error_returns_failure(self): + """ネットワーク障害でも例外を投げず success=False・空配列を返すこと(補助処理)。""" + client = _make_mock_client(post_side_effect=httpx.ConnectError("boom")) + with _patch_client(client): + ok, calendars = _run(fetch_all_contribution_calendars("gh-user", "token123")) + assert ok is False + assert calendars == [] class TestParseCalendar: def test_level_mapping_and_defaults(self): - """contributionLevel が 0–4 に正規化され、未知値は 0 になること。""" + """contributionLevel が 0–4 に正規化され、未知値は 0 になること。year も付与される。""" raw = { "totalContributions": 3, "weeks": [ @@ -147,12 +171,14 @@ def test_level_mapping_and_defaults(self): } ], } - calendar = _parse_calendar(raw) + calendar = _parse_calendar(raw, 2024) + assert calendar.year == 2024 assert calendar.total_contributions == 3 assert [d.level for d in calendar.weeks[0]] == [1, 3, 0] def test_empty_weeks(self): """weeks が空でも壊れないこと。""" - calendar = _parse_calendar({"totalContributions": 0, "weeks": []}) + calendar = _parse_calendar({"totalContributions": 0, "weeks": []}, 2022) + assert calendar.year == 2022 assert calendar.total_contributions == 0 assert calendar.weeks == [] diff --git a/backend/tests/test_github_collector_extended.py b/backend/tests/test_github_collector_extended.py index c12ddd63..a7fe0407 100644 --- a/backend/tests/test_github_collector_extended.py +++ b/backend/tests/test_github_collector_extended.py @@ -3,7 +3,7 @@ 対象モジュール: app.services.intelligence.github_collector テスト方針: - - fetch_repos_raw / fetch_languages / fetch_root_files は AsyncMock でモック化 + - fetch_repos_raw / fetch_languages は AsyncMock でモック化 - 実 GitHub API は一切叩かない - _passes_filter は純粋関数として直接テスト """ @@ -110,7 +110,6 @@ def _mock_http_client(): mock_client = MagicMock() mock_client.closed = False # client.get は非同期メソッドのため AsyncMock が必要 - # fetch_file_content が 404 を受け取り None を返すようにする _not_found = MagicMock(status_code=404) mock_client.get = AsyncMock(return_value=_not_found) @@ -126,7 +125,7 @@ async def _aexit(*_args, **_kwargs): class TestCollectRepos: - def _mock_collect(self, raw_repos, languages=None, root_files=None): + def _mock_collect(self, raw_repos, languages=None): """collect_repos の外部 API 呼び出しをすべてモック化して実行するヘルパー。""" mock_http, _ = _mock_http_client() with ( @@ -139,12 +138,7 @@ def _mock_collect(self, raw_repos, languages=None, root_files=None): patch( "app.services.intelligence.github_collector.fetch_languages", new_callable=AsyncMock, - return_value=languages or {"Python": 10000}, - ), - patch( - "app.services.intelligence.github_collector.fetch_root_files", - new_callable=AsyncMock, - return_value=root_files or [], + return_value=languages if languages is not None else {"Python": 10000}, ), ): return _run(collect_repos("testuser")) @@ -191,11 +185,6 @@ def test_fork_included_when_requested(self): new_callable=AsyncMock, return_value={"Python": 1000}, ), - patch( - "app.services.intelligence.github_collector.fetch_root_files", - new_callable=AsyncMock, - return_value=[], - ), ): repos = _run(collect_repos("testuser", include_forks=True)) assert len(repos) == 1 @@ -236,11 +225,6 @@ async def _on_repo_fetched(done: int, total: int) -> None: new_callable=AsyncMock, return_value={"Python": 2000}, ), - patch( - "app.services.intelligence.github_collector.fetch_root_files", - new_callable=AsyncMock, - return_value=[], - ), ): _run(collect_repos("testuser", on_repo_fetched=_on_repo_fetched)) @@ -269,11 +253,6 @@ async def _fetch_languages(client, *_args, **_kwargs): "app.services.intelligence.github_collector.fetch_languages", side_effect=_fetch_languages, ), - patch( - "app.services.intelligence.github_collector.fetch_root_files", - new_callable=AsyncMock, - return_value=[], - ), ): repos = _run(collect_repos("testuser")) diff --git a/backend/tests/test_github_link.py b/backend/tests/test_github_link.py index 4af725df..733e4062 100644 --- a/backend/tests/test_github_link.py +++ b/backend/tests/test_github_link.py @@ -7,7 +7,6 @@ import asyncio from unittest.mock import AsyncMock, patch -from app.services.intelligence.github.repo_analyzer import parse_go_mod, parse_pom_xml from app.services.intelligence.github_collector import RepoData from app.services.intelligence.pipeline import IntelligenceResult, run_pipeline from app.services.intelligence.response_mapper import map_pipeline_result @@ -25,11 +24,6 @@ def _make_repo( description="", created_at="2022-01-01T00:00:00Z", pushed_at="2023-06-01T00:00:00Z", - dependencies=None, - root_files=None, - detected_frameworks=None, - detected_devtools=None, - detected_infras=None, ): return RepoData( name=name, @@ -42,11 +36,6 @@ def _make_repo( fork=False, stargazers_count=0, default_branch="main", - dependencies=dependencies or [], - root_files=root_files or [], - detected_frameworks=detected_frameworks or [], - detected_devtools=detected_devtools or [], - detected_infras=detected_infras or [], ) @@ -165,115 +154,20 @@ def test_source_tracking(self): assert "language" in sources assert "topic" in sources - def test_extracts_from_dependencies(self): + def test_only_language_topic_description_sources(self): + """フレームワーク/インフラ検出撤去後、source は language/topic/description のみ。""" repos = [ _make_repo( - dependencies=["fastapi", "sqlalchemy", "uvicorn"], - ) - ] - result = extract_skills(repos) - names = {s.skill_name for s in result.skills} - assert "FastAPI" in names - assert "SQLAlchemy" in names - - def test_extracts_from_detected_devtools_and_infras(self): - repos = [ - _make_repo( - detected_devtools=["Docker", "GitHub Actions"], - detected_infras=["Terraform"], + languages={"Python": 10000}, + topics=["docker"], + description="Built with FastAPI", ) ] result = extract_skills(repos) - names = {s.skill_name for s in result.skills} - assert "Docker" in names - assert "GitHub Actions" in names - assert "Terraform" in names - - def test_dependency_source_label(self): - repos = [_make_repo(dependencies=["fastapi"])] - result = extract_skills(repos) + assert result.skills, "fixture が language/topic/description の各経路を発火させること" sources = {s.source for s in result.skills} - assert "dependency" in sources - - def test_root_file_source_label(self): - repos = [_make_repo(detected_devtools=["Docker"])] - result = extract_skills(repos) - sources = {s.source for s in result.skills} - assert "root_file" in sources - - def test_dedup_dependency_and_topic(self): - """Same skill from topic + dependency should appear once.""" - repos = [ - _make_repo( - topics=["fastapi"], - dependencies=["fastapi"], - ) - ] - result = extract_skills(repos) - fastapi_count = sum(1 for s in result.skills if s.skill_name == "FastAPI") - assert fastapi_count == 1 - - def test_dedup_root_file_and_language(self): - """Docker from language + root_file should appear once.""" - repos = [ - _make_repo( - languages={"Dockerfile": 500}, - detected_devtools=["Docker"], - ) - ] - result = extract_skills(repos) - docker_count = sum(1 for s in result.skills if s.skill_name == "Docker") - assert docker_count == 1 - - def test_full_repo_with_all_sources(self): - """A realistic repo should produce rich skill set.""" - repos = [ - _make_repo( - name="fullstack-app", - languages={"Python": 50000, "Dockerfile": 500}, - topics=["fastapi", "postgresql"], - description="Backend API", - dependencies=["fastapi", "sqlalchemy", "boto3"], - root_files=["Dockerfile", ".github", "terraform"], - detected_devtools=["Docker", "GitHub Actions"], - detected_infras=["Terraform"], - ) - ] - result = extract_skills(repos) - names = {s.skill_name for s in result.skills} - assert "Python" in names - assert "FastAPI" in names - assert "PostgreSQL" in names - assert "Docker" in names - assert "GitHub Actions" in names - assert "Terraform" in names - assert "SQLAlchemy" in names - assert "AWS" in names # from boto3 dependency - - -# ── Dependency Parsing(github_collector 経由でのみ確認すべき pom.xml / go.mod のみ)───── -# 一般的な requirements.txt / package.json / root file 検出は test_repo_analyzer.py で網羅。 - - -class TestDependencyParsing: - def test_parse_pom_xml(self): - - content = ( - "" - "spring-boot-starter-web" - "" - ) - result = parse_pom_xml(content) - assert "spring-boot-starter-web" in result - - def test_parse_go_mod(self): - - content = ( - "module example.com/app\n\nrequire (\n" - "\tgithub.com/gin-gonic/gin v1.9\n)\n" - ) - result = parse_go_mod(content) - assert "github.com/gin-gonic/gin" in result + # 3 経路すべてが発火し、かつそれ以外の source が混ざらないこと(撤去の取りこぼし検知)。 + assert sources == {"language", "topic", "description"} # ── Intelligence Endpoint Tests ──────────────────────────────────────── @@ -305,6 +199,9 @@ def test_map_pipeline_result_includes_languages() -> None: assert response.languages == {"Python": 50000, "TypeScript": 30000} assert response.username == "testuser" assert response.repos_analyzed == 3 + # 撤去したフィールドはレスポンス schema に存在しない + assert not hasattr(response, "detected_frameworks") + assert response.contribution_calendars == [] # ── Pipeline Tests ────────────────────────────────────────────────────── @@ -323,11 +220,6 @@ def _make_pipeline_repo(name: str, languages: dict | None = None) -> RepoData: fork=False, stargazers_count=0, default_branch="main", - dependencies=[], - root_files=[], - detected_frameworks=[], - detected_devtools=[], - detected_infras=[], ) diff --git a/backend/tests/test_repo_analyzer.py b/backend/tests/test_repo_analyzer.py index d192b899..97e4b0f7 100644 --- a/backend/tests/test_repo_analyzer.py +++ b/backend/tests/test_repo_analyzer.py @@ -1,16 +1,6 @@ """repo_analyzer モジュールのユニットテスト。""" -from app.services.intelligence.github.repo_analyzer import ( - compute_language_ratios, - detect_devtools_from_root_files, - detect_from_dependencies, - detect_infras_from_dependencies, - detect_infras_from_root_files, - merge_frameworks, - parse_package_json, - parse_pyproject_toml, - parse_requirements_txt, -) +from app.services.intelligence.github.repo_analyzer import compute_language_ratios # ── 言語 ratio 算出テスト ──────────────────────────────────────────────── @@ -39,259 +29,3 @@ def test_compute_language_ratios_all_zero_bytes() -> None: """全言語のバイト数が 0 の場合も空の辞書を返すこと。""" ratios = compute_language_ratios({"Python": 0, "Go": 0}) assert ratios == {} - - -# ── フレームワーク検出テスト ───────────────────────────────────────────── - - -def test_detect_from_root_files_requirements_txt_fastapi() -> None: - """requirements.txt に fastapi が含まれる場合、parse 後に FastAPI が検出されること。""" - content = "fastapi>=0.100\nuvicorn\n" - packages = parse_requirements_txt(content) - assert "fastapi" in packages - - -def test_detect_from_root_files_package_json_react() -> None: - """package.json に react が含まれる場合、parse 後に react が検出されること。""" - content = '{"dependencies": {"react": "^18.0.0"}}' - packages = parse_package_json(content) - assert "react" in packages - - -def test_detect_devtools_dockerfile() -> None: - """Dockerfile があれば Docker が DevTools として検出されること。""" - result = detect_devtools_from_root_files(["Dockerfile"]) - assert "Docker" in result - - -def test_detect_devtools_github_actions() -> None: - """.github があれば GitHub Actions が DevTools として検出されること。""" - result = detect_devtools_from_root_files([".github"]) - assert "GitHub Actions" in result - - -def test_detect_devtools_docker_compose_no_duplicates() -> None: - """docker-compose.yml と docker-compose.yaml は両方 Docker Compose に対応し重複しないこと。""" - result = detect_devtools_from_root_files(["docker-compose.yml", "docker-compose.yaml"]) - assert result.count("Docker Compose") == 1 - - -def test_detect_infras_terraform() -> None: - """terraform があれば Terraform がインフラとして検出されること。""" - result = detect_infras_from_root_files(["terraform"]) - assert "Terraform" in result - - -def test_detect_infras_no_duplicates() -> None: - """terraform と .terraform は両方 Terraform に対応し重複しないこと。""" - result = detect_infras_from_root_files(["terraform", ".terraform"]) - assert result.count("Terraform") == 1 - - -def test_detect_infras_infra_directory() -> None: - """infra/ ディレクトリがあれば Terraform として検出されること。""" - result = detect_infras_from_root_files(["infra"]) - assert "Terraform" in result - - -def test_detect_infras_kubernetes_directories() -> None: - """k8s / kubernetes ディレクトリがあれば Kubernetes として検出されること。""" - assert "Kubernetes" in detect_infras_from_root_files(["k8s"]) - assert "Kubernetes" in detect_infras_from_root_files(["kubernetes"]) - - -# ── detect_infras_from_dependencies テスト ────────────────────────────── - - -def test_detect_infras_from_dependencies_aws() -> None: - """boto3 があれば AWS として検出されること。""" - result = detect_infras_from_dependencies(["boto3"]) - assert "AWS" in result - - -def test_detect_infras_from_dependencies_gcp() -> None: - """google-cloud-storage があれば GCP として検出されること。""" - result = detect_infras_from_dependencies(["google-cloud-storage"]) - assert "GCP" in result - - -def test_detect_infras_from_dependencies_azure() -> None: - """azure-storage-blob があれば Azure として検出されること。""" - result = detect_infras_from_dependencies(["azure-storage-blob"]) - assert "Azure" in result - - -def test_detect_infras_from_dependencies_no_duplicate() -> None: - """boto3 と botocore は両方 AWS にマップされ重複しないこと。""" - result = detect_infras_from_dependencies(["boto3", "botocore"]) - assert result.count("AWS") == 1 - - -def test_detect_infras_from_dependencies_frameworks_excluded() -> None: - """fastapi など通常フレームワークはインフラとして検出されないこと。""" - result = detect_infras_from_dependencies(["fastapi", "react"]) - assert result == [] - - -# ── parse_pyproject_toml テスト ───────────────────────────────────────── - - -def test_parse_pyproject_toml_pep621() -> None: - """PEP 621 形式の dependencies = [...] から依存名が抽出されること。""" - content = '[project]\ndependencies = [\n "fastapi>=0.100",\n "uvicorn[standard]",\n]\n' - result = parse_pyproject_toml(content) - assert "fastapi" in result - assert "uvicorn" in result - - -def test_parse_pyproject_toml_poetry_simple() -> None: - """Poetry の name = "version" 形式から依存名が抽出されること。""" - content = "[tool.poetry.dependencies]\npython = \"^3.11\"\nfastapi = \"^0.109.0\"\n" - result = parse_pyproject_toml(content) - assert "fastapi" in result - assert "python" not in result - - -def test_parse_pyproject_toml_poetry_extras_inline_table() -> None: - """Poetry の extras インラインテーブル形式が正しく解析されること。""" - content = '[tool.poetry.dependencies]\nfastapi = {extras = ["standard"], version = "^0.109.0"}\n' - result = parse_pyproject_toml(content) - assert "fastapi" in result - - -def test_parse_pyproject_toml_section_restart_stops_parsing() -> None: - """新しいセクション開始で deps 解析が終了し、dev deps が混入しないこと。""" - content = ( - "[tool.poetry.dependencies]\n" - "fastapi = \"^0.109.0\"\n" - "\n" - "[tool.poetry.dev-dependencies]\n" - "pytest = \"^7.0\"\n" - ) - result = parse_pyproject_toml(content) - assert "fastapi" in result - assert "pytest" not in result - - -def test_parse_pyproject_toml_no_deps_section() -> None: - """deps セクションが存在しない pyproject.toml では空リストが返ること。""" - content = "[tool.ruff]\nline-length = 100\n" - result = parse_pyproject_toml(content) - assert result == [] - - -def test_detect_devtools_empty_input() -> None: - """空リストを渡した場合は空リストが返ること。""" - result = detect_devtools_from_root_files([]) - assert result == [] - - -def test_detect_devtools_unknown_file() -> None: - """未知のファイル名は無視されること。""" - result = detect_devtools_from_root_files(["unknown_file.xyz"]) - assert result == [] - - -# ── requirements.txt パーステスト ──────────────────────────────────────── - - -def test_parse_requirements_txt_basic() -> None: - """基本的なパッケージ名が抽出されること。""" - content = "fastapi>=0.100\nuvicorn\nsqlalchemy==2.0\n" - result = parse_requirements_txt(content) - assert "fastapi" in result - assert "uvicorn" in result - assert "sqlalchemy" in result - - -def test_parse_requirements_txt_ignores_comments() -> None: - """コメント行は無視されること。""" - content = "# コメント\nfastapi\n" - result = parse_requirements_txt(content) - assert "fastapi" in result - assert len([r for r in result if r.startswith("#")]) == 0 - - -def test_parse_requirements_txt_empty_input() -> None: - """空文字列では空リストが返ること。""" - result = parse_requirements_txt("") - assert result == [] - - -# ── package.json パーステスト ──────────────────────────────────────────── - - -def test_parse_package_json_dependencies_and_dev() -> None: - """dependencies と devDependencies の両方が抽出されること。""" - content = '{"dependencies": {"react": "^18"}, "devDependencies": {"jest": "^29"}}' - result = parse_package_json(content) - assert "react" in result - assert "jest" in result - - -def test_parse_package_json_invalid_json() -> None: - """不正な JSON では空リストが返ること。""" - result = parse_package_json("not json") - assert result == [] - - -def test_parse_package_json_empty_deps() -> None: - """依存関係が空の場合は空リストが返ること。""" - result = parse_package_json('{"name": "my-app"}') - assert result == [] - - -# ── detect_from_dependencies テスト(Issue #203) ──────────────────────── - - -def test_detect_from_dependencies_react_and_nextjs() -> None: - """react と next が React / Next.js として検出されること。""" - result = detect_from_dependencies(["react", "react-dom", "next"]) - assert "React" in result - assert "Next.js" in result - # react と react-dom はどちらも React にマップされ重複しないこと - assert result.count("React") == 1 - - -def test_detect_from_dependencies_fastapi() -> None: - """fastapi が FastAPI として検出されること。""" - result = detect_from_dependencies(["fastapi", "uvicorn"]) - assert "FastAPI" in result - - -def test_detect_from_dependencies_case_insensitive() -> None: - """大文字混じりの依存名でも検出できること。""" - result = detect_from_dependencies(["Django", "Spring-Boot"]) - assert "Django" in result - assert "Spring Boot" in result - - -def test_detect_from_dependencies_unknown_ignored() -> None: - """マッピングに無い依存は無視されること。""" - result = detect_from_dependencies(["unknown-lib", "my-internal-pkg"]) - assert result == [] - - -def test_detect_from_dependencies_empty() -> None: - """空リストでは空リストが返ること。""" - assert detect_from_dependencies([]) == [] - - -# ── merge_frameworks テスト ───────────────────────────────────────────── - - -def test_merge_frameworks_preserves_order_and_dedupes() -> None: - """複数ソースの framework リストを順序保持してマージ・重複除去できること。""" - root_fw = ["Docker", "GitHub Actions"] - dep_fw = ["React", "Docker", "FastAPI"] - result = merge_frameworks(root_fw, dep_fw) - # root 側の順序が先 - assert result.index("Docker") < result.index("React") - # 重複は 1 回だけ - assert result.count("Docker") == 1 - assert set(result) == {"Docker", "GitHub Actions", "React", "FastAPI"} - - -def test_merge_frameworks_empty_inputs() -> None: - """空リストのみでも空リストが返ること。""" - assert merge_frameworks([], []) == [] diff --git a/backend/tests/test_worker/test_github_link.py b/backend/tests/test_worker/test_github_link.py index c816a926..274739e3 100644 --- a/backend/tests/test_worker/test_github_link.py +++ b/backend/tests/test_worker/test_github_link.py @@ -37,11 +37,6 @@ def _sample_repos(self): fork=False, stargazers_count=0, default_branch="main", - dependencies=[], - root_files=[], - detected_frameworks=[], - detected_devtools=[], - detected_infras=[], ) ] @@ -57,9 +52,9 @@ def test_status_transitions_to_completed(self, db_session: Session, session_fact return_value=repos, ), patch( - "app.services.intelligence.github_link_service.fetch_contribution_calendar", + "app.services.intelligence.github_link_service.fetch_all_contribution_calendars", new_callable=AsyncMock, - return_value=None, + return_value=(True, []), ), patch( "app.services.progress_service.set_progress", @@ -103,10 +98,13 @@ def test_completed_persists_mapped_result_content( repos = self._sample_repos() # コントリビューション取得が成功するケース。これにより新たな警告は出ず、 # 前回の warning_message がクリアされることを純粋に検証できる。 - calendar = ContributionCalendar( - total_contributions=7, - weeks=[[ContributionDay(date="2024-03-01", count=3, level=2)]], - ) + calendars = [ + ContributionCalendar( + year=2024, + total_contributions=7, + weeks=[[ContributionDay(date="2024-03-01", count=3, level=2)]], + ) + ] sentinel_result = { "skills": [{"name": "Python", "score": 80}], "summary": "集計結果", @@ -121,9 +119,9 @@ def test_completed_persists_mapped_result_content( return_value=repos, ), patch( - "app.services.intelligence.github_link_service.fetch_contribution_calendar", + "app.services.intelligence.github_link_service.fetch_all_contribution_calendars", new_callable=AsyncMock, - return_value=calendar, + return_value=(True, calendars), ), patch("app.services.progress_service.set_progress", new_callable=AsyncMock), patch( @@ -170,10 +168,13 @@ def test_contribution_calendar_persisted_in_result( user, cache = self._make_user_and_cache(db_session, "calendar-user") repos = self._sample_repos() - calendar = ContributionCalendar( - total_contributions=7, - weeks=[[ContributionDay(date="2024-03-01", count=3, level=2)]], - ) + calendars = [ + ContributionCalendar( + year=2024, + total_contributions=7, + weeks=[[ContributionDay(date="2024-03-01", count=3, level=2)]], + ) + ] with ( patch( @@ -182,9 +183,9 @@ def test_contribution_calendar_persisted_in_result( return_value=repos, ), patch( - "app.services.intelligence.github_link_service.fetch_contribution_calendar", + "app.services.intelligence.github_link_service.fetch_all_contribution_calendars", new_callable=AsyncMock, - return_value=calendar, + return_value=(True, calendars), ), patch("app.services.progress_service.set_progress", new_callable=AsyncMock), patch( @@ -207,14 +208,15 @@ def test_contribution_calendar_persisted_in_result( db_session.refresh(cache) assert cache.status == "completed" assert cache.result is not None - assert cache.result["contribution_calendar"]["total_contributions"] == 7 - assert cache.result["contribution_calendar"]["weeks"][0][0]["level"] == 2 + assert cache.result["contribution_calendars"][0]["year"] == 2024 + assert cache.result["contribution_calendars"][0]["total_contributions"] == 7 + assert cache.result["contribution_calendars"][0]["weeks"][0][0]["level"] == 2 assert cache.warning_message is None def test_contribution_fetch_failure_sets_warning( self, db_session: Session, session_factory ): - """コントリビューション取得失敗(None)でも連携は completed のままで、 + """コントリビューション取得失敗(success=False)でも連携は completed のままで、 warning_message が立つこと。""" user, cache = self._make_user_and_cache(db_session, "warn-user") repos = self._sample_repos() @@ -226,9 +228,9 @@ def test_contribution_fetch_failure_sets_warning( return_value=repos, ), patch( - "app.services.intelligence.github_link_service.fetch_contribution_calendar", + "app.services.intelligence.github_link_service.fetch_all_contribution_calendars", new_callable=AsyncMock, - return_value=None, + return_value=(False, []), ), patch("app.services.progress_service.set_progress", new_callable=AsyncMock), patch( @@ -251,9 +253,51 @@ def test_contribution_fetch_failure_sets_warning( db_session.refresh(cache) assert cache.status == "completed" assert cache.result is not None - assert cache.result["contribution_calendar"] is None + assert cache.result["contribution_calendars"] == [] assert cache.warning_message is not None + def test_no_contributions_does_not_warn( + self, db_session: Session, session_factory + ): + """取得成功でカレンダーが空(貢献年ゼロ)なら、warning_message を立てないこと。""" + user, cache = self._make_user_and_cache(db_session, "no-contrib-user") + repos = self._sample_repos() + + with ( + patch( + "app.services.intelligence.github_link_service.collect_repos", + new_callable=AsyncMock, + return_value=repos, + ), + patch( + "app.services.intelligence.github_link_service.fetch_all_contribution_calendars", + new_callable=AsyncMock, + return_value=(True, []), + ), + patch("app.services.progress_service.set_progress", new_callable=AsyncMock), + patch( + "app.services.intelligence.github_link_service.decrypt_field", + return_value="token123", + ), + ): + _run( + _run_github_link( + session_factory, + { + "user_id": user.id, + "github_username": "gh-user", + "github_token": "encrypted_token", + "include_forks": False, + }, + ) + ) + + db_session.refresh(cache) + assert cache.status == "completed" + assert cache.result is not None + assert cache.result["contribution_calendars"] == [] + assert cache.warning_message is None + def test_status_transitions_to_processing_at_start( self, db_session: Session, session_factory ): diff --git a/frontend/e2e/career-field-modal.spec.ts b/frontend/e2e/career-field-modal.spec.ts index 93378efc..c287d969 100644 --- a/frontend/e2e/career-field-modal.spec.ts +++ b/frontend/e2e/career-field-modal.spec.ts @@ -70,7 +70,8 @@ test.describe("職務経歴書 自己PR・職務要約モーダル", () => { await expect(textarea).toHaveCount(0); // フォーム側プレビューに編集値が反映されている(即時に formCache へ反映)。 - await expect(page.getByText("新しい 自己PR 本文")).toBeVisible(); + // プレビューは Markdown 記法除去後の先頭10文字まで(超過分は省略記号)。 + await expect(page.getByText("新しい 自己PR 本…", { exact: true })).toBeVisible(); }); test("必須未入力で保存すると職務要約モーダルが自動で開く", async ({ page }) => { diff --git a/frontend/e2e/career-proofread.spec.ts b/frontend/e2e/career-proofread.spec.ts deleted file mode 100644 index 070cf586..00000000 --- a/frontend/e2e/career-proofread.spec.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { test, expect, type Page } from "@playwright/test"; -import { setupAuth, waitForAuthenticatedLayout } from "./helpers/auth"; - -/** - * 職務経歴書 保存時の文章校正(誤字脱字・表記ゆれ)E2E。 - * - * 検証: - * - 保存確認ダイアログ(左右 diff モーダル)に「校正の指摘」セクションが出る。 - * - 自己PR の表記ゆれ("javascript" → "JavaScript")が prh で実際に検出され、 - * worker(textlint + kuromoji 辞書ロード)経由で指摘が画面に出る。 - * - * 校正はフロント完結(API なし)なので backend モック不要。worker が public/kuromoji-dict/ - * から辞書をロードするため、指摘表示には余裕を持った待機を取る。 - */ -async function setupResumeApi(page: Page) { - const baseResume = { - id: "resume-1", - full_name: "山田 太郎", - career_summary: "サマリー", - // 表記ゆれ(javascript)を含む自己PR。 - self_pr: "私はjavascriptが得意です。", - experiences: [], - qualifications: [], - }; - - await page.route("**/api/master-data/qualification", (route) => - route.fulfill({ status: 200, contentType: "application/json", body: "[]" }), - ); - await page.route("**/api/master-data/technology-stack", (route) => - route.fulfill({ status: 200, contentType: "application/json", body: "[]" }), - ); - await page.route("**/api/resumes/preview", (route) => - route.fulfill({ - status: 200, - contentType: "application/json", - body: JSON.stringify({ html: "
preview
", css: "" }), - }), - ); - await page.route("**/api/resumes/latest", (route) => - route.fulfill({ - status: 200, - contentType: "application/json", - body: JSON.stringify(baseResume), - }), - ); -} - -test.describe("職務経歴書 保存時の文章校正", () => { - test.beforeEach(async ({ page }) => { - await setupAuth(page); - await setupResumeApi(page); - }); - - test("保存確認ダイアログに校正の指摘(表記ゆれ)が表示される", async ({ page }) => { - await page.goto("/career"); - await waitForAuthenticatedLayout(page); - - // 変更を作って確認ダイアログを開く(氏名を編集)。 - await page.getByPlaceholder("例: 山田 太郎").fill("佐藤 花子"); - await page.getByRole("button", { name: /更新する|保存する/ }).click(); - - // ダイアログと統合レビュー一覧(変更点+校正)が出る。 - await expect(page.getByRole("dialog", { name: "変更内容の確認" })).toBeVisible(); - await expect(page.getByText("変更点・校正", { exact: true })).toBeVisible(); - - // prh による表記ゆれ指摘(javascript => JavaScript)が worker 経由で表示される。 - // 辞書ロードを含むため待機時間を長めに取る。 - await expect(page.getByText(/JavaScript/)).toBeVisible({ timeout: 30000 }); - - // 校正があっても保存はブロックされない(「この内容で保存」が押せる)。 - await expect(page.getByRole("button", { name: "この内容で保存" })).toBeEnabled(); - }); -}); diff --git a/frontend/e2e/github-link.spec.ts b/frontend/e2e/github-link.spec.ts index f9e14526..4f593fdd 100644 --- a/frontend/e2e/github-link.spec.ts +++ b/frontend/e2e/github-link.spec.ts @@ -2,94 +2,18 @@ import { test, expect } from "@playwright/test"; import { setupAuth, waitForAuthenticatedLayout } from "./helpers/auth"; /** - * GitHub 連携ダッシュボードでの検出フレームワーク表示 E2E テスト(Issue #203) + * GitHub 連携ダッシュボードでのコントリビューションヒートマップ(年次表示)E2E テスト */ -test.describe("GitHub 連携 - 検出フレームワーク表示", () => { +test.describe("GitHub 連携 - コントリビューションヒートマップ", () => { test.beforeEach(async ({ page }) => { await setupAuth(page); }); - test("検出フレームワークが Frameworks セクションに表示される", async ({ + test("年セレクタで年を切り替えるとヒートマップの集計が切り替わる", async ({ page, }) => { // 連携キャッシュのモックを登録(キャッチオールより後 = 優先される) - await page.route("**/api/github-link/cache", (route) => - route.fulfill({ - status: 200, - contentType: "application/json", - body: JSON.stringify({ - status: "completed", - result: { - username: "e2e-test-user", - repos_analyzed: 3, - unique_skills: 5, - analyzed_at: "2026-04-24T00:00:00Z", - languages: { TypeScript: 60000, Python: 40000 }, - detected_frameworks: { React: 5, "Next.js": 3, FastAPI: 2 }, - position_scores: null, - }, - position_advice: null, - }), - }), - ); - - await page.goto("/github_link"); - await waitForAuthenticatedLayout(page); - - // ダッシュボードが表示されること - await expect( - page.getByRole("heading", { name: "e2e-test-user の連携結果" }), - ).toBeVisible(); - - // Frameworks セクションが存在し、各 framework 名が表示されること - await expect( - page.getByRole("heading", { name: "Frameworks" }), - ).toBeVisible(); - const list = page.getByRole("list", { name: "検出フレームワーク一覧" }); - await expect(list).toBeVisible(); - await expect(list.getByText("React")).toBeVisible(); - await expect(list.getByText("Next.js")).toBeVisible(); - await expect(list.getByText("FastAPI")).toBeVisible(); - }); - - test("検出フレームワークが空のとき Frameworks セクションが出ない", async ({ - page, - }) => { - await page.route("**/api/github-link/cache", (route) => - route.fulfill({ - status: 200, - contentType: "application/json", - body: JSON.stringify({ - status: "completed", - result: { - username: "e2e-test-user", - repos_analyzed: 1, - unique_skills: 0, - analyzed_at: "2026-04-24T00:00:00Z", - languages: { TypeScript: 100 }, - detected_frameworks: [], - position_scores: null, - }, - position_advice: null, - }), - }), - ); - - await page.goto("/github_link"); - await waitForAuthenticatedLayout(page); - - await expect( - page.getByRole("heading", { name: "e2e-test-user の連携結果" }), - ).toBeVisible(); - await expect( - page.getByRole("heading", { name: "Frameworks" }), - ).not.toBeVisible(); - }); - - test("コントリビューションカレンダーがあると Activity ヒートマップが表示される", async ({ - page, - }) => { await page.route("**/api/github-link/cache", (route) => route.fulfill({ status: 200, @@ -102,13 +26,18 @@ test.describe("GitHub 連携 - 検出フレームワーク表示", () => { unique_skills: 3, analyzed_at: "2026-04-24T00:00:00Z", languages: { TypeScript: 100 }, - detected_frameworks: {}, - detected_devtools: {}, - detected_infras: {}, - contribution_calendar: { - total_contributions: 256, - weeks: [[{ date: "2025-01-06", count: 4, level: 2 }]], - }, + contribution_calendars: [ + { + year: 2025, + total_contributions: 256, + weeks: [[{ date: "2025-01-06", count: 4, level: 2 }]], + }, + { + year: 2024, + total_contributions: 120, + weeks: [[{ date: "2024-01-01", count: 2, level: 1 }]], + }, + ], }, }), }), @@ -117,12 +46,23 @@ test.describe("GitHub 連携 - 検出フレームワーク表示", () => { await page.goto("/github_link"); await waitForAuthenticatedLayout(page); + // ダッシュボードが表示されること await expect( - page.getByRole("heading", { name: "Activity" }), + page.getByRole("heading", { name: "e2e-test-user の連携結果" }), ).toBeVisible(); - await expect(page.getByText("年間コントリビュート")).toBeVisible(); + + // デフォルトは最新年(2025) + await expect(page.getByRole("heading", { name: "Activity" })).toBeVisible(); + await expect(page.getByText("2025年のコントリビュート")).toBeVisible(); await expect(page.getByText("256")).toBeVisible(); + // 年セレクタで 2024 に切り替えると集計が切り替わる + await page + .getByRole("combobox", { name: "表示する年" }) + .selectOption("2024"); + await expect(page.getByText("2024年のコントリビュート")).toBeVisible(); + await expect(page.getByText("120")).toBeVisible(); + // ページは表示専用。連携トリガーボタン(更新する/再連携)は無い await expect( page.getByRole("button", { name: "更新する" }), diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 5c23e6c3..1995faa9 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -9,20 +9,15 @@ "version": "0.1.0", "dependencies": { "@reduxjs/toolkit": "^2.11.2", - "@textlint/kernel": "^15.7.1", - "@textlint/textlint-plugin-text": "^15.7.1", "dompurify": "^3.4.7", "marked": "^17.0.4", - "prh": "^6.0.6", "react": "^18.3.1", "react-dom": "^18.3.1", "react-pdf": "^9.2.1", "react-redux": "^9.2.0", "react-router-dom": "^7.13.1", "recharts": "^3.8.0", - "redux-persist": "^6.0.0", - "textlint-rule-preset-ja-technical-writing": "^12.0.2", - "textlint-rule-prh": "^6.1.0" + "redux-persist": "^6.0.0" }, "devDependencies": { "@eslint/js": "^9.39.4", @@ -30,13 +25,11 @@ "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.3.2", "@testing-library/user-event": "^14.6.1", - "@textlint/types": "^15.7.1", "@types/node": "^25.5.2", "@types/react": "^18.3.15", "@types/react-dom": "^18.3.1", "@vitejs/plugin-react": "^4.4.1", "@vitest/coverage-v8": "^4.1.2", - "assert": "^2.1.0", "concurrently": "^9.2.1", "eslint": "^9.39.4", "eslint-config-prettier": "^10.1.8", @@ -47,8 +40,6 @@ "jsdom": "^29.0.1", "msw": "^2.13.2", "openapi-typescript": "^7.13.0", - "os-browserify": "^0.3.0", - "path-browserify": "^1.0.1", "playwright": "^1.56.0", "prettier": "^3.8.1", "typescript": "^5.7.2", @@ -272,6 +263,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -281,6 +273,7 @@ "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -314,6 +307,7 @@ "version": "7.29.0", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.29.0" @@ -405,6 +399,7 @@ "version": "7.29.0", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", @@ -2007,55 +2002,6 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@kvs/env": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/@kvs/env/-/env-2.2.2.tgz", - "integrity": "sha512-tvy6eb1IHiQWqgSBjOxJp+gQ8wI/1bT5pWKU0VJL9d8SwAflOpN9777wQ3RY3jUtEUpcQOUtxrxcrMs9VYiNBw==", - "license": "MIT", - "dependencies": { - "@kvs/indexeddb": "^2.2.2", - "@kvs/node-localstorage": "^2.2.2", - "@kvs/storage": "^2.2.2", - "@kvs/types": "^2.2.2" - } - }, - "node_modules/@kvs/indexeddb": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/@kvs/indexeddb/-/indexeddb-2.2.2.tgz", - "integrity": "sha512-h9Fom6YvbRzMHhBLZKdNNJ/bgla0aBbmVBzUBLUoeEwT3C16Yx5eGA+spUQ69ZoRbsArZufX84qX/wRbyvjtrg==", - "license": "MIT", - "dependencies": { - "@kvs/storage": "^2.2.2", - "@kvs/types": "^2.2.2" - } - }, - "node_modules/@kvs/node-localstorage": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/@kvs/node-localstorage/-/node-localstorage-2.2.2.tgz", - "integrity": "sha512-e6r2CBWAQznaLEAmYSNNPGXUkSRP+yffSZNJYfgR4II8vjDFtGj1gByo2yk7pQsRiiId6IZ5dwDl1Tib3bNaqw==", - "license": "MIT", - "dependencies": { - "@kvs/storage": "^2.2.2", - "@kvs/types": "^2.2.2", - "app-root-path": "^3.1.0", - "node-localstorage": "^2.1.6" - } - }, - "node_modules/@kvs/storage": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/@kvs/storage/-/storage-2.2.2.tgz", - "integrity": "sha512-jThnhLTcT0w1SHzhr/PFTzzkKl+V3J6X07NyARceT02mIu5+tBWwtUhS68UNOaD7CTQ07rMFgb5UHoolies94g==", - "license": "MIT", - "dependencies": { - "@kvs/types": "^2.2.2" - } - }, - "node_modules/@kvs/types": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/@kvs/types/-/types-2.2.2.tgz", - "integrity": "sha512-5lmBuah+wOdypBf0O9t6BoHrnq5hX5MlhEPTDPBNKN38ayQwVc5gYgJOBR3Dg4EF7OwL+1c7JNtWXQEJXXVXlg==", - "license": "MIT" - }, "node_modules/@mswjs/interceptors": { "version": "0.41.3", "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.41.3.tgz", @@ -2748,176 +2694,6 @@ "@testing-library/dom": ">=7.21.4" } }, - "node_modules/@textlint-rule/textlint-rule-no-invalid-control-character": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@textlint-rule/textlint-rule-no-invalid-control-character/-/textlint-rule-no-invalid-control-character-3.0.0.tgz", - "integrity": "sha512-2o9n4z49ntSPtJPlcJtxakyB4dAg2MKSvR9ZCZEHjye0ee27oWYzK6yHz2HjsXQqt9VeCwxNHDOIGIx2CQX0Dw==", - "license": "MIT" - }, - "node_modules/@textlint-rule/textlint-rule-no-unmatched-pair": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@textlint-rule/textlint-rule-no-unmatched-pair/-/textlint-rule-no-unmatched-pair-2.0.4.tgz", - "integrity": "sha512-g9Ge1xUV9xJy8T7nuutF/2J6Cg2mmPx4gKsC3dCdxVxuL0wMqOOnAi8l6psFpAQ5UFtQuAzwkdclrehPtBT5tg==", - "license": "MIT", - "dependencies": { - "sentence-splitter": "^5.0.0", - "textlint-rule-helper": "^2.3.1" - } - }, - "node_modules/@textlint/ast-node-types": { - "version": "15.7.1", - "resolved": "https://registry.npmjs.org/@textlint/ast-node-types/-/ast-node-types-15.7.1.tgz", - "integrity": "sha512-Wii5UgUKFEh9Uv6wbq1zr4/Kf+dtjiUuzPrrXzKp8H+ifkvKNzi23V4Nz+6wVyHQn5T28AFuc8VH8OtzvGYecA==", - "license": "MIT" - }, - "node_modules/@textlint/ast-tester": { - "version": "15.7.1", - "resolved": "https://registry.npmjs.org/@textlint/ast-tester/-/ast-tester-15.7.1.tgz", - "integrity": "sha512-0CZWFKB7D21y8LA4Dv5irOX1/iGDGwM4OTaW7PxJpfRhXCL40uMOhS+P+1bjFmpSkM/DF/5HPRph0E744iJobw==", - "license": "MIT", - "dependencies": { - "@textlint/ast-node-types": "15.7.1", - "debug": "^4.4.3" - } - }, - "node_modules/@textlint/ast-traverse": { - "version": "15.7.1", - "resolved": "https://registry.npmjs.org/@textlint/ast-traverse/-/ast-traverse-15.7.1.tgz", - "integrity": "sha512-tFgFiSbh6fdQo7MF4FYQoQBHqM8BoTEfvubGXm7Xi65QRYefNz+iuXYaoyto6OlMj5M95u9Gk/Ni7577rZ8uow==", - "license": "MIT", - "dependencies": { - "@textlint/ast-node-types": "15.7.1" - } - }, - "node_modules/@textlint/feature-flag": { - "version": "15.7.1", - "resolved": "https://registry.npmjs.org/@textlint/feature-flag/-/feature-flag-15.7.1.tgz", - "integrity": "sha512-ZFIJ2edV7K04UJ7/7ptvL61vs54MHYzDaIfTW+vaXwq5KDeCD2QyjsKt+TbOHQ8n8sW6fO171n4p0gwRo01Ojg==", - "license": "MIT" - }, - "node_modules/@textlint/kernel": { - "version": "15.7.1", - "resolved": "https://registry.npmjs.org/@textlint/kernel/-/kernel-15.7.1.tgz", - "integrity": "sha512-OGghiMbXLD3ULHedZYP1B+A8Bj4snlCatzsOdz0M8q0v4I79NdcamWt/5GYmbp6AOQg3HBQG8+9V+0H+do19mw==", - "license": "MIT", - "dependencies": { - "@textlint/ast-node-types": "15.7.1", - "@textlint/ast-tester": "15.7.1", - "@textlint/ast-traverse": "15.7.1", - "@textlint/feature-flag": "15.7.1", - "@textlint/source-code-fixer": "15.7.1", - "@textlint/types": "15.7.1", - "@textlint/utils": "15.7.1", - "debug": "^4.4.3", - "fast-equals": "^4.0.3", - "structured-source": "^4.0.0" - } - }, - "node_modules/@textlint/markdown-to-ast": { - "version": "13.4.1", - "resolved": "https://registry.npmjs.org/@textlint/markdown-to-ast/-/markdown-to-ast-13.4.1.tgz", - "integrity": "sha512-jUa5bTNmxjEgfCXW4xfn7eSJqzUXyNKiIDWLKtI4MUKRNhT3adEaa/NuQl0Mii3Hu3HraZR7hYhRHLh+eeM43w==", - "license": "MIT", - "dependencies": { - "@textlint/ast-node-types": "^13.4.1", - "debug": "^4.3.4", - "mdast-util-gfm-autolink-literal": "^0.1.3", - "remark-footnotes": "^3.0.0", - "remark-frontmatter": "^3.0.0", - "remark-gfm": "^1.0.0", - "remark-parse": "^9.0.0", - "traverse": "^0.6.7", - "unified": "^9.2.2" - } - }, - "node_modules/@textlint/markdown-to-ast/node_modules/@textlint/ast-node-types": { - "version": "13.4.1", - "resolved": "https://registry.npmjs.org/@textlint/ast-node-types/-/ast-node-types-13.4.1.tgz", - "integrity": "sha512-qrZyhCh8Ekk6nwArx3BROybm9BnX6vF7VcZbijetV/OM3yfS4rTYhoMWISmhVEP2H2re0CtWEyMl/XF+WdvVLQ==", - "license": "MIT" - }, - "node_modules/@textlint/module-interop": { - "version": "14.8.4", - "resolved": "https://registry.npmjs.org/@textlint/module-interop/-/module-interop-14.8.4.tgz", - "integrity": "sha512-1LdPYLAVpa27NOt6EqvuFO99s4XLB0c19Hw9xKSG6xQ1K82nUEyuWhzTQKb3KJ5Qx7qj14JlXZLfnEuL6A16Bw==", - "license": "MIT" - }, - "node_modules/@textlint/regexp-string-matcher": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@textlint/regexp-string-matcher/-/regexp-string-matcher-1.1.1.tgz", - "integrity": "sha512-rrNUCKGKYBrZALotSF8D5A8xD05VHX6kxv0BP805Ig2M73Ha6LK+de31+ZocGm4CO+sikVFYyMCPPJhp7bCXcw==", - "license": "MIT", - "dependencies": { - "escape-string-regexp": "^2.0.0", - "execall": "^2.0.0", - "lodash.sortby": "^4.7.0", - "lodash.uniq": "^4.5.0", - "lodash.uniqwith": "^4.5.0", - "to-regex": "^3.0.2" - } - }, - "node_modules/@textlint/regexp-string-matcher/node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@textlint/source-code-fixer": { - "version": "15.7.1", - "resolved": "https://registry.npmjs.org/@textlint/source-code-fixer/-/source-code-fixer-15.7.1.tgz", - "integrity": "sha512-Z8ZfQQbVH2GIUMzSGEQWKKnAFCH+mbQtw5ipigFHExUImx2RC2ppaR1m8ZAy7SYgbXGhvfkPKZ4ndEmVWTs5Mg==", - "license": "MIT", - "dependencies": { - "@textlint/types": "15.7.1", - "debug": "^4.4.3" - } - }, - "node_modules/@textlint/text-to-ast": { - "version": "15.7.1", - "resolved": "https://registry.npmjs.org/@textlint/text-to-ast/-/text-to-ast-15.7.1.tgz", - "integrity": "sha512-W6AYCOcEAtw9hs0u69+Tte8hmWaLKYGdo3yBxgOpl20Q6AOkxxEaG305Nr5rgbO6caWJWkR/t2WxBkZzm3KG0A==", - "license": "MIT", - "dependencies": { - "@textlint/ast-node-types": "15.7.1" - } - }, - "node_modules/@textlint/textlint-plugin-markdown": { - "version": "13.4.1", - "resolved": "https://registry.npmjs.org/@textlint/textlint-plugin-markdown/-/textlint-plugin-markdown-13.4.1.tgz", - "integrity": "sha512-OcLkFKYmbYeGJ0kj2487qcicCYTiE2vJLwfPcUDJrNoMYak5JtvHJfWffck8gON2mEM00DPkHH0UdxZpFjDfeg==", - "license": "MIT", - "dependencies": { - "@textlint/markdown-to-ast": "^13.4.1" - } - }, - "node_modules/@textlint/textlint-plugin-text": { - "version": "15.7.1", - "resolved": "https://registry.npmjs.org/@textlint/textlint-plugin-text/-/textlint-plugin-text-15.7.1.tgz", - "integrity": "sha512-krU0KiDmG2LrESF3LchgfbSwclB/8I2itA4uzSY9Tln3miaWGKzUpLu00bQtvua0f2Ux7tsLnSmwAgGFf9gERA==", - "license": "MIT", - "dependencies": { - "@textlint/text-to-ast": "15.7.1", - "@textlint/types": "15.7.1" - } - }, - "node_modules/@textlint/types": { - "version": "15.7.1", - "resolved": "https://registry.npmjs.org/@textlint/types/-/types-15.7.1.tgz", - "integrity": "sha512-Vye/GmFNBTgVzZFtIFJTmLB+s2A7oIADxNG6r9UhfPuY+Czv0z5G3xeyFZZudPlfxURsKUyPIU5XsjOFqVp33A==", - "license": "MIT", - "dependencies": { - "@textlint/ast-node-types": "15.7.1" - } - }, - "node_modules/@textlint/utils": { - "version": "15.7.1", - "resolved": "https://registry.npmjs.org/@textlint/utils/-/utils-15.7.1.tgz", - "integrity": "sha512-+q5Z5fsNk/ixDY5D70ZCQungr7ppzBAAhmi207qDBzSBFeA5MM2ASGwMjPc5aMJ/3DvonI/01B3UIgbpVgIXVA==", - "license": "MIT" - }, "node_modules/@types/aria-query": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", @@ -3076,15 +2852,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/mdast": { - "version": "3.0.15", - "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.15.tgz", - "integrity": "sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==", - "license": "MIT", - "dependencies": { - "@types/unist": "^2" - } - }, "node_modules/@types/node": { "version": "25.5.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.2.tgz", @@ -3137,12 +2904,6 @@ "license": "MIT", "optional": true }, - "node_modules/@types/unist": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", - "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", - "license": "MIT" - }, "node_modules/@types/use-sync-external-store": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", @@ -3673,15 +3434,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/analyze-desumasu-dearu": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/analyze-desumasu-dearu/-/analyze-desumasu-dearu-5.0.2.tgz", - "integrity": "sha512-3PUxFk790GpQkME//hwiJellbtKMiAFX/CyA93etmAo5FujJ+5GKVXW+NU9v2MfF07iWcXsrNMbGW5/vVpqWwA==", - "license": "MIT", - "dependencies": { - "kuromojin": "^3.0.0" - } - }, "node_modules/ansi-colors": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", @@ -3718,19 +3470,11 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/app-root-path": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-3.1.0.tgz", - "integrity": "sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA==", - "license": "MIT", - "engines": { - "node": ">= 6.0.0" - } - }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, "license": "Python-2.0" }, "node_modules/aria-query": { @@ -3743,57 +3487,6 @@ "dequal": "^2.0.3" } }, - "node_modules/array-buffer-byte-length": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", - "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "is-array-buffer": "^3.0.5" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", - "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", - "license": "MIT", - "dependencies": { - "array-buffer-byte-length": "^1.0.1", - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "is-array-buffer": "^3.0.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/assert": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/assert/-/assert-2.1.0.tgz", - "integrity": "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "is-nan": "^1.3.2", - "object-is": "^1.1.5", - "object.assign": "^4.1.4", - "util": "^0.12.5" - } - }, "node_modules/assertion-error": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", @@ -3804,15 +3497,6 @@ "node": ">=12" } }, - "node_modules/assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/ast-v8-to-istanbul": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.0.tgz", @@ -3832,49 +3516,6 @@ "dev": true, "license": "MIT" }, - "node_modules/async": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", - "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", - "license": "MIT", - "dependencies": { - "lodash": "^4.17.14" - } - }, - "node_modules/async-function": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", - "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/available-typed-arrays": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", - "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", - "license": "MIT", - "dependencies": { - "possible-typed-array-names": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/bail": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz", - "integrity": "sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -3970,12 +3611,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/boundary": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/boundary/-/boundary-2.0.0.tgz", - "integrity": "sha512-rJKn5ooC9u8q13IMCrW0RSp31pxBCHE3y9V/tp3TdWSLf8Em3p6Di4NBpfzbJge9YjjFEsD0RtFEjtvHL5VyEA==", - "license": "BSD-2-Clause" - }, "node_modules/brace-expansion": { "version": "1.1.14", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", @@ -4069,28 +3704,11 @@ "node": ">= 0.8" } }, - "node_modules/call-bind": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", - "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "get-intrinsic": "^1.3.0", - "set-function-length": "^1.2.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -4104,6 +3722,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -4162,16 +3781,6 @@ "node": "^18.12.0 || >= 20.9.0" } }, - "node_modules/ccount": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ccount/-/ccount-1.1.0.tgz", - "integrity": "sha512-vlNK021QdI7PNeiUh/lKkC/mNHHfV0m/Ad5JoI0TYtlBnJAslM/JIkm/tGC88bkLIwO6OQ5uV6ztS6kVAtCDlg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/chai": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", @@ -4206,51 +3815,6 @@ "dev": true, "license": "MIT" }, - "node_modules/character-entities": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz", - "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-entities-legacy": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", - "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-reference-invalid": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", - "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/check-ends-with-period": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/check-ends-with-period/-/check-ends-with-period-3.0.2.tgz", - "integrity": "sha512-/Bw+avucqqZ7PjKCVDod1QDGyZjo7Ht2701pdgcpTXzK5jI73/OUh3VR+m18jNUoJx5DSOUv0AxELZF7FYtcDA==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.1.0" - } - }, - "node_modules/check-ends-with-period/node_modules/emoji-regex": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", - "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", - "license": "MIT" - }, "node_modules/chownr": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", @@ -4301,18 +3865,6 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/clone-regexp": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clone-regexp/-/clone-regexp-2.2.0.tgz", - "integrity": "sha512-beMpP7BOtTipFuW8hrJvREQ2DrRu3BE7by0ZpibtfBA+qfHYvMGTc2Yb1JMYPKg/JUw0CHYvpg796aNTSW9z7Q==", - "license": "MIT", - "dependencies": { - "is-regexp": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/clsx": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", @@ -4349,31 +3901,6 @@ "dev": true, "license": "MIT" }, - "node_modules/comma-separated-tokens": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz", - "integrity": "sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/commander": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", - "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", - "license": "MIT", - "engines": { - "node": ">=20" - } - }, - "node_modules/commandpost": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/commandpost/-/commandpost-1.4.0.tgz", - "integrity": "sha512-aE2Y4MTFJ870NuB/+2z1cXBhSBBzRydVVjzhFC4gtenEhpnj15yu0qptWGJsO9YGrcPZ3ezX8AWb1VA391MKpQ==", - "license": "MIT" - }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -4654,67 +4181,17 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, - "node_modules/data-view-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", - "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, "license": "MIT", "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.2" + "ms": "^2.1.3" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/data-view-byte-length": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", - "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/inspect-js" - } - }, - "node_modules/data-view-byte-offset": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", - "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" + "node": ">=6.0" }, "peerDependenciesMeta": { "supports-color": { @@ -4768,53 +4245,6 @@ "dev": true, "license": "MIT" }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/define-properties": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "license": "MIT", - "dependencies": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "license": "MIT", - "dependencies": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -4844,15 +4274,6 @@ "node": ">=8" } }, - "node_modules/diff": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-9.0.0.tgz", - "integrity": "sha512-svtcdpS8CgJyqAjEQIXdb3OjhFVVYjzGAPO8WGCmRbrml64SPw/jJD4GoE98aR7r25A0XcgrK3F02yw9R/vhQw==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, "node_modules/dom-accessibility-api": { "version": "0.5.16", "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", @@ -4870,16 +4291,11 @@ "@types/trusted-types": "^2.0.7" } }, - "node_modules/doublearray": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/doublearray/-/doublearray-0.0.2.tgz", - "integrity": "sha512-aw55FtZzT6AmiamEj2kvmR6BuFqvYgKZUkfQ7teqVRNqD5UE0rw8IeW/3gieHNKQ5sPuDKlljWEn4bzv5+1bHw==", - "license": "MIT" - }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -4954,78 +4370,11 @@ "url": "https://github.com/sponsors/antfu" } }, - "node_modules/es-abstract": { - "version": "1.24.2", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", - "integrity": "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==", - "license": "MIT", - "dependencies": { - "array-buffer-byte-length": "^1.0.2", - "arraybuffer.prototype.slice": "^1.0.4", - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "data-view-buffer": "^1.0.2", - "data-view-byte-length": "^1.0.2", - "data-view-byte-offset": "^1.0.1", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "es-set-tostringtag": "^2.1.0", - "es-to-primitive": "^1.3.0", - "function.prototype.name": "^1.1.8", - "get-intrinsic": "^1.3.0", - "get-proto": "^1.0.1", - "get-symbol-description": "^1.1.0", - "globalthis": "^1.0.4", - "gopd": "^1.2.0", - "has-property-descriptors": "^1.0.2", - "has-proto": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "internal-slot": "^1.1.0", - "is-array-buffer": "^3.0.5", - "is-callable": "^1.2.7", - "is-data-view": "^1.0.2", - "is-negative-zero": "^2.0.3", - "is-regex": "^1.2.1", - "is-set": "^2.0.3", - "is-shared-array-buffer": "^1.0.4", - "is-string": "^1.1.1", - "is-typed-array": "^1.1.15", - "is-weakref": "^1.1.1", - "math-intrinsics": "^1.1.0", - "object-inspect": "^1.13.4", - "object-keys": "^1.1.1", - "object.assign": "^4.1.7", - "own-keys": "^1.0.1", - "regexp.prototype.flags": "^1.5.4", - "safe-array-concat": "^1.1.3", - "safe-push-apply": "^1.0.0", - "safe-regex-test": "^1.1.0", - "set-proto": "^1.0.0", - "stop-iteration-iterator": "^1.1.0", - "string.prototype.trim": "^1.2.10", - "string.prototype.trimend": "^1.0.9", - "string.prototype.trimstart": "^1.0.8", - "typed-array-buffer": "^1.0.3", - "typed-array-byte-length": "^1.0.3", - "typed-array-byte-offset": "^1.0.4", - "typed-array-length": "^1.0.7", - "unbox-primitive": "^1.1.0", - "which-typed-array": "^1.1.19" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -5035,6 +4384,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -5051,6 +4401,7 @@ "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==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -5059,38 +4410,6 @@ "node": ">= 0.4" } }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-to-primitive": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", - "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", - "license": "MIT", - "dependencies": { - "is-callable": "^1.2.7", - "is-date-object": "^1.0.5", - "is-symbol": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/es-toolkit": { "version": "1.45.1", "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.45.1.tgz", @@ -5164,6 +4483,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -5326,19 +4646,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/esquery": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", @@ -5411,18 +4718,6 @@ "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", "license": "MIT" }, - "node_modules/execall": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/execall/-/execall-2.0.0.tgz", - "integrity": "sha512-0FU2hZ5Hh6iQnarpRtQurM/aAvp3RIbfvgLHrcqJYzhXyV2KFruhuChf9NC6waAhiUR7FFtlugkI4p7f2Fqlow==", - "license": "MIT", - "dependencies": { - "clone-regexp": "^2.1.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/expand-template": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", @@ -5497,25 +4792,6 @@ "node": ">= 0.6" } }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "license": "MIT" - }, - "node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", - "license": "MIT", - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -5523,12 +4799,6 @@ "dev": true, "license": "MIT" }, - "node_modules/fast-equals": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-4.0.3.tgz", - "integrity": "sha512-G3BSX9cfKttjr+2o1O22tYMLq0DPluZnYtq1rXumE1SpL/F/SLIfHx08WYQoWSIpeMYf8sRbJ8++71+v6Pnxfg==", - "license": "MIT" - }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -5543,19 +4813,6 @@ "dev": true, "license": "MIT" }, - "node_modules/fault": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/fault/-/fault-1.0.4.tgz", - "integrity": "sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==", - "license": "MIT", - "dependencies": { - "format": "^0.2.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -5681,29 +4938,6 @@ } } }, - "node_modules/for-each": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", - "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", - "license": "MIT", - "dependencies": { - "is-callable": "^1.2.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/format": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", - "integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==", - "engines": { - "node": ">=0.4.x" - } - }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -5750,49 +4984,12 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/function.prototype.name": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", - "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "functions-have-names": "^1.2.3", - "hasown": "^2.0.2", - "is-callable": "^1.2.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/generator-function": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", - "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -5817,6 +5014,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -5841,6 +5039,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -5850,23 +5049,6 @@ "node": ">= 0.4" } }, - "node_modules/get-symbol-description": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", - "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/github-from-package": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", @@ -5900,26 +5082,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/globalthis": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", - "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", - "license": "MIT", - "dependencies": { - "define-properties": "^1.2.1", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -5928,12 +5095,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "license": "ISC" - }, "node_modules/graphql": { "version": "16.13.2", "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.13.2.tgz", @@ -5944,18 +5105,6 @@ "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" } }, - "node_modules/has-bigints": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", - "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -5966,37 +5115,11 @@ "node": ">=8" } }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", - "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -6005,74 +5128,17 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "node_modules/hasown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "dev": true, "license": "MIT", "dependencies": { - "has-symbols": "^1.0.3" + "function-bind": "^1.1.2" }, "engines": { "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", - "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/hast-util-from-parse5": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-5.0.3.tgz", - "integrity": "sha512-gOc8UB99F6eWVWFtM9jUikjN7QkWxB3nY0df5Z0Zq1/Nkwl5V4hAAsl0tmwlgWl/1shlTF8DnNYLO8X6wRV9pA==", - "license": "MIT", - "dependencies": { - "ccount": "^1.0.3", - "hastscript": "^5.0.0", - "property-information": "^5.0.0", - "web-namespaces": "^1.1.2", - "xtend": "^4.0.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-parse-selector": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz", - "integrity": "sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hastscript": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-5.1.2.tgz", - "integrity": "sha512-WlztFuK+Lrvi3EggsqOkQ52rKbxkXL3RwB6t5lwoa8QLMemoWfBuL43eDrwOamJyR7uKQKdmKYaBH1NZBiIRrQ==", - "license": "MIT", - "dependencies": { - "comma-separated-tokens": "^1.0.0", - "hast-util-parse-selector": "^2.0.0", - "property-information": "^5.0.0", - "space-separated-tokens": "^1.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" } }, "node_modules/headers-polyfill": { @@ -6273,6 +5339,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.8.19" @@ -6315,20 +5382,6 @@ "license": "ISC", "optional": true }, - "node_modules/internal-slot": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", - "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "hasown": "^2.0.2", - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/internmap": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", @@ -6348,682 +5401,160 @@ "node": ">= 0.10" } }, - "node_modules/is-accessor-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.2.tgz", - "integrity": "sha512-AIbwAcazqP3R65dGvqk1V+a+vE5Fg1yu/ZKMOiBWSUIXXiwQkYmXQcVa2O0nh0tSDKDFKxG2mY7dB1Sr4hEP1g==", + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, "license": "MIT", - "dependencies": { - "hasown": "^2.0.3" - }, "engines": { - "node": ">= 0.4" + "node": ">=0.10.0" } }, - "node_modules/is-alphabetical": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", - "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "engines": { + "node": ">=8" } }, - "node_modules/is-alphanumerical": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", - "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, "license": "MIT", "dependencies": { - "is-alphabetical": "^1.0.0", - "is-decimal": "^1.0.0" + "is-extglob": "^2.1.1" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "engines": { + "node": ">=0.10.0" } }, - "node_modules/is-arguments": { + "node_modules/is-node-process": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", - "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", + "resolved": "https://registry.npmjs.org/is-node-process/-/is-node-process-1.2.0.tgz", + "integrity": "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==", "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "license": "MIT" }, - "node_modules/is-array-buffer": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", - "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "get-intrinsic": "^1.2.6" - }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=0.12.0" } }, - "node_modules/is-async-function": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", - "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true, "license": "MIT", - "dependencies": { - "async-function": "^1.0.0", - "call-bound": "^1.0.3", - "get-proto": "^1.0.1", - "has-tostringtag": "^1.0.2", - "safe-regex-test": "^1.1.0" - }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=0.10.0" } }, - "node_modules/is-bigint": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", - "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", - "license": "MIT", - "dependencies": { - "has-bigints": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" }, - "node_modules/is-boolean-object": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", - "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "dev": true, + "license": "MIT" }, - "node_modules/is-buffer": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", - "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "engines": { - "node": ">=4" - } + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "license": "MIT", + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=8" } }, - "node_modules/is-data-descriptor": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.1.tgz", - "integrity": "sha512-bc4NlCDiCr28U4aEsQ3Qs2491gVq4V8G7MQyws968ImqjKuYtTJXrl7Vq7jsN7Ly/C3xj5KWFrY7sHNeDkAzXw==", - "license": "MIT", + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "hasown": "^2.0.0" + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">= 0.4" + "node": ">=10" } }, - "node_modules/is-data-view": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", - "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", - "license": "MIT", + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "call-bound": "^1.0.2", - "get-intrinsic": "^1.2.6", - "is-typed-array": "^1.1.13" + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=8" } }, - "node_modules/is-date-object": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", - "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "node_modules/js-levenshtein": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", + "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==", + "dev": true, "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "has-tostringtag": "^1.0.2" - }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=0.10.0" } }, - "node_modules/is-decimal": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", - "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" }, - "node_modules/is-descriptor": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.4.tgz", - "integrity": "sha512-bv5z95W0dDtLfKwDfkTNxaRxmISBD3eQBKJeVxv2AQ7MjuUnDNG7cIQqvFtMOUYhsILWHhMayWdoGqNqYYYjww==", + "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==", + "dev": true, "license": "MIT", "dependencies": { - "is-accessor-descriptor": "^1.0.2", - "is-data-descriptor": "^1.0.1" + "argparse": "^2.0.1" }, - "engines": { - "node": ">= 0.4" + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "license": "MIT", - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-extendable/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-finalizationregistry": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", - "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-generator-function": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", - "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.4", - "generator-function": "^2.0.0", - "get-proto": "^1.0.1", - "has-tostringtag": "^1.0.2", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-hexadecimal": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", - "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-map": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", - "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-nan": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz", - "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-negative-zero": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", - "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-node-process": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-node-process/-/is-node-process-1.2.0.tgz", - "integrity": "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-number-object": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", - "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-plain-object": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", - "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-potential-custom-element-name": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-promise": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-regex": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", - "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-regexp": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-2.1.0.tgz", - "integrity": "sha512-OZ4IlER3zmRIoB9AqNhEggVxqIH4ofDns5nRrPS6yQxXE1TPCUpFznBfRQmQa8uC+pXqjMnukiJBxCisIxiLGA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/is-set": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", - "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", - "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-string": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", - "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-symbol": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", - "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "has-symbols": "^1.1.0", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-typed-array": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", - "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", - "license": "MIT", - "dependencies": { - "which-typed-array": "^1.1.16" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakmap": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", - "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakref": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", - "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakset": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", - "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "license": "MIT" - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-reports": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", - "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/japanese-numerals-to-number": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/japanese-numerals-to-number/-/japanese-numerals-to-number-1.0.2.tgz", - "integrity": "sha512-rgs/V7G8Gfy8Z4XtnVBYXzWMAb9oUWp1pDdRmwHmh0hcjcy1kOu+DOpC5rwoHUAN4TqANwb7WD6z5W2v7v7PQQ==", - "license": "MIT" - }, - "node_modules/js-levenshtein": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", - "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "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==", - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsdom": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.0.1.tgz", - "integrity": "sha512-z6JOK5gRO7aMybVq/y/MlIpKh8JIi68FBKMUtKkK2KH/wMSRlCxQ682d08LB9fYXplyY/UXG8P4XXTScmdjApg==", - "dev": true, + "node_modules/jsdom": { + "version": "29.0.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.0.1.tgz", + "integrity": "sha512-z6JOK5gRO7aMybVq/y/MlIpKh8JIi68FBKMUtKkK2KH/wMSRlCxQ682d08LB9fYXplyY/UXG8P4XXTScmdjApg==", + "dev": true, "license": "MIT", "dependencies": { "@asamuzakjp/css-color": "^5.0.1", @@ -7137,28 +5668,6 @@ "node": ">=6" } }, - "node_modules/kuromoji": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/kuromoji/-/kuromoji-0.1.2.tgz", - "integrity": "sha512-V0dUf+C2LpcPEXhoHLMAop/bOht16Dyr+mDiIE39yX3vqau7p80De/koFqpiTcL1zzdZlc3xuHZ8u5gjYRfFaQ==", - "license": "Apache-2.0", - "dependencies": { - "async": "^2.0.1", - "doublearray": "0.0.2", - "zlibjs": "^0.3.1" - } - }, - "node_modules/kuromojin": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/kuromojin/-/kuromojin-3.0.1.tgz", - "integrity": "sha512-E4rLmbTid8ZGH8Fw421UXvfgCe0IXGFKhUw/IosBVW6ootxVGKGbtb0UPQAvPswgsXX/8UuPiE+amz1NN11Uzg==", - "license": "MIT", - "dependencies": { - "@kvs/env": "^2.1.3", - "kuromoji": "0.1.2", - "lru_map": "^0.4.1" - } - }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -7189,46 +5698,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lodash": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", - "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", - "license": "MIT" - }, "node_modules/lodash.merge": { "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.sortby": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==", - "license": "MIT" - }, - "node_modules/lodash.uniq": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", - "license": "MIT" - }, - "node_modules/lodash.uniqwith": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniqwith/-/lodash.uniqwith-4.5.0.tgz", - "integrity": "sha512-7lYL8bLopMoy4CTICbxygAUq6CdRJ36vFc80DucPueUee+d5NBRxz3FdT9Pes/HEx5mPoT9jwnsEJWz1N7uq7Q==", - "license": "MIT" - }, - "node_modules/longest-streak": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-2.0.4.tgz", - "integrity": "sha512-vM6rUVCVUJJt33bnmHiZEvr7wPT78ztX7rojL+LW51bHtLh6HTjx84LA5W4+oa6aKEJA7jJu5LR6vQRBpA5DVg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" }, "node_modules/loose-envify": { "version": "1.4.0", @@ -7242,12 +5717,6 @@ "loose-envify": "cli.js" } }, - "node_modules/lru_map": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.4.1.tgz", - "integrity": "sha512-I+lBvqMMFfqaV8CJCISjI3wbjmwVu/VyOoU7+qtu9d7ioW5klMgsTTiUOUp+DJvfTTzKXoPbyC6YfgkNcyPSOg==", - "license": "MIT" - }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -7338,19 +5807,6 @@ "url": "https://github.com/wojtekmaj/make-event-props?sponsor=1" } }, - "node_modules/markdown-table": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-2.0.0.tgz", - "integrity": "sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A==", - "license": "MIT", - "dependencies": { - "repeat-string": "^1.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/marked": { "version": "17.0.4", "resolved": "https://registry.npmjs.org/marked/-/marked-17.0.4.tgz", @@ -7363,183 +5819,16 @@ "node": ">= 20" } }, - "node_modules/match-index": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/match-index/-/match-index-1.0.3.tgz", - "integrity": "sha512-1XjyBWqCvEFFUDW/MPv0RwbITRD4xQXOvKoPYtLDq8IdZTfdF/cQSo5Yn4qvhfSSZgjgkTFsqJD2wOUG4ovV8Q==", - "license": "MIT", - "dependencies": { - "regexp.prototype.flags": "^1.1.1" - } - }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" } }, - "node_modules/mdast-util-find-and-replace": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-1.1.1.tgz", - "integrity": "sha512-9cKl33Y21lyckGzpSmEQnIDjEfeeWelN5s1kUW1LwdB0Fkuq2u+4GdqcGEygYxJE8GVqCl0741bYXHgamfWAZA==", - "license": "MIT", - "dependencies": { - "escape-string-regexp": "^4.0.0", - "unist-util-is": "^4.0.0", - "unist-util-visit-parents": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-footnote": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/mdast-util-footnote/-/mdast-util-footnote-0.1.7.tgz", - "integrity": "sha512-QxNdO8qSxqbO2e3m09KwDKfWiLgqyCurdWTQ198NpbZ2hxntdc+VKS4fDJCmNWbAroUdYnSthu+XbZ8ovh8C3w==", - "license": "MIT", - "dependencies": { - "mdast-util-to-markdown": "^0.6.0", - "micromark": "~2.11.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-from-markdown": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-0.8.5.tgz", - "integrity": "sha512-2hkTXtYYnr+NubD/g6KGBS/0mFmBcifAsI0yIWRiRo0PjVs6SSOSOdtzbp6kSGnShDN6G5aWZpKQ2lWRy27mWQ==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^3.0.0", - "mdast-util-to-string": "^2.0.0", - "micromark": "~2.11.0", - "parse-entities": "^2.0.0", - "unist-util-stringify-position": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-frontmatter": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/mdast-util-frontmatter/-/mdast-util-frontmatter-0.2.0.tgz", - "integrity": "sha512-FHKL4w4S5fdt1KjJCwB0178WJ0evnyyQr5kXTM3wrOVpytD0hrkvd+AOOjU9Td8onOejCkmZ+HQRT3CZ3coHHQ==", - "license": "MIT", - "dependencies": { - "micromark-extension-frontmatter": "^0.2.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-0.1.2.tgz", - "integrity": "sha512-NNkhDx/qYcuOWB7xHUGWZYVXvjPFFd6afg6/e2g+SV4r9q5XUcCbV4Wfa3DLYIiD+xAEZc6K4MGaE/m0KDcPwQ==", - "license": "MIT", - "dependencies": { - "mdast-util-gfm-autolink-literal": "^0.1.0", - "mdast-util-gfm-strikethrough": "^0.2.0", - "mdast-util-gfm-table": "^0.1.0", - "mdast-util-gfm-task-list-item": "^0.1.0", - "mdast-util-to-markdown": "^0.6.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-autolink-literal": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-0.1.3.tgz", - "integrity": "sha512-GjmLjWrXg1wqMIO9+ZsRik/s7PLwTaeCHVB7vRxUwLntZc8mzmTsLVr6HW1yLokcnhfURsn5zmSVdi3/xWWu1A==", - "license": "MIT", - "dependencies": { - "ccount": "^1.0.0", - "mdast-util-find-and-replace": "^1.1.0", - "micromark": "^2.11.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-strikethrough": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-0.2.3.tgz", - "integrity": "sha512-5OQLXpt6qdbttcDG/UxYY7Yjj3e8P7X16LzvpX8pIQPYJ/C2Z1qFGMmcw+1PZMUM3Z8wt8NRfYTvCni93mgsgA==", - "license": "MIT", - "dependencies": { - "mdast-util-to-markdown": "^0.6.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-table": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-0.1.6.tgz", - "integrity": "sha512-j4yDxQ66AJSBwGkbpFEp9uG/LS1tZV3P33fN1gkyRB2LoRL+RR3f76m0HPHaby6F4Z5xr9Fv1URmATlRRUIpRQ==", - "license": "MIT", - "dependencies": { - "markdown-table": "^2.0.0", - "mdast-util-to-markdown": "~0.6.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-task-list-item": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-0.1.6.tgz", - "integrity": "sha512-/d51FFIfPsSmCIRNp7E6pozM9z1GYPIkSy1urQ8s/o4TC22BZ7DqfHFWiqBD23bc7J3vV1Fc9O4QIHBlfuit8A==", - "license": "MIT", - "dependencies": { - "mdast-util-to-markdown": "~0.6.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-to-markdown": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-0.6.5.tgz", - "integrity": "sha512-XeV9sDE7ZlOQvs45C9UKMtfTcctcaj/pGwH8YLbMHoMOXNNCn2LsqVQOqrF1+/NU8lKDAqozme9SCXWyo9oAcQ==", - "license": "MIT", - "dependencies": { - "@types/unist": "^2.0.0", - "longest-streak": "^2.0.0", - "mdast-util-to-string": "^2.0.0", - "parse-entities": "^2.0.0", - "repeat-string": "^1.0.0", - "zwitch": "^1.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-to-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-2.0.0.tgz", - "integrity": "sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/mdn-data": { "version": "2.27.1", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", @@ -7587,132 +5876,6 @@ } } }, - "node_modules/micromark": { - "version": "2.11.4", - "resolved": "https://registry.npmjs.org/micromark/-/micromark-2.11.4.tgz", - "integrity": "sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "debug": "^4.0.0", - "parse-entities": "^2.0.0" - } - }, - "node_modules/micromark-extension-footnote": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/micromark-extension-footnote/-/micromark-extension-footnote-0.3.2.tgz", - "integrity": "sha512-gr/BeIxbIWQoUm02cIfK7mdMZ/fbroRpLsck4kvFtjbzP4yi+OPVbnukTc/zy0i7spC2xYE/dbX1Sur8BEDJsQ==", - "license": "MIT", - "dependencies": { - "micromark": "~2.11.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-frontmatter": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/micromark-extension-frontmatter/-/micromark-extension-frontmatter-0.2.2.tgz", - "integrity": "sha512-q6nPLFCMTLtfsctAuS0Xh4vaolxSFUWUWR6PZSrXXiRy+SANGllpcqdXFv2z07l0Xz/6Hl40hK0ffNCJPH2n1A==", - "license": "MIT", - "dependencies": { - "fault": "^1.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-0.3.3.tgz", - "integrity": "sha512-oVN4zv5/tAIA+l3GbMi7lWeYpJ14oQyJ3uEim20ktYFAcfX1x3LNlFGGlmrZHt7u9YlKExmyJdDGaTt6cMSR/A==", - "license": "MIT", - "dependencies": { - "micromark": "~2.11.0", - "micromark-extension-gfm-autolink-literal": "~0.5.0", - "micromark-extension-gfm-strikethrough": "~0.6.5", - "micromark-extension-gfm-table": "~0.4.0", - "micromark-extension-gfm-tagfilter": "~0.3.0", - "micromark-extension-gfm-task-list-item": "~0.3.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-autolink-literal": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-0.5.7.tgz", - "integrity": "sha512-ePiDGH0/lhcngCe8FtH4ARFoxKTUelMp4L7Gg2pujYD5CSMb9PbblnyL+AAMud/SNMyusbS2XDSiPIRcQoNFAw==", - "license": "MIT", - "dependencies": { - "micromark": "~2.11.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-strikethrough": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-0.6.5.tgz", - "integrity": "sha512-PpOKlgokpQRwUesRwWEp+fHjGGkZEejj83k9gU5iXCbDG+XBA92BqnRKYJdfqfkrRcZRgGuPuXb7DaK/DmxOhw==", - "license": "MIT", - "dependencies": { - "micromark": "~2.11.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-table": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-0.4.3.tgz", - "integrity": "sha512-hVGvESPq0fk6ALWtomcwmgLvH8ZSVpcPjzi0AjPclB9FsVRgMtGZkUcpE0zgjOCFAznKepF4z3hX8z6e3HODdA==", - "license": "MIT", - "dependencies": { - "micromark": "~2.11.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-tagfilter": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-0.3.0.tgz", - "integrity": "sha512-9GU0xBatryXifL//FJH+tAZ6i240xQuFrSL7mYi8f4oZSbc+NvXjkrHemeYP0+L4ZUT+Ptz3b95zhUZnMtoi/Q==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-task-list-item": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-0.3.3.tgz", - "integrity": "sha512-0zvM5iSLKrc/NQl84pZSjGo66aTGd57C1idmlWmE87lkMcXrTxg1uXa/nXomxJytoje9trP0NDLvw4bZ/Z/XCQ==", - "license": "MIT", - "dependencies": { - "micromark": "~2.11.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -7841,44 +6004,11 @@ "license": "MIT", "optional": true }, - "node_modules/moji": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/moji/-/moji-0.5.1.tgz", - "integrity": "sha512-xYylXOjBS9mE/d690InK3Y74NpE0El0TmAKDmKJveWk9jds/0Tl7MQP4yhavS0U64diEq+5ey2905nhCpIHE+Q==", - "license": "MIT", - "dependencies": { - "object-assign": "^3.0.0" - } - }, - "node_modules/morpheme-match": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/morpheme-match/-/morpheme-match-2.0.4.tgz", - "integrity": "sha512-C3U5g2h47dpztGVePLA8w2O1aQEvuJORwIcahWaCG91YPrq+0u7qcPsF9Nqqe8noFvHwgO7b2EEk3iPnYuGTew==", - "license": "MIT" - }, - "node_modules/morpheme-match-all": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/morpheme-match-all/-/morpheme-match-all-2.0.5.tgz", - "integrity": "sha512-a6B6Nh4AhjMoEzVz8NOT5M9f3XwFVPM395gqnFNUXG/KTqQFTb9qM5JJFHJe+SvWfRVXTZSejyXNt+h+jmHUuQ==", - "license": "MIT", - "dependencies": { - "morpheme-match": "^2.0.4" - } - }, - "node_modules/morpheme-match-textlint": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/morpheme-match-textlint/-/morpheme-match-textlint-2.0.6.tgz", - "integrity": "sha512-CX+iQaUjjPMLvas+hZ8zg6Csxx5j1RLaytr+5w6lpBi/oTEV2pv6sgW5Vu3+pNJHbYcaqcuofQZsKocMNUNH8g==", - "license": "MIT", - "dependencies": { - "morpheme-match": "^2.0.4", - "morpheme-match-all": "^2.0.5" - } - }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, "license": "MIT" }, "node_modules/msw": { @@ -8012,18 +6142,6 @@ "license": "MIT", "optional": true }, - "node_modules/node-localstorage": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/node-localstorage/-/node-localstorage-2.2.1.tgz", - "integrity": "sha512-vv8fJuOUCCvSPjDjBLlMqYMHob4aGjkmrkaE42/mZr0VT+ZAU10jRF8oTnX9+pgU9/vYJ8P7YT3Vd6ajkmzSCw==", - "license": "MIT", - "dependencies": { - "write-file-atomic": "^1.1.4" - }, - "engines": { - "node": ">=0.12" - } - }, "node_modules/node-releases": { "version": "2.0.27", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", @@ -8031,66 +6149,12 @@ "dev": true, "license": "MIT" }, - "node_modules/object-assign": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", - "integrity": "sha512-jHP15vXVGeVh1HuaA2wY6lxk+whK/x4KBG88VXeRma7CCun7iGD5qPc4eYykQ9sdQvg8jkwFKsSxHln2ybW3xQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-is": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", - "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", "dev": true, "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.assign": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", - "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0", - "has-symbols": "^1.1.0", - "object-keys": "^1.1.1" - }, "engines": { "node": ">= 0.4" }, @@ -8184,13 +6248,6 @@ "node": ">= 0.8.0" } }, - "node_modules/os-browserify": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", - "integrity": "sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A==", - "dev": true, - "license": "MIT" - }, "node_modules/outvariant": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.3.tgz", @@ -8198,23 +6255,6 @@ "dev": true, "license": "MIT" }, - "node_modules/own-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", - "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.2.6", - "object-keys": "^1.1.1", - "safe-push-apply": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -8257,25 +6297,7 @@ "callsites": "^3.0.0" }, "engines": { - "node": ">=6" - } - }, - "node_modules/parse-entities": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz", - "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==", - "license": "MIT", - "dependencies": { - "character-entities": "^1.0.0", - "character-entities-legacy": "^1.0.0", - "character-reference-invalid": "^1.0.0", - "is-alphanumerical": "^1.0.0", - "is-decimal": "^1.0.0", - "is-hexadecimal": "^1.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "node": ">=6" } }, "node_modules/parse-json": { @@ -8332,13 +6354,6 @@ "node": ">= 0.8" } }, - "node_modules/path-browserify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", - "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", - "dev": true, - "license": "MIT" - }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -8473,15 +6488,6 @@ "node": ">=4" } }, - "node_modules/possible-typed-array-names": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", - "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, "node_modules/postcss": { "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", @@ -8603,36 +6609,6 @@ "license": "MIT", "peer": true }, - "node_modules/prh": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/prh/-/prh-6.0.6.tgz", - "integrity": "sha512-WJ7aaIa37vwHYg5sa1VaqykJiO/6AEkbQOTEnp/52XDXnbMcBPY1++oeV5trjFrLbiTqJY5nO5BaR/MR5ioJHw==", - "license": "MIT", - "dependencies": { - "commander": "^14.0.3", - "diff": "^9.0.0", - "js-yaml": "^4.1.0" - }, - "bin": { - "prh": "bin/prh" - }, - "engines": { - "node": ">=22" - } - }, - "node_modules/property-information": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-5.6.0.tgz", - "integrity": "sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==", - "license": "MIT", - "dependencies": { - "xtend": "^4.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -8951,152 +6927,6 @@ "redux": "^5.0.0" } }, - "node_modules/reflect.getprototypeof": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", - "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.9", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.7", - "get-proto": "^1.0.1", - "which-builtin-type": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "license": "MIT", - "dependencies": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/regexp.prototype.flags": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", - "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-errors": "^1.3.0", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "set-function-name": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/regx": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/regx/-/regx-1.0.4.tgz", - "integrity": "sha512-Z/5ochRUyD5TkJgFq+66ajKePlj6KzpSLfDO2lOLOLu7E82xAjNux0m8mx1DAXBj5ECHiRCBWoqL25b4lkwcgw==", - "license": "MIT" - }, - "node_modules/rehype-parse": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-6.0.2.tgz", - "integrity": "sha512-0S3CpvpTAgGmnz8kiCyFLGuW5yA4OQhyNTm/nwPopZ7+PI11WnGl1TTWTGv/2hPEe/g2jRLlhVVSsoDH8waRug==", - "license": "MIT", - "dependencies": { - "hast-util-from-parse5": "^5.0.0", - "parse5": "^5.0.0", - "xtend": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/rehype-parse/node_modules/parse5": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", - "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", - "license": "MIT" - }, - "node_modules/remark-footnotes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/remark-footnotes/-/remark-footnotes-3.0.0.tgz", - "integrity": "sha512-ZssAvH9FjGYlJ/PBVKdSmfyPc3Cz4rTWgZLI4iE/SX8Nt5l3o3oEjv3wwG5VD7xOjktzdwp5coac+kJV9l4jgg==", - "license": "MIT", - "dependencies": { - "mdast-util-footnote": "^0.1.0", - "micromark-extension-footnote": "^0.3.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-frontmatter": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/remark-frontmatter/-/remark-frontmatter-3.0.0.tgz", - "integrity": "sha512-mSuDd3svCHs+2PyO29h7iijIZx4plX0fheacJcAoYAASfgzgVIcXGYSq9GFyYocFLftQs8IOmmkgtOovs6d4oA==", - "license": "MIT", - "dependencies": { - "mdast-util-frontmatter": "^0.2.0", - "micromark-extension-frontmatter": "^0.2.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-gfm": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-1.0.0.tgz", - "integrity": "sha512-KfexHJCiqvrdBZVbQ6RopMZGwaXz6wFJEfByIuEwGf0arvITHjiKKZ1dpXujjH9KZdm1//XJQwgfnJ3lmXaDPA==", - "license": "MIT", - "dependencies": { - "mdast-util-gfm": "^0.1.0", - "micromark-extension-gfm": "^0.3.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-parse": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-9.0.0.tgz", - "integrity": "sha512-geKatMwSzEXKHuzBNU1z676sGcDcFoChMK38TgdHJNAYfFtsfHDQG7MoJAjs6sgYMqyLduCYWDIWZIxiPeafEw==", - "license": "MIT", - "dependencies": { - "mdast-util-from-markdown": "^0.8.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", - "license": "MIT", - "engines": { - "node": ">=0.10" - } - }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -9140,15 +6970,6 @@ "node": ">=4" } }, - "node_modules/ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "license": "MIT", - "engines": { - "node": ">=0.12" - } - }, "node_modules/rettime": { "version": "0.10.1", "resolved": "https://registry.npmjs.org/rettime/-/rettime-0.10.1.tgz", @@ -9239,25 +7060,6 @@ "tslib": "^2.1.0" } }, - "node_modules/safe-array-concat": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.4.tgz", - "integrity": "sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.9", - "call-bound": "^1.0.4", - "get-intrinsic": "^1.3.0", - "has-symbols": "^1.1.0", - "isarray": "^2.0.5" - }, - "engines": { - "node": ">=0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -9279,48 +7081,6 @@ "license": "MIT", "optional": true }, - "node_modules/safe-push-apply": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", - "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "isarray": "^2.0.5" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==", - "license": "MIT", - "dependencies": { - "ret": "~0.1.10" - } - }, - "node_modules/safe-regex-test": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", - "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-regex": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -9387,22 +7147,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/sentence-splitter": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/sentence-splitter/-/sentence-splitter-5.0.1.tgz", - "integrity": "sha512-Eu9l95hQfI0WdhXTznuBzqsFjTuL6UnPq341Ro1s2bjE3DMoBuhE0wk4z+rwbwVXfOAeuOQM0UF30HR2+66VuQ==", - "license": "MIT", - "dependencies": { - "@textlint/ast-node-types": "^13.4.1", - "structured-source": "^4.0.0" - } - }, - "node_modules/sentence-splitter/node_modules/@textlint/ast-node-types": { - "version": "13.4.1", - "resolved": "https://registry.npmjs.org/@textlint/ast-node-types/-/ast-node-types-13.4.1.tgz", - "integrity": "sha512-qrZyhCh8Ekk6nwArx3BROybm9BnX6vF7VcZbijetV/OM3yfS4rTYhoMWISmhVEP2H2re0CtWEyMl/XF+WdvVLQ==", - "license": "MIT" - }, "node_modules/serve-static": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", @@ -9429,52 +7173,6 @@ "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", "license": "MIT" }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/set-function-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", - "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/set-proto": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", - "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -9580,6 +7278,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -9599,6 +7298,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -9615,6 +7315,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -9633,6 +7334,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -9715,15 +7417,6 @@ "simple-concat": "^1.0.0" } }, - "node_modules/slide": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz", - "integrity": "sha512-NwrtjCg+lZoqhFU8fOwl4ay2ei8PaqCBOUV3/ektPY9trO1yQ1oXEfmHAhKArUVUr/hOHvy5f6AdP17dCM0zMw==", - "license": "ISC", - "engines": { - "node": "*" - } - }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -9734,22 +7427,6 @@ "node": ">=0.10.0" } }, - "node_modules/space-separated-tokens": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz", - "integrity": "sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "license": "BSD-3-Clause" - }, "node_modules/stackback": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", @@ -9774,19 +7451,6 @@ "dev": true, "license": "MIT" }, - "node_modules/stop-iteration-iterator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", - "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "internal-slot": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/strict-event-emitter": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz", @@ -9804,75 +7468,19 @@ "safe-buffer": "~5.2.0" } }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string.prototype.trim": { - "version": "1.2.10", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", - "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "define-data-property": "^1.1.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-object-atoms": "^1.0.0", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimend": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", - "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", - "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=8" } }, "node_modules/strip-ansi": { @@ -9914,15 +7522,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/structured-source": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/structured-source/-/structured-source-4.0.0.tgz", - "integrity": "sha512-qGzRFNJDjFieQkl/sVOI2dUjHKRyL9dAJi2gCPGJLbJHBIkyOHxjuocpIEfbLioX+qSJpvbYdT49/YCdMznKxA==", - "license": "BSD-2-Clause", - "dependencies": { - "boundary": "^2.0.0" - } - }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -9959,576 +7558,31 @@ "node_modules/tar-fs": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", - "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/textlint-rule-helper": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/textlint-rule-helper/-/textlint-rule-helper-2.5.0.tgz", - "integrity": "sha512-QIbFPtyqLy0g5BJn8mryk9iHzGYicNaFIpLFPiEnb4RXxrEGeQ2W2aARQ9yEXLIAqo+OwK4ndWBAWkbgJEPzTQ==", - "license": "MIT", - "dependencies": { - "@textlint/ast-node-types": "^15.2.1", - "structured-source": "^4.0.0", - "unist-util-visit": "^2.0.3" - } - }, - "node_modules/textlint-rule-ja-no-abusage": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/textlint-rule-ja-no-abusage/-/textlint-rule-ja-no-abusage-3.0.0.tgz", - "integrity": "sha512-DIqPZgYTwTsvjX6Bgj7GA8vlwGMObagJpNoUtkucOaoy7E7GwUOL+knqFjcTtlkWSmoKpIkw5OWW5CV3kivlfQ==", - "license": "MIT", - "dependencies": { - "kuromojin": "^3.0.0", - "morpheme-match-textlint": "^2.0.6", - "textlint-rule-prh": "^5.3.0" - } - }, - "node_modules/textlint-rule-ja-no-abusage/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/textlint-rule-ja-no-abusage/node_modules/diff": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", - "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/textlint-rule-ja-no-abusage/node_modules/js-yaml": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", - "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", - "license": "MIT", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/textlint-rule-ja-no-abusage/node_modules/prh": { - "version": "5.4.4", - "resolved": "https://registry.npmjs.org/prh/-/prh-5.4.4.tgz", - "integrity": "sha512-UATF+R/2H8owxwPvF12Knihu9aYGTuZttGHrEEq5NBWz38mREh23+WvCVKX3fhnIZIMV7ye6E1fnqAl+V6WYEw==", - "license": "MIT", - "dependencies": { - "commandpost": "^1.2.1", - "diff": "^4.0.1", - "js-yaml": "^3.9.1" - }, - "bin": { - "prh": "bin/prh" - } - }, - "node_modules/textlint-rule-ja-no-abusage/node_modules/textlint-rule-prh": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/textlint-rule-prh/-/textlint-rule-prh-5.3.0.tgz", - "integrity": "sha512-gdod+lL1SWUDyXs1ICEwvQawaSshT3mvPGufBIjF2R5WFPdKQDMsiuzsjkLm+aF+9d97dA6pFsiyC8gSW7mSgg==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.7.5", - "prh": "^5.4.4", - "textlint-rule-helper": "^2.1.1", - "untildify": "^3.0.3" - } - }, - "node_modules/textlint-rule-ja-no-mixed-period": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/textlint-rule-ja-no-mixed-period/-/textlint-rule-ja-no-mixed-period-3.0.2.tgz", - "integrity": "sha512-/WHRvbAXPJxjDAmVYjIp5qBHPS7Uqv3H+fH0oGMWzujEV4n2URtXmxqTUv7nd4giTRqeV2yuMZWYRRgA9Mh0Sg==", - "license": "MIT", - "dependencies": { - "check-ends-with-period": "^3.0.2", - "textlint-rule-helper": "^2.2.4" - } - }, - "node_modules/textlint-rule-ja-no-redundant-expression": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/textlint-rule-ja-no-redundant-expression/-/textlint-rule-ja-no-redundant-expression-4.0.1.tgz", - "integrity": "sha512-r8Qe6S7u9N97wD0gcrASqBUdZs5CMEVlgc8Ul+D2NQFiOi1BoseOMo5I9yUsEZMAL46yh/eaw9+EWz6IDlPWeA==", - "license": "MIT", - "dependencies": { - "@textlint/regexp-string-matcher": "^1.1.0", - "kuromojin": "^3.0.0", - "morpheme-match": "^2.0.4", - "morpheme-match-all": "^2.0.5", - "textlint-rule-helper": "^2.2.1", - "textlint-util-to-string": "^3.1.1" - } - }, - "node_modules/textlint-rule-ja-no-successive-word": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/textlint-rule-ja-no-successive-word/-/textlint-rule-ja-no-successive-word-2.0.1.tgz", - "integrity": "sha512-XKTXkHwMu86SnGaj73B67U4apDdTquDKF3SfG24tRbzMyJoGe/Iba5VMId8sp8QHeTonp1bYOSxjZsbkpGyCNw==", - "license": "MIT", - "dependencies": { - "@textlint/regexp-string-matcher": "^1.1.0", - "kuromojin": "^3.0.0" - } - }, - "node_modules/textlint-rule-ja-no-weak-phrase": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/textlint-rule-ja-no-weak-phrase/-/textlint-rule-ja-no-weak-phrase-2.0.0.tgz", - "integrity": "sha512-jZ1/3VnE8Bz/p0CCe/+BVOmWE1cZwpDkjKb6hnq39nGk3OD4XPW7biYavK/4/mZhk4bh2+Vtu1xV26lg8d+vBw==", - "license": "MIT", - "dependencies": { - "kuromojin": "^3.0.0", - "morpheme-match": "^2.0.4", - "morpheme-match-all": "^2.0.5" - } - }, - "node_modules/textlint-rule-ja-unnatural-alphabet": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/textlint-rule-ja-unnatural-alphabet/-/textlint-rule-ja-unnatural-alphabet-2.0.1.tgz", - "integrity": "sha512-n93V8qh6OKdh8OB6yLT+9Xl8DSoZ7gWi51tWdhlLEPIWgBm18nLMgm6Ck+nVc3eENOdRcQURWUpCGotI8wTemA==", - "license": "MIT", - "dependencies": { - "@textlint/regexp-string-matcher": "^1.0.2", - "match-index": "^1.0.1", - "regx": "^1.0.4" - } - }, - "node_modules/textlint-rule-max-comma": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/textlint-rule-max-comma/-/textlint-rule-max-comma-4.0.0.tgz", - "integrity": "sha512-2vKKXNg1YuTqr9/FrHvOGEHFe+6lNSDtzuEv+KRB+tuaj++UNa/YPvyY34UdDYuHUSKNcYdto8GlIUhAJDW9WQ==", - "license": "MIT", - "dependencies": { - "sentence-splitter": "^5.0.0", - "textlint-util-to-string": "^3.3.4" - } - }, - "node_modules/textlint-rule-max-kanji-continuous-len": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/textlint-rule-max-kanji-continuous-len/-/textlint-rule-max-kanji-continuous-len-1.1.1.tgz", - "integrity": "sha512-os+p7dS9Op4PtZV7B9/wnzN5qpDO2WR2kj7nTnqEPbApS06AXX+qf5eH3fKdm4blzUCcPF5ozMN8zbs3AJUAug==", - "license": "MIT", - "dependencies": { - "match-index": "^1.0.1", - "textlint-rule-helper": "^2.0.0" - } - }, - "node_modules/textlint-rule-max-ten": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/textlint-rule-max-ten/-/textlint-rule-max-ten-5.0.0.tgz", - "integrity": "sha512-EWOvbEa3Ukxz0+GAUEJ91DYFSC3IkyJ10dBcsU6VlL33k1BvTRoFr3m26w6upnXJffXQUI70Etn39I++2duyhA==", - "license": "MIT", - "dependencies": { - "kuromojin": "^3.0.0", - "sentence-splitter": "^5.0.0", - "textlint-rule-helper": "^2.3.1", - "textlint-util-to-string": "^3.3.4" - } - }, - "node_modules/textlint-rule-no-double-negative-ja": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/textlint-rule-no-double-negative-ja/-/textlint-rule-no-double-negative-ja-2.0.1.tgz", - "integrity": "sha512-LRofmNt+nd2mp+AHmG0ltk9AlbzKbWPE+EToYQ1zORCd8N8suE1YxNEplz9OeQ59ea9ITtudDIWoqeHaZnbDsg==", - "license": "MIT", - "dependencies": { - "kuromojin": "^3.0.0" - } - }, - "node_modules/textlint-rule-no-doubled-conjunction": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/textlint-rule-no-doubled-conjunction/-/textlint-rule-no-doubled-conjunction-3.0.1.tgz", - "integrity": "sha512-fRJEiy0rkrmUDVdgzGiUKAgGngziUfsjL4eqY4a3YfFxXwtczOP+hyHfvLNrHOUI58Y6lNoLZpa5lNNXbzFyNg==", - "license": "MIT", - "dependencies": { - "kuromojin": "^3.0.0", - "sentence-splitter": "^5.0.0", - "textlint-rule-helper": "^2.3.1" - } - }, - "node_modules/textlint-rule-no-doubled-conjunctive-particle-ga": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/textlint-rule-no-doubled-conjunctive-particle-ga/-/textlint-rule-no-doubled-conjunctive-particle-ga-3.0.0.tgz", - "integrity": "sha512-4IowX2YlTlD9VifThZwpENRh918BpPNTks0i4bOL7Gn82jUiXK0EZuV8Jtksm7i+RYG1xsO0U7P9AnxmuSxeDg==", - "license": "MIT", - "dependencies": { - "kuromojin": "^3.0.0", - "sentence-splitter": "^5.0.0", - "textlint-rule-helper": "^2.3.1", - "textlint-util-to-string": "^3.3.4" - } - }, - "node_modules/textlint-rule-no-doubled-joshi": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/textlint-rule-no-doubled-joshi/-/textlint-rule-no-doubled-joshi-5.1.1.tgz", - "integrity": "sha512-bxiuqZiHE9ZZGHDX+O6J4vzhre7ghb7NBkPi/5TAWVvnQLULgY3COx/KXJgFItPmSrpYF8/AT/uTHC4QOg59qw==", - "license": "MIT", - "dependencies": { - "kuromojin": "^3.0.0", - "sentence-splitter": "^5.0.0", - "textlint-rule-helper": "^2.3.1", - "textlint-util-to-string": "^3.3.4" - } - }, - "node_modules/textlint-rule-no-dropping-the-ra": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/textlint-rule-no-dropping-the-ra/-/textlint-rule-no-dropping-the-ra-3.0.0.tgz", - "integrity": "sha512-KbSIlcxj1kLs4sGSMSLGA8OmgRoaPAWtodJaGEyEUiy7uiZM/VPqYALpuD8vf16N1OR5SM/bXXeZFME65r8ZgQ==", - "license": "MIT", - "dependencies": { - "kuromojin": "^3.0.0", - "textlint-rule-helper": "^2.1.1" - } - }, - "node_modules/textlint-rule-no-exclamation-question-mark": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/textlint-rule-no-exclamation-question-mark/-/textlint-rule-no-exclamation-question-mark-1.1.0.tgz", - "integrity": "sha512-FcBH3uH2qp5VG7I9LKwolUCcPigONcsdam1JhAFPcu10YZNYX0r1l2y9Hzg+E4+1fXLgtGyiObWwfsfelTx8Bw==", - "license": "MIT", - "dependencies": { - "@textlint/regexp-string-matcher": "^1.1.0", - "match-index": "^1.0.3", - "textlint-rule-helper": "^2.1.1" - } - }, - "node_modules/textlint-rule-no-hankaku-kana": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/textlint-rule-no-hankaku-kana/-/textlint-rule-no-hankaku-kana-2.0.1.tgz", - "integrity": "sha512-39s94HK6V1xnII1haYiYQnWy1YQAKK7Zj0mcpUMFHHC4M5JdsRnhGs6DQPVEff0gQIFV0iuDNlofXt15kjMtEA==", - "license": "MIT", - "dependencies": { - "textlint-rule-helper": "^2.3.0", - "textlint-tester": "^13.3.1" - } - }, - "node_modules/textlint-rule-no-mix-dearu-desumasu": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/textlint-rule-no-mix-dearu-desumasu/-/textlint-rule-no-mix-dearu-desumasu-6.0.4.tgz", - "integrity": "sha512-SmALtOFbtmJ//k2iLMvtqhGrgJ/6uDVZFK7TBj2npVAbt10VxgLL87K+62pQ/BqiN9DpOVObshVFdug7lUOKHw==", - "license": "MIT", - "dependencies": { - "analyze-desumasu-dearu": "^5.0.2", - "textlint-rule-helper": "^2.3.1" - } - }, - "node_modules/textlint-rule-no-nfd": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/textlint-rule-no-nfd/-/textlint-rule-no-nfd-2.0.2.tgz", - "integrity": "sha512-lIUvcQ+wqtConpPQU2YwEJl2dRcRyyrxPYZ3V76UwnkVg++XPLIrE5mLDgyNE/UIQ34e/KitJfMLqKWvnkFbNQ==", - "license": "MIT", - "dependencies": { - "match-index": "^1.0.3", - "textlint-rule-helper": "^2.3.0" - } - }, - "node_modules/textlint-rule-no-zero-width-spaces": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/textlint-rule-no-zero-width-spaces/-/textlint-rule-no-zero-width-spaces-1.0.1.tgz", - "integrity": "sha512-AkxpzBILGB4YsXddzHx2xqpXmqMv5Yd+PQm4anUV+ADSJuwLP1Jd6yHf/LOtu9j3ps8K3XM9vQrXRK73z0bU3A==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/hata6502" - } - }, - "node_modules/textlint-rule-preset-ja-technical-writing": { - "version": "12.0.2", - "resolved": "https://registry.npmjs.org/textlint-rule-preset-ja-technical-writing/-/textlint-rule-preset-ja-technical-writing-12.0.2.tgz", - "integrity": "sha512-BBVY6oA5V799k5wRfP+gCpDHsp6vWjWX2UT+/KLlAFFsNdmRB8Z6qyOnqiOjfzmLGIRgoMcPI1dXj5upOqnD6Q==", - "license": "MIT", - "dependencies": { - "@textlint-rule/textlint-rule-no-invalid-control-character": "^3.0.0", - "@textlint-rule/textlint-rule-no-unmatched-pair": "^2.0.4", - "@textlint/module-interop": "^14.4.0", - "textlint-rule-ja-no-abusage": "^3.0.0", - "textlint-rule-ja-no-mixed-period": "^3.0.1", - "textlint-rule-ja-no-redundant-expression": "^4.0.1", - "textlint-rule-ja-no-successive-word": "^2.0.1", - "textlint-rule-ja-no-weak-phrase": "^2.0.0", - "textlint-rule-ja-unnatural-alphabet": "2.0.1", - "textlint-rule-max-comma": "^4.0.0", - "textlint-rule-max-kanji-continuous-len": "^1.1.1", - "textlint-rule-max-ten": "^5.0.0", - "textlint-rule-no-double-negative-ja": "^2.0.1", - "textlint-rule-no-doubled-conjunction": "^3.0.0", - "textlint-rule-no-doubled-conjunctive-particle-ga": "^3.0.0", - "textlint-rule-no-doubled-joshi": "^5.1.0", - "textlint-rule-no-dropping-the-ra": "^3.0.0", - "textlint-rule-no-exclamation-question-mark": "^1.1.0", - "textlint-rule-no-hankaku-kana": "^2.0.1", - "textlint-rule-no-mix-dearu-desumasu": "^6.0.3", - "textlint-rule-no-nfd": "^2.0.2", - "textlint-rule-no-zero-width-spaces": "^1.0.1", - "textlint-rule-preset-jtf-style": "^3.0.1", - "textlint-rule-sentence-length": "^5.2.0" - } - }, - "node_modules/textlint-rule-preset-jtf-style": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/textlint-rule-preset-jtf-style/-/textlint-rule-preset-jtf-style-3.0.3.tgz", - "integrity": "sha512-NTvqjg3oMTGjSDoH6MnYu+DDWW6cEghA66Lb8N0bheTphAj99aaG10HGhwTfDbaH2P6NfPQvRP5ISxiXCT9zdg==", - "license": "MIT", - "dependencies": { - "analyze-desumasu-dearu": "^2.1.2", - "japanese-numerals-to-number": "^1.0.2", - "match-index": "^1.0.3", - "moji": "^0.5.1", - "regexp.prototype.flags": "^1.5.3", - "regx": "^1.0.4", - "textlint-rule-helper": "^2.3.1", - "textlint-rule-prh": "^6.0.0" - } - }, - "node_modules/textlint-rule-preset-jtf-style/node_modules/analyze-desumasu-dearu": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/analyze-desumasu-dearu/-/analyze-desumasu-dearu-2.1.5.tgz", - "integrity": "sha512-4YPL7IRAuaZflE10+BVhKr6k5KQl/DiLeNCIF7ISqKr0ogM2hqm9ztRNCPqL/xYDI7hfuIHR8T+U7mIDRLQNXw==", - "license": "MIT" - }, - "node_modules/textlint-rule-prh": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/textlint-rule-prh/-/textlint-rule-prh-6.1.0.tgz", - "integrity": "sha512-KrchADHw1/LZ/tAQ2XwL/XdUhunKCvlNmwgp+6hdyzuWX7uojOkDdJWWV0KAN4XWsK6Te5w/SZcYwQ7X6i3B0A==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.27.0", - "prh": "^5.4.4", - "textlint-rule-helper": "^2.3.1" - } - }, - "node_modules/textlint-rule-prh/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/textlint-rule-prh/node_modules/diff": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", - "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/textlint-rule-prh/node_modules/js-yaml": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", - "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", - "license": "MIT", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/textlint-rule-prh/node_modules/prh": { - "version": "5.4.4", - "resolved": "https://registry.npmjs.org/prh/-/prh-5.4.4.tgz", - "integrity": "sha512-UATF+R/2H8owxwPvF12Knihu9aYGTuZttGHrEEq5NBWz38mREh23+WvCVKX3fhnIZIMV7ye6E1fnqAl+V6WYEw==", - "license": "MIT", - "dependencies": { - "commandpost": "^1.2.1", - "diff": "^4.0.1", - "js-yaml": "^3.9.1" - }, - "bin": { - "prh": "bin/prh" - } - }, - "node_modules/textlint-rule-sentence-length": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/textlint-rule-sentence-length/-/textlint-rule-sentence-length-5.2.1.tgz", - "integrity": "sha512-z4HylabPy+e1I5DcuzDmcT65hvyytTlsD+uM49JkCBTSJBWqzQQ3kCGk4b1cF+kANS2HifWNnpTG/jfSOeavIw==", - "license": "MIT", - "dependencies": { - "@textlint/regexp-string-matcher": "^2.0.2", - "sentence-splitter": "^5.0.0", - "textlint-rule-helper": "^2.3.1", - "textlint-util-to-string": "^3.3.4" - } - }, - "node_modules/textlint-rule-sentence-length/node_modules/@textlint/regexp-string-matcher": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@textlint/regexp-string-matcher/-/regexp-string-matcher-2.0.2.tgz", - "integrity": "sha512-OXLD9XRxMhd3S0LWuPHpiARQOI7z9tCOs0FsynccW2lmyZzHHFJ9/eR6kuK9xF459Qf+740qI5h+/0cx+NljzA==", - "license": "MIT", - "dependencies": { - "escape-string-regexp": "^4.0.0", - "lodash.sortby": "^4.7.0", - "lodash.uniq": "^4.5.0", - "lodash.uniqwith": "^4.5.0" - } - }, - "node_modules/textlint-tester": { - "version": "13.4.1", - "resolved": "https://registry.npmjs.org/textlint-tester/-/textlint-tester-13.4.1.tgz", - "integrity": "sha512-Ubm9kUZ/NyY0Ggo1RkW2o5QSx6EJ/ogMT1kD5b5pk4cdFTWpUSs8StDMROgcz29wPhvS6sY/35qYALzqyZh8ew==", - "license": "MIT", - "dependencies": { - "@textlint/feature-flag": "^13.4.1", - "@textlint/kernel": "^13.4.1", - "@textlint/textlint-plugin-markdown": "^13.4.1", - "@textlint/textlint-plugin-text": "^13.4.1" - } - }, - "node_modules/textlint-tester/node_modules/@textlint/ast-node-types": { - "version": "13.4.1", - "resolved": "https://registry.npmjs.org/@textlint/ast-node-types/-/ast-node-types-13.4.1.tgz", - "integrity": "sha512-qrZyhCh8Ekk6nwArx3BROybm9BnX6vF7VcZbijetV/OM3yfS4rTYhoMWISmhVEP2H2re0CtWEyMl/XF+WdvVLQ==", - "license": "MIT" - }, - "node_modules/textlint-tester/node_modules/@textlint/ast-tester": { - "version": "13.4.1", - "resolved": "https://registry.npmjs.org/@textlint/ast-tester/-/ast-tester-13.4.1.tgz", - "integrity": "sha512-YSHUR1qDgMPGF5+nvrquEhif6zRJ667xUnfP/9rTNtThIhoTQINvczr5/7xa43F1PDWplL6Curw+2jrE1qHwGQ==", - "license": "MIT", - "dependencies": { - "@textlint/ast-node-types": "^13.4.1", - "debug": "^4.3.4" - } - }, - "node_modules/textlint-tester/node_modules/@textlint/ast-traverse": { - "version": "13.4.1", - "resolved": "https://registry.npmjs.org/@textlint/ast-traverse/-/ast-traverse-13.4.1.tgz", - "integrity": "sha512-uucuC7+NHWkXx2TX5vuyreuHeb+GFiA83V65I+FnYP5EC4dAMOQ86rTSPrZmCwLz+qIWgfDgihGzPccpj3EZGg==", - "license": "MIT", - "dependencies": { - "@textlint/ast-node-types": "^13.4.1" - } - }, - "node_modules/textlint-tester/node_modules/@textlint/feature-flag": { - "version": "13.4.1", - "resolved": "https://registry.npmjs.org/@textlint/feature-flag/-/feature-flag-13.4.1.tgz", - "integrity": "sha512-qY8gKUf30XtzWMTkwYeKytCo6KPx6milpz8YZhuRsEPjT/5iNdakJp5USWDQWDrwbQf7RbRncQdU+LX5JbM9YA==", - "license": "MIT" - }, - "node_modules/textlint-tester/node_modules/@textlint/kernel": { - "version": "13.4.1", - "resolved": "https://registry.npmjs.org/@textlint/kernel/-/kernel-13.4.1.tgz", - "integrity": "sha512-r2sUhjPysFjl2Ax37x9AfWkJM8jgKN0bL4SX3xRzOukdcj69Dst5On5qBZtULaVMX1LDkwkdxA6ZEADmq27qQA==", - "license": "MIT", - "dependencies": { - "@textlint/ast-node-types": "^13.4.1", - "@textlint/ast-tester": "^13.4.1", - "@textlint/ast-traverse": "^13.4.1", - "@textlint/feature-flag": "^13.4.1", - "@textlint/source-code-fixer": "^13.4.1", - "@textlint/types": "^13.4.1", - "@textlint/utils": "^13.4.1", - "debug": "^4.3.4", - "fast-equals": "^4.0.3", - "structured-source": "^4.0.0" - } - }, - "node_modules/textlint-tester/node_modules/@textlint/source-code-fixer": { - "version": "13.4.1", - "resolved": "https://registry.npmjs.org/@textlint/source-code-fixer/-/source-code-fixer-13.4.1.tgz", - "integrity": "sha512-Sl29f3Tpimp0uVE3ysyJBjxaFTVYLOXiJX14eWCQ/kC5ZhIXGosEbStzkP1n8Urso1rs1W4p/2UemVAm3NH2ng==", - "license": "MIT", - "dependencies": { - "@textlint/types": "^13.4.1", - "debug": "^4.3.4" - } - }, - "node_modules/textlint-tester/node_modules/@textlint/text-to-ast": { - "version": "13.4.1", - "resolved": "https://registry.npmjs.org/@textlint/text-to-ast/-/text-to-ast-13.4.1.tgz", - "integrity": "sha512-vCA7uMmbjRv06sEHPbwxTV5iS8OQedC5s7qwmXnWAn2LLWxg4Yp98mONPS1o4D5cPomzYyKNCSfbLwu6yJBUQA==", - "license": "MIT", - "dependencies": { - "@textlint/ast-node-types": "^13.4.1" - } - }, - "node_modules/textlint-tester/node_modules/@textlint/textlint-plugin-text": { - "version": "13.4.1", - "resolved": "https://registry.npmjs.org/@textlint/textlint-plugin-text/-/textlint-plugin-text-13.4.1.tgz", - "integrity": "sha512-z0p5B8WUfTCIRmhjVHFfJv719oIElDDKWOIZei4CyYkfMGo0kq8fkrYBkUR6VZ6gofHwc+mwmIABdUf1rDHzYA==", - "license": "MIT", - "dependencies": { - "@textlint/text-to-ast": "^13.4.1" - } - }, - "node_modules/textlint-tester/node_modules/@textlint/types": { - "version": "13.4.1", - "resolved": "https://registry.npmjs.org/@textlint/types/-/types-13.4.1.tgz", - "integrity": "sha512-1ApwQa31sFmiJeJ5yTNFqjbb2D1ICZvIDW0tFSM0OtmQCSDFNcKD3YrrwDBgSokZ6gWQq/FpNjlhi6iETUWt0Q==", - "license": "MIT", - "dependencies": { - "@textlint/ast-node-types": "^13.4.1" - } - }, - "node_modules/textlint-tester/node_modules/@textlint/utils": { - "version": "13.4.1", - "resolved": "https://registry.npmjs.org/@textlint/utils/-/utils-13.4.1.tgz", - "integrity": "sha512-wX8RT1ejHAPTDmqlzngf0zI5kYoe3QvGDcj+skoTxSv+m/wOs/NyEr92d+ahCP32YqFYzXlqU7aDx2FkULKT+g==", - "license": "MIT" - }, - "node_modules/textlint-util-to-string": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/textlint-util-to-string/-/textlint-util-to-string-3.3.4.tgz", - "integrity": "sha512-XF4Qfw0ES+czKy03BwuvBUoXC8NAg920VuRxW0pd72fW76zMeMbPI/bRN5PHq3SbCdOm7U69/Pk+DX34xqIYqA==", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", "license": "MIT", + "optional": true, "dependencies": { - "@textlint/ast-node-types": "^13.4.1", - "rehype-parse": "^6.0.1", - "structured-source": "^4.0.0", - "unified": "^8.4.0" + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" } }, - "node_modules/textlint-util-to-string/node_modules/@textlint/ast-node-types": { - "version": "13.4.1", - "resolved": "https://registry.npmjs.org/@textlint/ast-node-types/-/ast-node-types-13.4.1.tgz", - "integrity": "sha512-qrZyhCh8Ekk6nwArx3BROybm9BnX6vF7VcZbijetV/OM3yfS4rTYhoMWISmhVEP2H2re0CtWEyMl/XF+WdvVLQ==", - "license": "MIT" - }, - "node_modules/textlint-util-to-string/node_modules/unified": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/unified/-/unified-8.4.2.tgz", - "integrity": "sha512-JCrmN13jI4+h9UAyKEoGcDZV+i1E7BLFuG7OsaDvTXI5P0qhHX+vZO/kOhz9jn8HGENDKbwSeB0nVOg4gVStGA==", + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", "license": "MIT", + "optional": true, "dependencies": { - "bail": "^1.0.0", - "extend": "^3.0.0", - "is-plain-obj": "^2.0.0", - "trough": "^1.0.0", - "vfile": "^4.0.0" + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "engines": { + "node": ">=6" } }, "node_modules/tiny-invariant": { @@ -10601,21 +7655,6 @@ "dev": true, "license": "MIT" }, - "node_modules/to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "license": "MIT", - "dependencies": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -10665,23 +7704,6 @@ "node": ">=20" } }, - "node_modules/traverse": { - "version": "0.6.11", - "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.11.tgz", - "integrity": "sha512-vxXDZg8/+p3gblxB6BhhG5yWVn1kGRlaL8O78UDXc3wRnPizB5g83dcvWV1jpDMIPnjZjOFuxlMmE82XJ4407w==", - "license": "MIT", - "dependencies": { - "gopd": "^1.2.0", - "typedarray.prototype.slice": "^1.0.5", - "which-typed-array": "^1.1.18" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/tree-kill": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", @@ -10692,16 +7714,6 @@ "tree-kill": "cli.js" } }, - "node_modules/trough": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/trough/-/trough-1.0.5.tgz", - "integrity": "sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/ts-api-utils": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", @@ -10779,102 +7791,6 @@ "node": ">= 0.6" } }, - "node_modules/typed-array-buffer": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", - "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-typed-array": "^1.1.14" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/typed-array-byte-length": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", - "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "for-each": "^0.3.3", - "gopd": "^1.2.0", - "has-proto": "^1.2.0", - "is-typed-array": "^1.1.14" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-byte-offset": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", - "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", - "license": "MIT", - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "for-each": "^0.3.3", - "gopd": "^1.2.0", - "has-proto": "^1.2.0", - "is-typed-array": "^1.1.15", - "reflect.getprototypeof": "^1.0.9" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-length": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.8.tgz", - "integrity": "sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.9", - "for-each": "^0.3.5", - "gopd": "^1.2.0", - "is-typed-array": "^1.1.15", - "possible-typed-array-names": "^1.1.0", - "reflect.getprototypeof": "^1.0.10" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typedarray.prototype.slice": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/typedarray.prototype.slice/-/typedarray.prototype.slice-1.0.5.tgz", - "integrity": "sha512-q7QNVDGTdl702bVFiI5eY4l/HkgCM6at9KhcFbgUAzezHFbOVy4+0O/lCjsABEQwbZPravVfBIiBVGo89yzHFg==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.9", - "es-errors": "^1.3.0", - "get-proto": "^1.0.1", - "math-intrinsics": "^1.1.0", - "typed-array-buffer": "^1.0.3", - "typed-array-byte-offset": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", @@ -10913,24 +7829,6 @@ "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/unbox-primitive": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", - "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-bigints": "^1.0.2", - "has-symbols": "^1.1.0", - "which-boxed-primitive": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/undici": { "version": "7.24.8", "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.8.tgz", @@ -10958,76 +7856,6 @@ "pathe": "^2.0.3" } }, - "node_modules/unified": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/unified/-/unified-9.2.2.tgz", - "integrity": "sha512-Sg7j110mtefBD+qunSLO1lqOEKdrwBFBrR6Qd8f4uwkhWNlbkaqwHse6e7QvD3AP/MNoJdEDLaf8OxYyoWgorQ==", - "license": "MIT", - "dependencies": { - "bail": "^1.0.0", - "extend": "^3.0.0", - "is-buffer": "^2.0.0", - "is-plain-obj": "^2.0.0", - "trough": "^1.0.0", - "vfile": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-is": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.1.0.tgz", - "integrity": "sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-stringify-position": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz", - "integrity": "sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==", - "license": "MIT", - "dependencies": { - "@types/unist": "^2.0.2" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-visit": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-2.0.3.tgz", - "integrity": "sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q==", - "license": "MIT", - "dependencies": { - "@types/unist": "^2.0.0", - "unist-util-is": "^4.0.0", - "unist-util-visit-parents": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-visit-parents": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-3.1.1.tgz", - "integrity": "sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg==", - "license": "MIT", - "dependencies": { - "@types/unist": "^2.0.0", - "unist-util-is": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -11048,15 +7876,6 @@ "url": "https://github.com/sponsors/kettanaito" } }, - "node_modules/untildify": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/untildify/-/untildify-3.0.3.tgz", - "integrity": "sha512-iSk/J8efr8uPT/Z4eSUywnqyrQU7DSdMfdqK4iWEaUVVmcP5JcnpRqmVMwcwcnmI1ATFNgC5V90u09tBynNFKA==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/update-browserslist-db": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", @@ -11114,20 +7933,6 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, - "node_modules/util": { - "version": "0.12.5", - "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", - "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "is-arguments": "^1.0.4", - "is-generator-function": "^1.0.7", - "is-typed-array": "^1.1.3", - "which-typed-array": "^1.1.2" - } - }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -11145,36 +7950,6 @@ "node": ">= 0.8" } }, - "node_modules/vfile": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-4.2.1.tgz", - "integrity": "sha512-O6AE4OskCG5S1emQ/4gl8zK586RqA3srz3nfK/Viy0UPToBc5Trp9BVFb1u0CjsKrAWwnpr4ifM/KBXPWwJbCA==", - "license": "MIT", - "dependencies": { - "@types/unist": "^2.0.0", - "is-buffer": "^2.0.0", - "unist-util-stringify-position": "^2.0.0", - "vfile-message": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/vfile-message": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-2.0.4.tgz", - "integrity": "sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ==", - "license": "MIT", - "dependencies": { - "@types/unist": "^2.0.0", - "unist-util-stringify-position": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/victory-vendor": { "version": "37.3.6", "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz", @@ -11376,16 +8151,6 @@ "loose-envify": "^1.0.0" } }, - "node_modules/web-namespaces": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-1.1.4.tgz", - "integrity": "sha512-wYxSGajtmoP4WxfejAPIr4l0fVh+jeMXZb08wNc0tMg6xsfZXj3cECqIK0G7ZAqUq0PP8WlMDtaOGVBTAWztNw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/webidl-conversions": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", @@ -11437,91 +8202,6 @@ "node": ">= 8" } }, - "node_modules/which-boxed-primitive": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", - "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", - "license": "MIT", - "dependencies": { - "is-bigint": "^1.1.0", - "is-boolean-object": "^1.2.1", - "is-number-object": "^1.1.1", - "is-string": "^1.1.1", - "is-symbol": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-builtin-type": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", - "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "function.prototype.name": "^1.1.6", - "has-tostringtag": "^1.0.2", - "is-async-function": "^2.0.0", - "is-date-object": "^1.1.0", - "is-finalizationregistry": "^1.1.0", - "is-generator-function": "^1.0.10", - "is-regex": "^1.2.1", - "is-weakref": "^1.0.2", - "isarray": "^2.0.5", - "which-boxed-primitive": "^1.1.0", - "which-collection": "^1.0.2", - "which-typed-array": "^1.1.16" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-collection": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", - "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", - "license": "MIT", - "dependencies": { - "is-map": "^2.0.3", - "is-set": "^2.0.3", - "is-weakmap": "^2.0.2", - "is-weakset": "^2.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-typed-array": { - "version": "1.1.21", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.21.tgz", - "integrity": "sha512-zbRA8cVm6io/d5W8uIe2hblzN76/Wm3v/yiythQvr+dpBWeqhPSWIDNj4zOyHi4zKbMK6DN34Xsr9jPHJERAEw==", - "license": "MIT", - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.9", - "call-bound": "^1.0.4", - "for-each": "^0.3.5", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/why-is-node-running": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", @@ -12111,17 +8791,6 @@ "devOptional": true, "license": "ISC" }, - "node_modules/write-file-atomic": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-1.3.4.tgz", - "integrity": "sha512-SdrHoC/yVBPpV0Xq/mUZQIpW2sWXAShb/V4pomcJXh92RuaO+f3UTWItiR3Px+pLnV2PvC2/bfn5cwr5X6Vfxw==", - "license": "ISC", - "dependencies": { - "graceful-fs": "^4.1.11", - "imurmurhash": "^0.1.4", - "slide": "^1.1.5" - } - }, "node_modules/ws": { "version": "8.18.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", @@ -12161,15 +8830,6 @@ "dev": true, "license": "MIT" }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "license": "MIT", - "engines": { - "node": ">=0.4" - } - }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -12274,15 +8934,6 @@ "error-stack-parser-es": "^1.0.5" } }, - "node_modules/zlibjs": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/zlibjs/-/zlibjs-0.3.1.tgz", - "integrity": "sha512-+J9RrgTKOmlxFSDHo0pI1xM6BLVUv+o0ZT9ANtCxGkjIVCCUdx9alUF8Gm+dGLKbkkkidWIHFDZHDMpfITt4+w==", - "license": "MIT", - "engines": { - "node": "*" - } - }, "node_modules/zod": { "version": "4.3.6", "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", @@ -12305,16 +8956,6 @@ "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } - }, - "node_modules/zwitch": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-1.0.5.tgz", - "integrity": "sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } } } } diff --git a/frontend/package.json b/frontend/package.json index 2c82e4d0..1eb1842e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -21,20 +21,15 @@ }, "dependencies": { "@reduxjs/toolkit": "^2.11.2", - "@textlint/kernel": "^15.7.1", - "@textlint/textlint-plugin-text": "^15.7.1", "dompurify": "^3.4.7", "marked": "^17.0.4", - "prh": "^6.0.6", "react": "^18.3.1", "react-dom": "^18.3.1", "react-pdf": "^9.2.1", "react-redux": "^9.2.0", "react-router-dom": "^7.13.1", "recharts": "^3.8.0", - "redux-persist": "^6.0.0", - "textlint-rule-preset-ja-technical-writing": "^12.0.2", - "textlint-rule-prh": "^6.1.0" + "redux-persist": "^6.0.0" }, "devDependencies": { "@eslint/js": "^9.39.4", @@ -42,13 +37,11 @@ "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.3.2", "@testing-library/user-event": "^14.6.1", - "@textlint/types": "^15.7.1", "@types/node": "^25.5.2", "@types/react": "^18.3.15", "@types/react-dom": "^18.3.1", "@vitejs/plugin-react": "^4.4.1", "@vitest/coverage-v8": "^4.1.2", - "assert": "^2.1.0", "concurrently": "^9.2.1", "eslint": "^9.39.4", "eslint-config-prettier": "^10.1.8", @@ -59,8 +52,6 @@ "jsdom": "^29.0.1", "msw": "^2.13.2", "openapi-typescript": "^7.13.0", - "os-browserify": "^0.3.0", - "path-browserify": "^1.0.1", "playwright": "^1.56.0", "prettier": "^3.8.1", "typescript": "^5.7.2", diff --git a/frontend/public/_headers b/frontend/public/_headers deleted file mode 100644 index dad4c164..00000000 --- a/frontend/public/_headers +++ /dev/null @@ -1,7 +0,0 @@ -# kuromoji(校正機能の形態素解析)辞書の配信ヘッダ(Cloudflare Pages 用)。 -# .gz を gzip 転送エンコーディング扱いさせず、生の gzip バイト列として返す -# (kuromoji が自前で gunzip するため)。octet-stream 指定で edge の自動圧縮も避ける。 -# 辞書は不変なので長期キャッシュする。 -/kuromoji-dict/* - Content-Type: application/octet-stream - Cache-Control: public, max-age=31536000, immutable diff --git a/frontend/public/kuromoji-dict/base.dat.gz b/frontend/public/kuromoji-dict/base.dat.gz deleted file mode 100644 index 917954c3..00000000 Binary files a/frontend/public/kuromoji-dict/base.dat.gz and /dev/null differ diff --git a/frontend/public/kuromoji-dict/cc.dat.gz b/frontend/public/kuromoji-dict/cc.dat.gz deleted file mode 100644 index 7d867067..00000000 Binary files a/frontend/public/kuromoji-dict/cc.dat.gz and /dev/null differ diff --git a/frontend/public/kuromoji-dict/check.dat.gz b/frontend/public/kuromoji-dict/check.dat.gz deleted file mode 100644 index 7e977a10..00000000 Binary files a/frontend/public/kuromoji-dict/check.dat.gz and /dev/null differ diff --git a/frontend/public/kuromoji-dict/tid.dat.gz b/frontend/public/kuromoji-dict/tid.dat.gz deleted file mode 100644 index 195a8707..00000000 Binary files a/frontend/public/kuromoji-dict/tid.dat.gz and /dev/null differ diff --git a/frontend/public/kuromoji-dict/tid_map.dat.gz b/frontend/public/kuromoji-dict/tid_map.dat.gz deleted file mode 100644 index d04bba3b..00000000 Binary files a/frontend/public/kuromoji-dict/tid_map.dat.gz and /dev/null differ diff --git a/frontend/public/kuromoji-dict/tid_pos.dat.gz b/frontend/public/kuromoji-dict/tid_pos.dat.gz deleted file mode 100644 index e4d5cffd..00000000 Binary files a/frontend/public/kuromoji-dict/tid_pos.dat.gz and /dev/null differ diff --git a/frontend/public/kuromoji-dict/unk.dat.gz b/frontend/public/kuromoji-dict/unk.dat.gz deleted file mode 100644 index 83464686..00000000 Binary files a/frontend/public/kuromoji-dict/unk.dat.gz and /dev/null differ diff --git a/frontend/public/kuromoji-dict/unk_char.dat.gz b/frontend/public/kuromoji-dict/unk_char.dat.gz deleted file mode 100644 index 8b7693a3..00000000 Binary files a/frontend/public/kuromoji-dict/unk_char.dat.gz and /dev/null differ diff --git a/frontend/public/kuromoji-dict/unk_compat.dat.gz b/frontend/public/kuromoji-dict/unk_compat.dat.gz deleted file mode 100644 index 87030e97..00000000 Binary files a/frontend/public/kuromoji-dict/unk_compat.dat.gz and /dev/null differ diff --git a/frontend/public/kuromoji-dict/unk_invoke.dat.gz b/frontend/public/kuromoji-dict/unk_invoke.dat.gz deleted file mode 100644 index c6736eba..00000000 Binary files a/frontend/public/kuromoji-dict/unk_invoke.dat.gz and /dev/null differ diff --git a/frontend/public/kuromoji-dict/unk_map.dat.gz b/frontend/public/kuromoji-dict/unk_map.dat.gz deleted file mode 100644 index 12d427bf..00000000 Binary files a/frontend/public/kuromoji-dict/unk_map.dat.gz and /dev/null differ diff --git a/frontend/public/kuromoji-dict/unk_pos.dat.gz b/frontend/public/kuromoji-dict/unk_pos.dat.gz deleted file mode 100644 index 50bb65de..00000000 Binary files a/frontend/public/kuromoji-dict/unk_pos.dat.gz and /dev/null differ diff --git a/frontend/src/api/generated.ts b/frontend/src/api/generated.ts index 15122729..fc69220a 100644 --- a/frontend/src/api/generated.ts +++ b/frontend/src/api/generated.ts @@ -933,7 +933,7 @@ export interface components { }; /** * ContributionCalendar - * @description 直近1年のコントリビューションカレンダー(GitHub の緑の四角)。 + * @description 1年分のコントリビューションカレンダー(GitHub の緑の四角)。 */ ContributionCalendar: { /** @@ -946,6 +946,11 @@ export interface components { * @description 週ごとの日配列(列=週、各週は最大7日) */ weeks?: components["schemas"]["ContributionDay"][][]; + /** + * Year + * @description このカレンダーが対象とする西暦年 + */ + year: number; }; /** * ContributionDay @@ -1088,29 +1093,11 @@ export interface components { GitHubLinkResponse: { /** Analyzed At */ analyzed_at: string; - /** @description 直近1年のコントリビューションカレンダー(取得失敗時は None) */ - contribution_calendar?: components["schemas"]["ContributionCalendar"] | null; - /** - * Detected Devtools - * @description ルートファイルから検出した DevTools 名 → 使用リポジトリ数 - */ - detected_devtools?: { - [key: string]: number; - }; /** - * Detected Frameworks - * @description 依存関係から検出したフレームワーク名 → 使用リポジトリ数 + * Contribution Calendars + * @description 年ごとのコントリビューションカレンダー(新しい年順。取得失敗時は空配列) */ - detected_frameworks?: { - [key: string]: number; - }; - /** - * Detected Infras - * @description ルートファイルから検出したインフラツール名 → 使用リポジトリ数 - */ - detected_infras?: { - [key: string]: number; - }; + contribution_calendars?: components["schemas"]["ContributionCalendar"][]; /** * Languages * @description 言語ごとのバイト数(GitHub linguist ベース) diff --git a/frontend/src/api/types.ts b/frontend/src/api/types.ts index 76115802..4627454d 100644 --- a/frontend/src/api/types.ts +++ b/frontend/src/api/types.ts @@ -31,7 +31,7 @@ export type SubProgress = Schemas["SubProgress"]; /** コントリビューションカレンダーの 1 日分。backend `schemas/github_link.py:ContributionDay`。 */ export type ContributionDay = Schemas["ContributionDay"]; -/** 直近1年のコントリビューションカレンダー。backend `schemas/github_link.py:ContributionCalendar`。 */ +/** 年ごとのコントリビューションカレンダー。backend `schemas/github_link.py:ContributionCalendar`。 */ export type ContributionCalendar = Schemas["ContributionCalendar"]; /** GitHub 連携の解析結果。backend `schemas/github_link.py:GitHubLinkResponse`。 */ diff --git a/frontend/src/components/forms/CareerDiffModal.module.css b/frontend/src/components/forms/CareerDiffModal.module.css index b5e32263..1edd53f8 100644 --- a/frontend/src/components/forms/CareerDiffModal.module.css +++ b/frontend/src/components/forms/CareerDiffModal.module.css @@ -254,58 +254,3 @@ padding: 0.75rem 1.25rem; border-top: 1px solid var(--border-color, #e0e0e0); } - -.proofreadLoading { - font-size: 0.7rem; - font-weight: 400; - color: #3b82f6; -} - -/* 校正の指摘リスト(青系・控えめ)。エントリ内の差分の下に続く。 */ -.entryIssues { - list-style: none; - margin: 0; - padding: 0.3rem 0.5rem 0.4rem 0.55rem; - display: flex; - flex-direction: column; - gap: 0.3rem; - background: rgba(37, 99, 235, 0.05); - border-top: 1px solid rgba(37, 99, 235, 0.18); -} - -.proofreadItem { - border-left: 2px solid rgba(37, 99, 235, 0.45); - padding-left: 0.45rem; -} - -.proofreadMessage { - margin: 0; - font-size: 0.74rem; - color: var(--text-primary, #1f2937); - line-height: 1.4; - white-space: pre-wrap; - overflow-wrap: break-word; -} - -.proofreadExcerpt { - margin: 0.15rem 0 0; - font-size: 0.68rem; - color: var(--text-muted, #6b7280); - overflow-wrap: break-word; -} - -.proofreadEmpty { - margin: 0; - padding: 0 0.7rem 0.4rem; - font-size: 0.74rem; - color: var(--text-muted, #6b7280); -} - -.proofreadHint { - flex-shrink: 0; - margin: 0; - padding: 0.35rem 0.7rem 0.5rem; - font-size: 0.66rem; - color: #2563eb; - line-height: 1.4; -} diff --git a/frontend/src/components/forms/CareerDiffModal.tsx b/frontend/src/components/forms/CareerDiffModal.tsx index dae5b985..5849db41 100644 --- a/frontend/src/components/forms/CareerDiffModal.tsx +++ b/frontend/src/components/forms/CareerDiffModal.tsx @@ -1,7 +1,6 @@ import { useMemo, useRef } from "react"; -import { DIFF_DIALOG_MESSAGES as D, PROOFREAD_MESSAGES as P } from "../../constants/messages"; -import type { ProofreadIssue } from "../../proofread/types"; +import { DIFF_DIALOG_MESSAGES as D } from "../../constants/messages"; import { buildReviewEntries } from "../../utils/careerReview"; import type { CareerChange, ChangeKind } from "../../utils/careerDiff"; import { @@ -22,7 +21,6 @@ const KIND_LABEL: Record = { /** * iframe 内に注入する diff 着色 CSS。backend の resume.css に追記する形で srcDoc に埋め込む。 * 緑=追加 / 赤=削除 / 黄=修正。VSCode 系 diff の配色に寄せる。 - * 校正指摘は青の波線(diff の背景色と重ねても潰れないよう下線で表現)。 */ const DIFF_CSS = ` body { margin: 0; padding: 12px 16px; background: #fff; } @@ -30,11 +28,6 @@ const DIFF_CSS = ` .diff-added { background: rgba(22,163,74,0.18); box-shadow: 0 0 0 1px rgba(22,163,74,0.45); } .diff-removed { background: rgba(220,38,38,0.16); box-shadow: 0 0 0 1px rgba(220,38,38,0.40); } .diff-modified { background: rgba(234,179,8,0.25); box-shadow: 0 0 0 1px rgba(234,179,8,0.50); } - .diff-proofread { - text-decoration: underline wavy #2563eb; - text-decoration-skip-ink: none; - text-underline-offset: 2px; - } details.fold { margin: 3px 0; } summary.fold-summary { cursor: pointer; list-style: none; font-size: 8pt; color: #6b7280; @@ -71,9 +64,6 @@ export function CareerDiffModal({ loading, error, saving, - issues, - proofreading, - proofreadError, onConfirm, onCancel, onRollback, @@ -85,12 +75,6 @@ export function CareerDiffModal({ loading: boolean; error: string | null; saving: boolean; - /** 編集中フォームの校正指摘(フィールド横断)。 */ - issues: ProofreadIssue[]; - /** 校正処理中フラグ。 */ - proofreading: boolean; - /** 校正失敗時のメッセージ(null なら正常)。 */ - proofreadError: string | null; onConfirm: () => void; onCancel: () => void; onRollback: (change: CareerChange) => void; @@ -101,19 +85,12 @@ export function CareerDiffModal({ const pathKindMap = useMemo(() => buildPathKindMap(changes), [changes]); /** - * 変更点と校正指摘を 1 本のレビュー一覧へ統合し、PDF レイアウト順に並べる。 + * 変更点をフィールド単位にまとめ、PDF レイアウト順に並べる。 * 左右ペイン(PDF)とサイドバーの縦順が一致し、上から順に突合できる。 */ - const reviewEntries = useMemo(() => buildReviewEntries(changes, issues), [changes, issues]); - - /** 校正指摘のあるフィールド id 集合(編集中ペインの青マーク/折りたたみ除外に使う)。 */ - const proofreadFieldIds = useMemo( - () => new Set(issues.map((issue) => issue.fieldId)), - [issues], - ); + const reviewEntries = useMemo(() => buildReviewEntries(changes), [changes]); // 着色(annotateHtml)→ 変更なし領域を畳む(foldUnchanged)の順で整形する。 - // baseline(保存済み)側は校正マークを付けない(指摘は編集中フォームに対するもの)。 const baselineDoc = useMemo(() => { if (baselineHtml === null) return null; return buildSrcDoc(css, foldUnchanged(annotateHtml(baselineHtml, pathKindMap), pathKindMap)); @@ -121,19 +98,21 @@ export function CareerDiffModal({ const editedDoc = useMemo(() => { if (editedHtml === null) return null; - // 着色(差分+校正青マーク)→ 削除跡のプレースホルダ挿入 → 変更なし領域の折りたたみ。 - // 校正指摘のある項目は畳まないよう foldUnchanged にも fieldId 集合を渡す。 - const annotated = annotateHtml(editedHtml, pathKindMap, proofreadFieldIds); + // 着色(差分)→ 削除跡のプレースホルダ挿入 → 変更なし領域の折りたたみ。 + const annotated = annotateHtml(editedHtml, pathKindMap); const withStubs = injectRemovedPlaceholders(annotated, changes); - return buildSrcDoc(css, foldUnchanged(withStubs, pathKindMap, proofreadFieldIds)); - }, [editedHtml, css, pathKindMap, changes, proofreadFieldIds]); + return buildSrcDoc(css, foldUnchanged(withStubs, pathKindMap)); + }, [editedHtml, css, pathKindMap, changes]); /** レビュー項目クリックで、右ペイン(編集中)の該当ノードへスクロールする。 */ const scrollToPath = (fp: string) => { const doc = editedFrameRef.current?.contentDocument; if (!doc) return; const escaped = CSS.escape(fp); + // 削除項目は編集中ペインから消えており data-fp が無い(次要素が繰り上がって同じ + // パスを持つ)ため、まず injectRemovedPlaceholders が挿す削除スタブ(data-removed)を狙う。 const target = + doc.querySelector(`[data-removed="${escaped}"]`) ?? doc.querySelector(`[data-fp="${escaped}"]`) ?? doc.querySelector(`[data-fp^="${escaped}."]`); target?.scrollIntoView({ block: "center", behavior: "smooth" }); @@ -194,13 +173,11 @@ export function CareerDiffModal({ {loading && editedDoc &&
{D.PREVIEW_LOADING}
} - {/* レビュー一覧: 変更点と校正を 1 本に統合し PDF レイアウト順に並べる。 */} + {/* 変更点リスト: PDF レイアウト順に並べる。 */} diff --git a/frontend/src/components/forms/CareerResumeForm.tsx b/frontend/src/components/forms/CareerResumeForm.tsx index f7bb9617..23dc70f2 100644 --- a/frontend/src/components/forms/CareerResumeForm.tsx +++ b/frontend/src/components/forms/CareerResumeForm.tsx @@ -14,7 +14,6 @@ import { SUCCESS_MESSAGES, UI_MESSAGES } from "../../constants/messages"; import { createInitialCareerForm, mapCareerResumeToForm } from "../../formMappers"; import { useCareerDirty } from "../../hooks/career/useCareerDirty"; import { useImportPanelLayout } from "../../hooks/career/useImportPanelLayout"; -import { useProofread } from "../../hooks/career/useProofread"; import { useResumeDiffPreview } from "../../hooks/career/useResumeDiffPreview"; import { useResumeImportAssist } from "../../hooks/career/useResumeImportAssist"; import { useDocumentForm } from "../../hooks/useDocumentForm"; @@ -104,9 +103,6 @@ export function CareerResumeForm() { /** 左右 diff モーダル用の整形 HTML プレビュー(保存済み / 編集中)。開いている間だけ取得する。 */ const preview = useResumeDiffPreview(form, baseline, showSaveConfirm); - /** 保存確認ダイアログが開いている間、編集中フォームを校正する(誤字脱字・表記ゆれ)。 */ - const proofread = useProofread(form, showSaveConfirm); - const { downloading, previewUrl, @@ -226,9 +222,6 @@ export function CareerResumeForm() { loading={preview.loading} error={preview.error} saving={saving} - issues={proofread.issues} - proofreading={proofread.proofreading} - proofreadError={proofread.error} onConfirm={handleConfirmSave} onCancel={() => setShowSaveConfirm(false)} onRollback={(change) => setForm((prev) => change.rollback(prev))} diff --git a/frontend/src/components/forms/MarkdownFieldTrigger.tsx b/frontend/src/components/forms/MarkdownFieldTrigger.tsx index aa35d7a9..df7716a6 100644 --- a/frontend/src/components/forms/MarkdownFieldTrigger.tsx +++ b/frontend/src/components/forms/MarkdownFieldTrigger.tsx @@ -1,15 +1,16 @@ import { UI_MESSAGES } from "../../constants/messages"; import shared from "../../styles/shared.module.css"; +import { markdownToPlainText } from "../../utils/markdown"; import { DirtyDot } from "../ui/DirtyDot"; import { Skeleton } from "../ui/Skeleton"; import styles from "./MarkdownFieldTrigger.module.css"; /** プレビューに出す最大文字数(超過分は省略記号で切り詰め) */ -const PREVIEW_MAX_LENGTH = 120; +const PREVIEW_MAX_LENGTH = 10; -/** 改行を空白に潰し、先頭を切り詰めた 1 行プレビュー文字列を作る。 */ +/** Markdown 記法を除去し、改行を空白に潰した先頭 10 文字プレビュー文字列を作る。 */ function toPreview(value: string): string { - const flat = value.replace(/\s+/g, " ").trim(); + const flat = markdownToPlainText(value).replace(/\s+/g, " ").trim(); if (flat.length <= PREVIEW_MAX_LENGTH) return flat; return `${flat.slice(0, PREVIEW_MAX_LENGTH)}…`; } diff --git a/frontend/src/components/github-link/ContributionHeatmap.test.tsx b/frontend/src/components/github-link/ContributionHeatmap.test.tsx index 3f14348a..0b7a54db 100644 --- a/frontend/src/components/github-link/ContributionHeatmap.test.tsx +++ b/frontend/src/components/github-link/ContributionHeatmap.test.tsx @@ -5,6 +5,7 @@ import type { ContributionCalendar } from "../../api"; function makeCalendar(): ContributionCalendar { return { + year: 2024, total_contributions: 15, weeks: [ [ @@ -21,10 +22,10 @@ function makeCalendar(): ContributionCalendar { } describe("ContributionHeatmap", () => { - it("年間コントリビュート総数を表示する", () => { + it("対象年のコントリビュート総数を年ラベル付きで表示する", () => { render(); expect(screen.getByText("15")).toBeInTheDocument(); - expect(screen.getByText("年間コントリビュート")).toBeInTheDocument(); + expect(screen.getByText("2024年のコントリビュート")).toBeInTheDocument(); }); it("各日セルに日付とコントリビューション数の title を付ける", () => { @@ -40,6 +41,7 @@ describe("ContributionHeatmap", () => { it("最大連続日数を算出する(count>0 の連続セル)", () => { // 実データは全日が連続して並ぶ。0 で途切れ、最大連続は 3。 const calendar: ContributionCalendar = { + year: 2024, total_contributions: 6, weeks: [ [ @@ -65,7 +67,7 @@ describe("ContributionHeatmap", () => { it("空の weeks でもクラッシュしない", () => { render( , ); // total=0 / streak=0 の 2 枚のカードが描画される diff --git a/frontend/src/components/github-link/ContributionHeatmap.tsx b/frontend/src/components/github-link/ContributionHeatmap.tsx index 4ce3ec18..7285d72d 100644 --- a/frontend/src/components/github-link/ContributionHeatmap.tsx +++ b/frontend/src/components/github-link/ContributionHeatmap.tsx @@ -1,5 +1,10 @@ import { useMemo } from "react"; import type { ContributionCalendar, ContributionDay } from "../../api"; +import { + GITHUB_LINK_MESSAGES, + contributionAriaLabel, + contributionSummaryLabel, +} from "../../constants/messages"; import styles from "./ContributionHeatmap.module.css"; const MONTH_NAMES = [ @@ -56,18 +61,18 @@ export function ContributionHeatmap({ calendar }: ContributionHeatmapProps) {
{calendar.total_contributions}
-
年間コントリビュート
+
{contributionSummaryLabel(calendar.year)}
{longestStreak}
-
最大連続日数
+
{GITHUB_LINK_MESSAGES.LONGEST_STREAK_LABEL}
{monthLabels.map((label, i) => ( diff --git a/frontend/src/components/github-link/GitHubLinkDashboard.module.css b/frontend/src/components/github-link/GitHubLinkDashboard.module.css index 2c58acd1..b5949a41 100644 --- a/frontend/src/components/github-link/GitHubLinkDashboard.module.css +++ b/frontend/src/components/github-link/GitHubLinkDashboard.module.css @@ -171,6 +171,29 @@ color: var(--text-secondary); } +/* セクション見出しと年セレクタを横並びにする(Activity 用) */ +.sectionHeader { + display: flex; + align-items: center; + justify-content: space-between; + gap: 0.5rem; + margin-bottom: 0.8rem; +} + +.sectionHeader h2 { + margin: 0; +} + +.yearSelect { + padding: 0.3rem 0.6rem; + font-size: 0.85rem; + border: 1px solid var(--border); + border-radius: 6px; + background: var(--bg-card); + color: var(--text-primary); + cursor: pointer; +} + /* ── Overview ───────────────────────────────────────────────── */ .overviewCards { diff --git a/frontend/src/components/github-link/GitHubLinkDashboard.test.tsx b/frontend/src/components/github-link/GitHubLinkDashboard.test.tsx index 179d302c..51b545cc 100644 --- a/frontend/src/components/github-link/GitHubLinkDashboard.test.tsx +++ b/frontend/src/components/github-link/GitHubLinkDashboard.test.tsx @@ -1,4 +1,5 @@ import { screen, waitFor } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; import { describe, it, expect } from "vitest"; import { http, HttpResponse } from "msw"; import { server } from "../../test/mswServer"; @@ -78,46 +79,32 @@ describe("GitHubLinkDashboard", () => { expect(screen.getByText("リポジトリ")).toBeInTheDocument(); }); - it("コントリビューションカレンダーがあると Activity ヒートマップが表示される", async () => { + it("コントリビューションカレンダーがあると Activity ヒートマップが最新年で表示される", async () => { renderPage(); await waitFor(() => { expect(screen.getByText("Activity")).toBeInTheDocument(); }); - expect(screen.getByText("年間コントリビュート")).toBeInTheDocument(); + // 配列先頭(最新年=2025)がデフォルト表示される + expect(screen.getByText("2025年のコントリビュート")).toBeInTheDocument(); expect(screen.getByText("123")).toBeInTheDocument(); }); - it("検出フレームワークがあるとき Frameworks セクションにバーが表示される", async () => { + it("年セレクタで年を切り替えるとヒートマップの集計が切り替わる", async () => { renderPage(); await waitFor(() => { - expect(screen.getByText("Frameworks")).toBeInTheDocument(); - }); - expect(screen.getByText("React")).toBeInTheDocument(); - expect(screen.getByText("FastAPI")).toBeInTheDocument(); - }); - - it("DevTools があるとき DevTools セクションが表示される", async () => { - renderPage(); - - await waitFor(() => { - expect(screen.getByText("DevTools")).toBeInTheDocument(); + expect(screen.getByText("Activity")).toBeInTheDocument(); }); - expect(screen.getByText("Docker")).toBeInTheDocument(); - expect(screen.getByText("GitHub Actions")).toBeInTheDocument(); - }); - it("Infra があるとき Infra セクションが表示される", async () => { - renderPage(); + const select = screen.getByRole("combobox", { name: "表示する年" }); + await userEvent.selectOptions(select, "2024"); - await waitFor(() => { - expect(screen.getByText("Infra")).toBeInTheDocument(); - }); - expect(screen.getByText("Terraform")).toBeInTheDocument(); + expect(screen.getByText("2024年のコントリビュート")).toBeInTheDocument(); + expect(screen.getByText("88")).toBeInTheDocument(); }); - it("検出フレームワークが空のとき Frameworks セクションが描画されない", async () => { + it("コントリビューションカレンダーが空のとき Activity セクションが描画されない", async () => { server.use( http.get("*/api/github-link/cache", () => HttpResponse.json({ @@ -128,9 +115,7 @@ describe("GitHubLinkDashboard", () => { unique_skills: 0, analyzed_at: "2026-01-01T00:00:00Z", languages: { TypeScript: 100 }, - detected_frameworks: {}, - detected_devtools: {}, - detected_infras: {}, + contribution_calendars: [], }, }), ), @@ -143,7 +128,7 @@ describe("GitHubLinkDashboard", () => { screen.getByText("test-user-001 の連携結果"), ).toBeInTheDocument(); }); - expect(screen.queryByText("Frameworks")).not.toBeInTheDocument(); + expect(screen.queryByText("Activity")).not.toBeInTheDocument(); }); it("サイドバーからの連携実行(runNonce)でポーリング画面に遷移する", async () => { diff --git a/frontend/src/components/github-link/GitHubLinkDashboard.tsx b/frontend/src/components/github-link/GitHubLinkDashboard.tsx index 75a3acaa..90cec692 100644 --- a/frontend/src/components/github-link/GitHubLinkDashboard.tsx +++ b/frontend/src/components/github-link/GitHubLinkDashboard.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef } from "react"; +import { useEffect, useRef, useState } from "react"; import { useLocation } from "react-router-dom"; import { runGitHubLink, @@ -11,11 +11,16 @@ import { import { InlineSpinner } from "../ui/InlineSpinner"; import { AsyncTaskLoading } from "../ui/AsyncTaskLoading"; import { useAppErrorToast } from "../ui/toast"; -import { FALLBACK_MESSAGES, LOADING_MESSAGES, UI_MESSAGES } from "../../constants/messages"; +import { + FALLBACK_MESSAGES, + GITHUB_LINK_MESSAGES, + LOADING_MESSAGES, + UI_MESSAGES, + yearLabel, +} from "../../constants/messages"; import { useAsyncTaskPage } from "../../hooks/useAsyncTaskPage"; import { ContributionHeatmap } from "./ContributionHeatmap"; import { LanguageBar } from "./LanguageBar"; -import { TechBar } from "./TechBar"; import shared from "../../styles/shared.module.css"; import styles from "./GitHubLinkDashboard.module.css"; @@ -36,6 +41,8 @@ type GitHubLinkNavState = { export function GitHubLinkDashboard() { const location = useLocation(); const handledNonceRef = useRef(undefined); + // ヒートマップで表示中の年(null のときは最新年=配列先頭を表示) + const [selectedYear, setSelectedYear] = useState(null); const { phase, result, error, setError, transitionToPolling } = useAsyncTaskPage({ @@ -102,13 +109,34 @@ export function GitHubLinkDashboard() {

{result.username} の連携結果

- {/* アクティビティ(コントリビューションヒートマップ) */} - {result.contribution_calendar && ( -
-

Activity

- -
- )} + {/* アクティビティ(年ごとのコントリビューションヒートマップ) */} + {(() => { + const calendars = result.contribution_calendars ?? []; + if (calendars.length === 0) return null; + // selectedYear が未選択 or 該当年が無い場合は先頭(最新年)にフォールバック + const active = + calendars.find((c) => c.year === selectedYear) ?? calendars[0]; + return ( +
+
+

{GITHUB_LINK_MESSAGES.ACTIVITY_HEADING}

+ +
+ +
+ ); + })()} {/* 概要 */}
@@ -132,36 +160,6 @@ export function GitHubLinkDashboard() {
)} - - {/* 検出フレームワーク */} - {result.detected_frameworks && - Object.keys(result.detected_frameworks).length > 0 && ( -
-

Frameworks

- -
- )} - - {/* DevTools */} - {result.detected_devtools && - Object.keys(result.detected_devtools).length > 0 && ( -
-

DevTools

- -
- )} - - {/* インフラ */} - {result.detected_infras && - Object.keys(result.detected_infras).length > 0 && ( -
-

Infra

- -
- )} )}
diff --git a/frontend/src/components/github-link/TechBar.test.tsx b/frontend/src/components/github-link/TechBar.test.tsx deleted file mode 100644 index c6f91f49..00000000 --- a/frontend/src/components/github-link/TechBar.test.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { render, screen } from "@testing-library/react"; -import { describe, it, expect } from "vitest"; -import { TechBar } from "./TechBar"; - -describe("TechBar", () => { - it("techs が空オブジェクトの場合は何も描画しない", () => { - const { container } = render(); - expect(container).toBeEmptyDOMElement(); - }); - - it("渡された各ツール名を凡例として表示する", () => { - render(); - expect(screen.getByText("React")).toBeInTheDocument(); - expect(screen.getByText("FastAPI")).toBeInTheDocument(); - expect(screen.getByText("Docker")).toBeInTheDocument(); - }); - - it("割合(%)が表示される", () => { - render(); - expect(screen.getByText("100.0%")).toBeInTheDocument(); - }); - - it("リポジトリ数の多い順に並ぶ", () => { - render(); - const texts = screen - .getAllByText(/^(React|FastAPI|Vue)$/) - .map((el) => el.textContent); - expect(texts.indexOf("React")).toBeLessThan(texts.indexOf("FastAPI")); - expect(texts.indexOf("FastAPI")).toBeLessThan(texts.indexOf("Vue")); - }); -}); diff --git a/frontend/src/components/github-link/TechBar.tsx b/frontend/src/components/github-link/TechBar.tsx deleted file mode 100644 index 6a4467ae..00000000 --- a/frontend/src/components/github-link/TechBar.tsx +++ /dev/null @@ -1,151 +0,0 @@ -import { useMemo, useState } from "react"; -import styles from "./TechBar.module.css"; - -/** 主要フレームワーク・DevTools・インフラのカラーマッピング */ -const TECH_COLORS: Record = { - // フロントエンドフレームワーク - React: "#61dafb", - "Next.js": "#0070f3", - Vue: "#42b883", - "Nuxt.js": "#00dc82", - Angular: "#dd0031", - Svelte: "#ff3e00", - Astro: "#ff5a03", - Gatsby: "#663399", - Remix: "#3992ff", - // バックエンドフレームワーク - FastAPI: "#009688", - Django: "#092e20", - Flask: "#8a8a8a", - Express: "#404040", - NestJS: "#e0234e", - "Spring Boot": "#6db33f", - Gin: "#00add8", - Echo: "#2196f3", - Fiber: "#00acd7", - // データ・ML - Pandas: "#150458", - NumPy: "#4dabcf", - "scikit-learn": "#f89939", - TensorFlow: "#ff6f00", - PyTorch: "#ee4c2c", - LangChain: "#1c3c3c", - // データストア・インフラ系 (dependency 由来) - SQLAlchemy: "#d71f00", - GraphQL: "#e10098", - Redis: "#dc382d", - MongoDB: "#47a248", - PostgreSQL: "#336791", - AWS: "#ff9900", - GCP: "#4285f4", - Azure: "#0089d6", - // DevTools - Docker: "#2496ed", - "Docker Compose": "#1d63a1", - "GitHub Actions": "#2088ff", - Jenkins: "#d24939", - "GitLab CI": "#fc6d26", - CircleCI: "#343434", - Make: "#427819", - // インフラ - Terraform: "#7b42bc", -}; - -const FALLBACK_COLORS = [ - "#6e7681", - "#8b949e", - "#7c8088", - "#636c76", - "#545d68", -]; - -interface TechBarProps { - /** ツール名 → 使用リポジトリ数 */ - techs: Record; - /** リスト要素の aria-label */ - ariaLabel?: string; -} - -export function TechBar({ techs, ariaLabel }: TechBarProps) { - const [hoveredTech, setHoveredTech] = useState(null); - - const items = useMemo(() => { - const total = Object.values(techs).reduce((sum, v) => sum + v, 0); - if (total === 0) return []; - - const sorted = Object.entries(techs).sort(([, a], [, b]) => b - a); - - let fallbackIdx = 0; - return sorted.map(([name, count]) => { - const percentage = (count / total) * 100; - const color = - TECH_COLORS[name] ?? - FALLBACK_COLORS[fallbackIdx++ % FALLBACK_COLORS.length]; - return { name, count, percentage, color }; - }); - }, [techs]); - - if (items.length === 0) return null; - - const hoveredItem = hoveredTech - ? items.find((i) => i.name === hoveredTech) - : null; - let tooltipLeft = 0; - if (hoveredItem) { - let cumulative = 0; - for (const item of items) { - if (item.name === hoveredTech) { - tooltipLeft = cumulative + item.percentage / 2; - break; - } - cumulative += item.percentage; - } - } - - return ( -
-
- {hoveredItem && ( - - {hoveredItem.name}{" "} - {hoveredItem.percentage < 0.1 - ? "<0.1" - : hoveredItem.percentage.toFixed(1)} - % - - )} -
- {items.map(({ name, percentage, color }) => ( - setHoveredTech(name)} - onMouseLeave={() => setHoveredTech(null)} - /> - ))} -
-
- -
    - {items.map(({ name, percentage, color }) => ( -
  • setHoveredTech(name)} - onMouseLeave={() => setHoveredTech(null)} - > - - {name} - - {percentage < 0.1 ? "<0.1" : percentage.toFixed(1)}% - -
  • - ))} -
-
- ); -} diff --git a/frontend/src/constants/messages.ts b/frontend/src/constants/messages.ts index 08df8298..15e41317 100644 --- a/frontend/src/constants/messages.ts +++ b/frontend/src/constants/messages.ts @@ -63,7 +63,6 @@ export const FALLBACK_MESSAGES = { AUTH_CHECK: "ログイン状態の確認に失敗しました。", GITHUB_OAUTH_START: "GitHub OAuth の開始に失敗しました", GITHUB_LINK: "連携に失敗しました", - PROOFREAD: "文章の校正に失敗しました。", } as const; /** @@ -119,6 +118,31 @@ export const EXTERNAL_LINKS = { ISSUE_REPORT: "https://github.com/yusuke0610/devforge/issues", } as const; +/** GitHub 連携ダッシュボード / コントリビューションヒートマップの UI 文言。 */ +export const GITHUB_LINK_MESSAGES = { + /** アクティビティ(ヒートマップ)セクションの見出し。 */ + ACTIVITY_HEADING: "Activity", + /** 表示年セレクトの aria-label。 */ + YEAR_SELECT_ARIA: "表示する年", + /** ヒートマップサマリーの最大連続日数ラベル。 */ + LONGEST_STREAK_LABEL: "最大連続日数", +} as const; + +/** 年セレクトの選択肢表記「N年」。 */ +export function yearLabel(year: number): string { + return `${year}年`; +} + +/** ヒートマップサマリーの「N年のコントリビュート」ラベル。 */ +export function contributionSummaryLabel(year: number): string { + return `${year}年のコントリビュート`; +} + +/** ヒートマップの aria 文言「N年のコントリビューション (合計 M)」。 */ +export function contributionAriaLabel(year: number, total: number): string { + return `${year}年のコントリビューション (合計 ${total})`; +} + /** ファイル取り込み補助(PDF / Markdown 原本上の選択 → 流し込み)UI の文言 */ export const IMPORT_ASSIST_MESSAGES = { TITLE: "ファイルから下書きを取り込む", @@ -240,23 +264,6 @@ export const DIFF_DIALOG_MESSAGES = { BASELINE_EMPTY: "保存済みデータがありません。", /** 変更点サイドバーの見出し */ CHANGES_HEADING: "変更点", - /** 変更点+校正を統合したレビュー一覧の見出し */ - REVIEW_HEADING: "変更点・校正", -} as const; - -/** - * 保存確認ダイアログ内の文章校正(誤字脱字・表記ゆれ)セクションの文言。 - * 指摘メッセージ本文は textlint 由来の外部正本なのでここには持たず、そのまま表示する。 - */ -export const PROOFREAD_MESSAGES = { - /** サイドバーの校正セクション見出し。 */ - HEADING: "校正の指摘", - /** 校正処理中の表示。 */ - LOADING: "文章を校正中...", - /** 指摘ゼロ件のときの表示。 */ - NONE: "校正の指摘はありません。", - /** セクションの補足(保存はブロックしない旨)。 */ - HINT: "保存はブロックされません。気になる箇所のみ修正してください。", } as const; /** 左右 diff で「変更なし領域」を畳んだときの展開ラベル(件数を埋め込む)。 */ diff --git a/frontend/src/hooks/career/useProofread.test.ts b/frontend/src/hooks/career/useProofread.test.ts deleted file mode 100644 index 1688928b..00000000 --- a/frontend/src/hooks/career/useProofread.test.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { act, renderHook } from "@testing-library/react"; -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; - -import type { CareerFormState } from "../../payloadBuilders"; -import type { ProofreadIssue } from "../../proofread/types"; -import { useProofread } from "./useProofread"; - -vi.mock("../../proofread/proofreadClient", () => ({ - proofreadCareerForm: vi.fn(), -})); - -import { proofreadCareerForm } from "../../proofread/proofreadClient"; - -const mockProofread = proofreadCareerForm as unknown as ReturnType; - -const form: CareerFormState = { - full_name: "山田 太郎", - career_summary: "サマリー", - self_pr: "自己PR", - experiences: [], - qualifications: [], -}; - -const issue: ProofreadIssue = { - fieldId: "self_pr", - fieldLabel: "自己PR", - ruleId: "prh", - message: "javascript => JavaScript", - severity: "warning", - line: 1, - column: 1, - index: 0, - excerpt: "javascript", -}; - -beforeEach(() => { - mockProofread.mockReset(); - mockProofread.mockResolvedValue([issue]); - vi.useFakeTimers(); -}); - -afterEach(() => { - vi.useRealTimers(); -}); - -describe("useProofread", () => { - it("成功: enabled の間フォームを校正し指摘を返す", async () => { - const { result } = renderHook(() => useProofread(form, true)); - await act(async () => { - await vi.advanceTimersByTimeAsync(350); - }); - expect(mockProofread).toHaveBeenCalledTimes(1); - expect(result.current.issues).toEqual([issue]); - expect(result.current.proofreading).toBe(false); - expect(result.current.error).toBeNull(); - }); - - it("失敗: reject したらエラーを立て指摘を空にする", async () => { - mockProofread.mockRejectedValue(new Error("校正失敗")); - const { result } = renderHook(() => useProofread(form, true)); - await act(async () => { - await vi.advanceTimersByTimeAsync(350); - }); - expect(result.current.error).toBe("校正失敗"); - expect(result.current.issues).toEqual([]); - expect(result.current.proofreading).toBe(false); - }); - - it("無効化中(enabled=false)は校正せず、結果は空のまま", async () => { - const { result } = renderHook(() => useProofread(form, false)); - await act(async () => { - await vi.advanceTimersByTimeAsync(350); - }); - expect(mockProofread).not.toHaveBeenCalled(); - expect(result.current.issues).toEqual([]); - expect(result.current.error).toBeNull(); - }); - - it("enabled が false に変わると前回の指摘をクリアする", async () => { - const { result, rerender } = renderHook( - ({ enabled }: { enabled: boolean }) => useProofread(form, enabled), - { initialProps: { enabled: true } }, - ); - await act(async () => { - await vi.advanceTimersByTimeAsync(350); - }); - expect(result.current.issues).toEqual([issue]); - - rerender({ enabled: false }); - await act(async () => { - await vi.advanceTimersByTimeAsync(10); - }); - expect(result.current.issues).toEqual([]); - }); -}); diff --git a/frontend/src/hooks/career/useProofread.ts b/frontend/src/hooks/career/useProofread.ts deleted file mode 100644 index 02095ee7..00000000 --- a/frontend/src/hooks/career/useProofread.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { useEffect, useState } from "react"; - -import { FALLBACK_MESSAGES } from "../../constants/messages"; -import type { CareerFormState } from "../../payloadBuilders"; -import { proofreadCareerForm } from "../../proofread/proofreadClient"; -import type { ProofreadIssue } from "../../proofread/types"; - -/** 校正の取得状態。 */ -export type ProofreadState = { - /** 校正指摘の一覧(フィールド横断・収集順)。 */ - issues: ProofreadIssue[]; - /** 校正処理中フラグ。 */ - proofreading: boolean; - /** 校正失敗時のメッセージ。 */ - error: string | null; -}; - -/** - * デバウンス。ダイアログ表示中に form が変わるのはロールバック操作のみだが、 - * 校正は worker 往復があるため軽くまとめる。 - */ -const DEBOUNCE_MS = 300; - -/** - * 保存確認ダイアログが開いている間(`enabled`)、編集中フォームを校正するフック。 - * - * - ロールバックで form が変わるたびに再校正する(デバウンス付き)。 - * - ダイアログを閉じたら結果をクリアし、次回開いた時に前回の指摘を残さない。 - * - `react-hooks/set-state-in-effect` を避けるため、状態更新はすべて setTimeout 内で行う。 - */ -export function useProofread(form: CareerFormState, enabled: boolean): ProofreadState { - const [issues, setIssues] = useState([]); - const [proofreading, setProofreading] = useState(false); - const [error, setError] = useState(null); - - useEffect(() => { - let active = true; - const handle = setTimeout( - () => { - if (!active) return; - if (!enabled) { - setIssues([]); - setError(null); - setProofreading(false); - return; - } - setProofreading(true); - setError(null); - proofreadCareerForm(form) - .then((result) => { - if (active) setIssues(result); - }) - .catch((err) => { - if (active) { - setIssues([]); - setError(err instanceof Error ? err.message : FALLBACK_MESSAGES.PROOFREAD); - } - }) - .finally(() => { - if (active) setProofreading(false); - }); - }, - enabled ? DEBOUNCE_MS : 0, - ); - return () => { - active = false; - clearTimeout(handle); - }; - }, [enabled, form]); - - return { issues, proofreading, error }; -} diff --git a/frontend/src/proofread/collectCareerTexts.test.ts b/frontend/src/proofread/collectCareerTexts.test.ts deleted file mode 100644 index d1d6200d..00000000 --- a/frontend/src/proofread/collectCareerTexts.test.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { describe, expect, it } from "vitest"; - -import { - blankCareerClient, - blankCareerExperience, - blankCareerProject, -} from "../constants"; -import type { CareerExperienceForm, CareerFormState } from "../payloadBuilders"; -import { collectCareerTexts } from "./collectCareerTexts"; - -/** 必要なフィールドだけ上書きした職歴を作る。 */ -function makeExperience(overrides: Partial): CareerExperienceForm { - return { ...blankCareerExperience, ...overrides }; -} - -function makeForm(overrides: Partial): CareerFormState { - return { - full_name: "", - career_summary: "", - self_pr: "", - experiences: [], - qualifications: [], - ...overrides, - }; -} - -describe("collectCareerTexts", () => { - it("トップレベルの職務要約・自己PRを収集する(氏名は対象外)", () => { - const items = collectCareerTexts( - makeForm({ full_name: "山田 太郎", career_summary: "要約です", self_pr: "PRです" }), - ); - const ids = items.map((i) => i.id); - expect(ids).toContain("career_summary"); - expect(ids).toContain("self_pr"); - expect(ids).not.toContain("full_name"); - }); - - it("空白のみの値は除外する", () => { - const items = collectCareerTexts(makeForm({ career_summary: " ", self_pr: "" })); - expect(items).toEqual([]); - }); - - it("非IT企業は description を収集し、clients は辿らない", () => { - const exp = makeExperience({ - company: "非IT社", - business_description: "事業", - start_date: "2020-01", - is_it_company: false, - description: "詳細テキスト", - clients: [{ ...blankCareerClient, has_client: true, name: "取引先A" }], - }); - const items = collectCareerTexts(makeForm({ experiences: [exp] })); - const ids = items.map((i) => i.id); - expect(ids).toContain("experiences.0.company"); - expect(ids).toContain("experiences.0.business_description"); - expect(ids).toContain("experiences.0.description"); - // 非IT企業なので取引先配下は収集しない - expect(ids.some((id) => id.includes("clients"))).toBe(false); - }); - - it("IT企業は取引先名・案件の自由記述を辿り、ラベルはパンくずになる", () => { - const exp = makeExperience({ - company: "IT社", - business_description: "事業", - start_date: "2020-01", - is_it_company: true, - clients: [ - { - ...blankCareerClient, - has_client: true, - name: "取引先A", - projects: [ - { - ...blankCareerProject, - name: "案件X", - role: "リーダー", - description: "案件の説明", - }, - ], - }, - ], - }); - const items = collectCareerTexts(makeForm({ experiences: [exp] })); - const byId = new Map(items.map((i) => [i.id, i])); - expect(byId.has("experiences.0.clients.0.name")).toBe(true); - expect(byId.has("experiences.0.clients.0.projects.0.name")).toBe(true); - expect(byId.has("experiences.0.clients.0.projects.0.role")).toBe(true); - expect(byId.has("experiences.0.clients.0.projects.0.description")).toBe(true); - // パンくず: 職歴1 > 取引先1 > プロジェクト1 > 案件詳細 - expect(byId.get("experiences.0.clients.0.projects.0.description")?.label).toContain("職歴1"); - expect(byId.get("experiences.0.clients.0.projects.0.description")?.label).toContain( - "プロジェクト1", - ); - }); - - it("休暇の取引先は休暇内容のみ収集する", () => { - const exp = makeExperience({ - company: "IT社", - business_description: "事業", - start_date: "2020-01", - is_it_company: true, - clients: [ - { - ...blankCareerClient, - is_vacation: true, - vacation_start_date: "2021-01", - vacation_description: "育児休暇を取得", - }, - ], - }); - const items = collectCareerTexts(makeForm({ experiences: [exp] })); - const ids = items.map((i) => i.id); - expect(ids).toContain("experiences.0.clients.0.vacation_description"); - expect(ids.some((id) => id.includes("projects"))).toBe(false); - }); - - it("資格は名称を収集する", () => { - const items = collectCareerTexts( - makeForm({ qualifications: [{ acquired_date: "2020-01", name: "基本情報技術者" }] }), - ); - expect(items.map((i) => i.id)).toContain("qualifications.0.name"); - }); -}); diff --git a/frontend/src/proofread/collectCareerTexts.ts b/frontend/src/proofread/collectCareerTexts.ts deleted file mode 100644 index 3c353cbe..00000000 --- a/frontend/src/proofread/collectCareerTexts.ts +++ /dev/null @@ -1,106 +0,0 @@ -/** - * 職務経歴フォーム(`CareerFormState`)から校正対象のテキスト項目を平坦化する純粋関数。 - * - * ## 方針 - * - 校正は「保存される自由記述」を対象にする。日付・数値・真偽・選択値(技術スタック等)は除外する。 - * - 収集条件は `payloadBuilders.ts` の包含判定(`experienceIncluded` / `clientIncluded` / - * `projectIncluded`)を再利用し、payload に載らない(=保存されない)行は校正しない。 - * - `id`(ドット区切りパス)と `label`(パンくず)は `utils/careerDiff.ts` の体系に揃える。 - * そうすることで、ダイアログのサイドバーで「変更点」と「校正の指摘」が同じラベルで並ぶ。 - * - * 空白のみの値は除外する(指摘の意味がないため)。 - */ -import { CAREER_DIFF_LABELS as L, DIFF_DIALOG_MESSAGES as D } from "../constants/messages"; -import { - clientIncluded, - experienceIncluded, - projectIncluded, - type CareerFormState, -} from "../payloadBuilders"; -import type { CareerTextItem } from "./types"; - -/** ラベルセグメントを区切り文字で連結する(careerDiff の joinLabel と同一)。 */ -function joinLabel(segments: string[]): string { - return segments.join(D.PATH_SEPARATOR); -} - -/** 値が非空(trim 後)なら 1 項目 push する。 */ -function pushText( - items: CareerTextItem[], - id: string, - labelSegments: string[], - value: string, -): void { - if (!value.trim()) return; - items.push({ id, label: joinLabel(labelSegments), value }); -} - -/** - * フォームを走査し、校正対象テキストを順番(フォームの並び)どおりに収集する。 - */ -export function collectCareerTexts(form: CareerFormState): CareerTextItem[] { - const items: CareerTextItem[] = []; - - // 並び順は PDF レイアウト(職務要約 → 職務経歴 → 資格 → 自己PR)に合わせ、 - // 左右ペイン・変更点リストと校正セクションの縦順を一致させる。 - pushText(items, "career_summary", [L.CAREER_SUMMARY], form.career_summary); - - form.experiences.forEach((exp, expIndex) => { - if (!experienceIncluded(exp)) return; - const expSeg = [`${L.EXPERIENCE}${expIndex + 1}`]; - const expPath = `experiences.${expIndex}`; - - pushText(items, `${expPath}.company`, [...expSeg, L.COMPANY], exp.company); - pushText( - items, - `${expPath}.business_description`, - [...expSeg, L.BUSINESS_DESCRIPTION], - exp.business_description, - ); - - if (!exp.is_it_company) { - pushText(items, `${expPath}.description`, [...expSeg, L.DESCRIPTION], exp.description); - return; - } - - exp.clients.forEach((client, clientIndex) => { - if (!clientIncluded(client)) return; - const clientSeg = [...expSeg, `${L.CLIENT}${clientIndex + 1}`]; - const clientPath = `${expPath}.clients.${clientIndex}`; - - if (client.is_vacation) { - pushText( - items, - `${clientPath}.vacation_description`, - [...clientSeg, L.VACATION_DESCRIPTION], - client.vacation_description, - ); - return; - } - - if (client.has_client) { - pushText(items, `${clientPath}.name`, [...clientSeg, L.CLIENT_NAME], client.name); - } - - client.projects.forEach((proj, projIndex) => { - if (!projectIncluded(proj)) return; - const projSeg = [...clientSeg, `${L.PROJECT}${projIndex + 1}`]; - const projPath = `${clientPath}.projects.${projIndex}`; - - pushText(items, `${projPath}.name`, [...projSeg, L.PROJECT_NAME], proj.name); - pushText(items, `${projPath}.role`, [...projSeg, L.ROLE], proj.role); - pushText(items, `${projPath}.description`, [...projSeg, L.PROJECT_DESCRIPTION], proj.description); - }); - }); - }); - - form.qualifications.forEach((qual, index) => { - const qualSeg = [`${L.QUALIFICATION}${index + 1}`]; - pushText(items, `qualifications.${index}.name`, [...qualSeg, L.QUALIFICATION_NAME], qual.name); - }); - - // 自己PR は PDF 上で最後に来るため末尾に収集する。 - pushText(items, "self_pr", [L.SELF_PR], form.self_pr); - - return items; -} diff --git a/frontend/src/proofread/issueFormat.test.ts b/frontend/src/proofread/issueFormat.test.ts deleted file mode 100644 index 4e8c472e..00000000 --- a/frontend/src/proofread/issueFormat.test.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { describe, expect, it } from "vitest"; - -import { buildExcerpt, groupIssuesByField, mapSeverity } from "./issueFormat"; -import type { ProofreadIssue } from "./types"; - -describe("mapSeverity", () => { - it("0=info / 1=warning / 2 以上=error に写像する", () => { - expect(mapSeverity(0)).toBe("info"); - expect(mapSeverity(1)).toBe("warning"); - expect(mapSeverity(2)).toBe("error"); - expect(mapSeverity(3)).toBe("error"); - }); -}); - -describe("buildExcerpt", () => { - it("指摘箇所の前後を切り出す(両端が途切れると省略記号を付ける)", () => { - // 40 文字。半径 12 なので index=20 だと前後とも途切れる。 - const text = "あいうえおかきくけこさしすせそたちつてとアイウエオカキクケコサシスセソタチツテト"; - const excerpt = buildExcerpt(text, 20, 1); - expect(excerpt).toContain("カ"); - expect(excerpt.startsWith("…")).toBe(true); - expect(excerpt.endsWith("…")).toBe(true); - }); - - it("先頭付近は前側の省略記号を付けない", () => { - const excerpt = buildExcerpt("短いテキスト", 0, 1); - expect(excerpt.startsWith("…")).toBe(false); - expect(excerpt).toContain("短い"); - }); - - it("改行・連続空白は 1 つの空白に潰す", () => { - const excerpt = buildExcerpt("行1\n\n 行2", 0, 1); - expect(excerpt).not.toContain("\n"); - expect(excerpt).toBe("行1 行2"); - }); - - it("空文字なら空を返す", () => { - expect(buildExcerpt("", 0, 1)).toBe(""); - }); -}); - -describe("groupIssuesByField", () => { - const makeIssue = (fieldId: string, fieldLabel: string, message: string): ProofreadIssue => ({ - fieldId, - fieldLabel, - ruleId: "prh", - message, - severity: "warning", - line: 1, - column: 1, - index: 0, - excerpt: "", - }); - - it("フィールド単位でグルーピングし、入力順を保つ", () => { - const issues = [ - makeIssue("self_pr", "自己PR", "A"), - makeIssue("career_summary", "職務要約", "B"), - makeIssue("self_pr", "自己PR", "C"), - ]; - const groups = groupIssuesByField(issues); - expect(groups.map((g) => g.fieldId)).toEqual(["self_pr", "career_summary"]); - expect(groups[0].issues.map((i) => i.message)).toEqual(["A", "C"]); - expect(groups[1].issues.map((i) => i.message)).toEqual(["B"]); - }); - - it("空配列なら空グループ", () => { - expect(groupIssuesByField([])).toEqual([]); - }); -}); diff --git a/frontend/src/proofread/issueFormat.ts b/frontend/src/proofread/issueFormat.ts deleted file mode 100644 index 4fbccf4b..00000000 --- a/frontend/src/proofread/issueFormat.ts +++ /dev/null @@ -1,59 +0,0 @@ -/** - * 校正結果(textlint メッセージ)を `ProofreadIssue` へ整形する純粋関数群。 - * - * worker 本体(`proofread.worker.ts`)から呼ばれるが、textlint/kuromoji に依存しないため - * 単体テスト可能。worker 経路をモックしても、ここはそのままテストできる。 - */ -import type { ProofreadIssue, ProofreadSeverity } from "./types"; - -/** 抜粋に含める指摘箇所前後の文字数。 */ -const EXCERPT_RADIUS = 12; - -/** textlint の severity 数値(0=info / 1=warning / 2=error)を文字列へ写像する。 */ -export function mapSeverity(severity: number): ProofreadSeverity { - if (severity >= 2) return "error"; - if (severity === 1) return "warning"; - return "info"; -} - -/** - * フィールド本文から指摘箇所の前後を抜き出す。 - * - index 周辺を `EXCERPT_RADIUS` 文字ずつ切り出し、端が途切れる場合は省略記号を付ける。 - * - 改行は空白に潰して 1 行で見せる。 - */ -export function buildExcerpt(text: string, index: number, length = 1): string { - if (!text) return ""; - const safeIndex = Math.max(0, Math.min(index, text.length)); - const start = Math.max(0, safeIndex - EXCERPT_RADIUS); - const end = Math.min(text.length, safeIndex + Math.max(1, length) + EXCERPT_RADIUS); - const slice = text.slice(start, end).replace(/\s+/g, " ").trim(); - const prefix = start > 0 ? "…" : ""; - const suffix = end < text.length ? "…" : ""; - return `${prefix}${slice}${suffix}`; -} - -/** フィールド単位にグルーピングした指摘。サイドバー表示で使う。 */ -export type ProofreadGroup = { - fieldId: string; - fieldLabel: string; - issues: ProofreadIssue[]; -}; - -/** - * 指摘をフィールド単位へグルーピングする。 - * 入力順(= 収集順 = フォームの並び)を保ったままグループ化し、各グループ内も入力順を保つ。 - */ -export function groupIssuesByField(issues: ProofreadIssue[]): ProofreadGroup[] { - const groups: ProofreadGroup[] = []; - const indexById = new Map(); - for (const issue of issues) { - let pos = indexById.get(issue.fieldId); - if (pos === undefined) { - pos = groups.length; - indexById.set(issue.fieldId, pos); - groups.push({ fieldId: issue.fieldId, fieldLabel: issue.fieldLabel, issues: [] }); - } - groups[pos].issues.push(issue); - } - return groups; -} diff --git a/frontend/src/proofread/prh-it-terms.yml b/frontend/src/proofread/prh-it-terms.yml deleted file mode 100644 index 5e33ce76..00000000 --- a/frontend/src/proofread/prh-it-terms.yml +++ /dev/null @@ -1,98 +0,0 @@ -# IT 用語の表記ゆれ辞書(prh)。 -# 職務経歴書で頻出する固有名詞・技術用語の正規化に絞る。 -# `expected` が正、`patterns` が誤記(自動マッチ)。誤検出を避けるため過剰なパターンは登録しない。 -version: 1 -rules: - # 言語・ランタイム - - expected: JavaScript - patterns: - - javascript - - Javascript - - java script - - expected: TypeScript - patterns: - - typescript - - Typescript - - type script - - expected: Node.js - patterns: - - node.js - - Nodejs - - NodeJS - - node js - - expected: Python - patterns: - - python - - expected: Ruby - patterns: - - ruby - - expected: PHP - patterns: - - Php - - expected: Golang - patterns: - - golang - # フレームワーク・ライブラリ - - expected: React - patterns: - - react - - expected: Vue.js - patterns: - - vue.js - - Vuejs - - expected: Next.js - patterns: - - next.js - - Nextjs - - expected: Django - patterns: - - django - - expected: FastAPI - patterns: - - fastapi - - Fastapi - # インフラ・ツール - - expected: Docker - patterns: - - docker - - expected: Kubernetes - patterns: - - kubernetes - - expected: GitHub - patterns: - - github - - Github - - git hub - - expected: GitLab - patterns: - - gitlab - - Gitlab - - expected: MySQL - patterns: - - mysql - - Mysql - - expected: PostgreSQL - patterns: - - postgresql - - Postgresql - - postgres - - expected: Redis - patterns: - - redis - # 一般的な表記ゆれ - - expected: ソフトウェア - patterns: - - ソフトウエア - - expected: ハードウェア - patterns: - - ハードウエア - - expected: インターフェース - patterns: - - インタフェース - - インターフェイス - - expected: データベース - patterns: - - データーベース - - expected: ディレクトリ - patterns: - - ディレクトリー diff --git a/frontend/src/proofread/proofread.worker.ts b/frontend/src/proofread/proofread.worker.ts deleted file mode 100644 index 166cb761..00000000 --- a/frontend/src/proofread/proofread.worker.ts +++ /dev/null @@ -1,190 +0,0 @@ -/// -/** - * 文章校正 Web Worker。textlint kernel + 技術文書プリセット + prh をこの中で組み立て、 - * メインスレッドから渡されたテキスト項目を校正して `ProofreadIssue[]` を返す。 - * - * ## ブラウザ統合のポイント(ハマりどころ) - * - kuromoji(形態素解析)の辞書はブラウザに fs が無いため、`public/kuromoji-dict/` を - * 静的配信し、kuromojin が参照する `globalThis.kuromojin.dicPath` に URL を注入する。 - * kuromojin は `typeof window !== "undefined"` を前提とするため、worker では `window` も補う。 - * - kuromoji の辞書ロード(数 MB)に失敗しても機能停止させない。辞書を使うルール - * (二重助詞・冗長表現など)は preload 成功時のみ登録し、失敗時は prh + 形態素非依存 - * ルールだけで動作させる(グレースフルデグラデーション)。 - * - prh ルールはファイル読み込み不可なので、YAML を `?raw` 文字列で読み込み `ruleContents` で渡す。 - */ -// 最初に import すること(textlint/kuromoji 依存の評価前にグローバルを用意する)。 -import "./worker-env-polyfill"; - -import { TextlintKernel } from "@textlint/kernel"; -import type { TextlintKernelPlugin, TextlintKernelRule } from "@textlint/kernel"; - -import prhRule from "textlint-rule-prh"; - -import { buildExcerpt, mapSeverity } from "./issueFormat"; -import prhYaml from "./prh-it-terms.yml?raw"; -import type { - CareerTextItem, - ProofreadIssue, - ProofreadRequest, - ProofreadResponse, -} from "./types"; - -/** - * textlint ルール / プラグインは CJS↔ESM 相互運用で `default` が多重ラップされることがある。 - * 本体(関数 もしくは `{ linter }` / `{ create }` を持つオブジェクト)まで掘り下げる。 - */ -function unwrapModule(mod: unknown): T { - let current = mod as Record | unknown; - for (let i = 0; i < 5; i += 1) { - if (typeof current === "function") return current as T; - if ( - current && - typeof current === "object" && - ("linter" in current || "create" in current || "fixer" in current) - ) { - return current as T; - } - if (current && typeof current === "object" && "default" in current) { - current = (current as { default: unknown }).default; - continue; - } - break; - } - return current as T; -} - -const kernel = new TextlintKernel(); - -/** - * 技術文書プリセットのうち kuromoji(形態素解析)に依存するルール ID。 - * 辞書 preload に失敗した場合、これらを除外して機能停止を避ける。 - */ -const KUROMOJI_RULE_IDS = new Set([ - "max-ten", - "no-double-negative-ja", - "no-doubled-conjunction", - "no-doubled-conjunctive-particle-ga", - "no-doubled-joshi", - "no-dropping-the-ra", - "ja-no-weak-phrase", - "ja-no-successive-word", - "ja-no-abusage", - "ja-no-redundant-expression", - // 念のため漢数字ルールも辞書依存側に寄せる(誤検出より安全側)。 - "arabic-kanji-numbers", -]); - -/** 技術文書プリセットの import 形({ rules, rulesConfig })。 */ -type PresetModule = { - rules: Record; - rulesConfig: Record; -}; - -/** - * プリセットから kernel 用のルール記述子を組み立てる。 - * `includeKuromoji=false` のときは形態素解析依存ルールを除外する。 - * prh(表記ゆれ)はプリセット外なので常に末尾へ足す。 - */ -function buildRules(preset: PresetModule, includeKuromoji: boolean): TextlintKernelRule[] { - const rules: TextlintKernelRule[] = []; - for (const [ruleId, rule] of Object.entries(preset.rules)) { - if (!includeKuromoji && KUROMOJI_RULE_IDS.has(ruleId)) continue; - const options = preset.rulesConfig[ruleId]; - rules.push({ - ruleId, - rule: unwrapModule(rule), - // rulesConfig の値が false/true(無効/デフォルト)の場合は options を渡さない。 - ...(options && typeof options === "object" ? { options } : {}), - }); - } - rules.push({ ruleId: "prh", rule: unwrapModule(prhRule), options: { ruleContents: [prhYaml] } }); - return rules; -} - -/** kernel.lintText に渡す構築済み設定。 */ -type ProofreadConfig = { - plugins: TextlintKernelPlugin[]; - rules: TextlintKernelRule[]; -}; - -/** テキストプラグイン(.txt 解析)と全ルールを 1 回だけ構築してキャッシュする。 */ -let setupPromise: Promise | null = null; - -async function setup(): Promise { - const [textPlugin, presetModule] = await Promise.all([ - import("@textlint/textlint-plugin-text"), - import("textlint-rule-preset-ja-technical-writing"), - ]); - const preset = (("default" in presetModule ? presetModule.default : presetModule) ?? - presetModule) as PresetModule; - const plugins: TextlintKernelPlugin[] = [ - { pluginId: "text", plugin: unwrapModule(textPlugin) }, - ]; - - // 辞書を preload。成功時のみ kuromoji 依存ルールを含める。 - let includeKuromoji = false; - try { - const kuromojin = await import("kuromojin"); - await kuromojin.getTokenizer(); - includeKuromoji = true; - } catch { - // 辞書ロード失敗時は形態素非依存ルール + prh のみで継続(機能停止させない)。 - includeKuromoji = false; - } - - return { plugins, rules: buildRules(preset, includeKuromoji) }; -} - -function ensureSetup() { - if (!setupPromise) { - // setup 失敗(依存ロード不能)も握りつぶさず、各リクエストで再試行できるよう null に戻す。 - setupPromise = setup().catch((err) => { - setupPromise = null; - throw err; - }); - } - return setupPromise; -} - -/** 1 テキスト項目を校正し、ProofreadIssue 配列へ整形する。 */ -async function proofreadItem( - item: CareerTextItem, - config: ProofreadConfig, -): Promise { - const result = await kernel.lintText(item.value, { - ext: ".txt", - plugins: config.plugins, - rules: config.rules, - }); - return result.messages.map((message) => ({ - fieldId: item.id, - fieldLabel: item.label, - ruleId: message.ruleId, - message: message.message, - severity: mapSeverity(message.severity), - line: message.line, - column: message.column, - index: message.index, - excerpt: buildExcerpt(item.value, message.index), - })); -} - -self.onmessage = async (event: MessageEvent) => { - const data = event.data; - if (!data || data.type !== "proofread") return; - const { requestId, items } = data; - try { - const config = await ensureSetup(); - const nested = await Promise.all(items.map((item) => proofreadItem(item, config))); - const issues = nested.flat(); - const response: ProofreadResponse = { type: "result", requestId, issues }; - self.postMessage(response); - } catch (err) { - const response: ProofreadResponse = { - type: "error", - requestId, - message: err instanceof Error ? err.message : String(err), - }; - self.postMessage(response); - } -}; diff --git a/frontend/src/proofread/proofreadClient.ts b/frontend/src/proofread/proofreadClient.ts deleted file mode 100644 index e74cd253..00000000 --- a/frontend/src/proofread/proofreadClient.ts +++ /dev/null @@ -1,58 +0,0 @@ -/** - * 校正 worker のメインスレッド側サービス。 - * - * - worker は初回呼び出し時にのみ遅延生成する(textlint / kuromoji 一式は worker チャンクへ - * 分離され、初期バンドル・初回描画には載らない)。 - * - 各リクエストに連番 `requestId` を振り、応答を id で突合してレースを防ぐ。 - * - 収集テキストが 0 件なら worker を起動せず空配列を返す。 - */ -import type { CareerFormState } from "../payloadBuilders"; -import { collectCareerTexts } from "./collectCareerTexts"; -import type { ProofreadIssue, ProofreadRequest, ProofreadResponse } from "./types"; - -let worker: Worker | null = null; -let nextRequestId = 1; - -/** worker を遅延生成する(テストでは本モジュールごとモックするため、ここは実行されない)。 */ -function getWorker(): Worker { - if (!worker) { - worker = new Worker(new URL("./proofread.worker.ts", import.meta.url), { type: "module" }); - } - return worker; -} - -/** - * 編集中フォームの全テキスト項目を校正する。 - * 収集 0 件なら即 `[]`。それ以外は worker へ投げ、当該 requestId の応答を待つ。 - */ -export function proofreadCareerForm(form: CareerFormState): Promise { - const items = collectCareerTexts(form); - if (items.length === 0) return Promise.resolve([]); - - const activeWorker = getWorker(); - const requestId = nextRequestId; - nextRequestId += 1; - - return new Promise((resolve, reject) => { - const cleanup = () => { - activeWorker.removeEventListener("message", onMessage); - activeWorker.removeEventListener("error", onError); - }; - const onMessage = (event: MessageEvent) => { - const data = event.data; - if (!data || data.requestId !== requestId) return; - cleanup(); - if (data.type === "result") resolve(data.issues); - else reject(new Error(data.message)); - }; - const onError = (event: ErrorEvent) => { - cleanup(); - reject(event.error instanceof Error ? event.error : new Error(event.message)); - }; - activeWorker.addEventListener("message", onMessage); - activeWorker.addEventListener("error", onError); - - const request: ProofreadRequest = { type: "proofread", requestId, items }; - activeWorker.postMessage(request); - }); -} diff --git a/frontend/src/proofread/textlint-modules.d.ts b/frontend/src/proofread/textlint-modules.d.ts deleted file mode 100644 index 900eab8c..00000000 --- a/frontend/src/proofread/textlint-modules.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -/** - * textlint ルール群の型宣言。これらの npm パッケージは型定義を同梱しておらず、 - * 校正 worker はルール本体を `unwrapModule` 経由で扱うため、ここで any として宣言する。 - * (ルールの内部構造に型安全は不要 — kernel に渡すだけ)。 - */ -// textlint-rule-* 系(プリセット同梱の個別ルール)をまとめてワイルドカード宣言する。 -declare module "textlint-rule-*"; diff --git a/frontend/src/proofread/types.ts b/frontend/src/proofread/types.ts deleted file mode 100644 index 8baa0c37..00000000 --- a/frontend/src/proofread/types.ts +++ /dev/null @@ -1,52 +0,0 @@ -/** - * 文章校正(誤字脱字・表記ゆれ)機能の型 Single Source of Truth。 - * - * メインスレッド(hook / コンポーネント)は textlint や kuromoji を一切知らず、 - * この `ProofreadIssue` 配列だけを介して worker とやり取りする。エンジンを差し替えても - * この型が変わらなければ UI 側は影響を受けない(疎結合の境界)。 - */ - -/** 指摘の深刻度。textlint の severity 数値(0=info / 1=warning / 2=error)を文字列へ写像したもの。 */ -export type ProofreadSeverity = "info" | "warning" | "error"; - -/** 校正対象となる 1 テキスト項目(フォームから平坦化したもの)。 */ -export type CareerTextItem = { - /** フォーム上のドット区切りパス(例: "experiences.0.description")。careerDiff の path と揃える。 */ - id: string; - /** 人間可読ラベル(パンくず。例: "職歴1 > 詳細")。 */ - label: string; - /** 校正にかける本文。 */ - value: string; -}; - -/** 1 件の校正指摘。フィールド単位にグルーピングして表示する。 */ -export type ProofreadIssue = { - /** 指摘が属するフィールドの id(`CareerTextItem.id`)。 */ - fieldId: string; - /** 指摘が属するフィールドのラベル(`CareerTextItem.label`)。 */ - fieldLabel: string; - /** textlint のルール ID(例: "prh" / "ja-no-mixed-period")。 */ - ruleId: string; - /** 指摘メッセージ(textlint 由来の外部正本。そのまま表示してよい)。 */ - message: string; - severity: ProofreadSeverity; - /** フィールド本文内の行・列(1 始まり)。 */ - line: number; - column: number; - /** フィールド本文内の文字オフセット(0 始まり)。 */ - index: number; - /** 指摘箇所の前後を抜き出した短い抜粋(UI のコンテキスト表示用)。 */ - excerpt: string; -}; - -/** メインスレッド → worker への校正リクエスト。 */ -export type ProofreadRequest = { - type: "proofread"; - requestId: number; - items: CareerTextItem[]; -}; - -/** worker → メインスレッドへの応答。 */ -export type ProofreadResponse = - | { type: "result"; requestId: number; issues: ProofreadIssue[] } - | { type: "error"; requestId: number; message: string }; diff --git a/frontend/src/proofread/worker-env-polyfill.ts b/frontend/src/proofread/worker-env-polyfill.ts deleted file mode 100644 index 29b27908..00000000 --- a/frontend/src/proofread/worker-env-polyfill.ts +++ /dev/null @@ -1,46 +0,0 @@ -/** - * 校正 worker 用の最小グローバル補完。worker の **最初** に import して副作用を効かせる - * (textlint / kuromoji 系の依存が評価・実行時に参照する node/ブラウザのグローバルを先に用意する)。 - * - * - `process`: async / textlint 依存が `process.env` / `process.nextTick` を参照する。worker には無い。 - * - `window`: kuromojin は辞書パス解決で `typeof window !== "undefined"` を前提とする。worker には無い。 - * - `window.kuromojin.dicPath`: 静的配信した辞書(public/kuromoji-dict/)の URL を注入する。 - */ -type MinimalProcess = { - env: Record; - nextTick: (cb: (...args: unknown[]) => void, ...args: unknown[]) => void; - cwd: () => string; - platform: string; - version: string; - argv: string[]; -}; - -const g = globalThis as unknown as { - process?: Partial; - window?: unknown; - kuromojin?: { dicPath: string }; -}; - -if (typeof g.process === "undefined") { - g.process = {}; -} -// 一部の textlint 依存(prh 経由の ja-no-abusage 等)が参照する API を最小実装で補う。 -const proc = g.process as Partial; -proc.env ??= {}; -proc.nextTick ??= (cb, ...args) => { - void Promise.resolve().then(() => cb(...args)); -}; -proc.cwd ??= () => "/"; -proc.platform ??= "browser"; -proc.version ??= ""; -proc.argv ??= []; - -if (typeof g.window === "undefined") { - g.window = globalThis; -} - -// 辞書の静的配信パスを kuromojin に渡す。 -// 注意: ルート相対パスにする(`http://…` のような絶対 URL を渡すと、kuromoji 内部の -// path.join(ブラウザでは path-browserify)が `://` を `:/` に潰して壊れる)。 -// XHR はこのルート相対パスを worker の origin 基準で解決する。 -g.kuromojin = { dicPath: "/kuromoji-dict" }; diff --git a/frontend/src/test/handlers.ts b/frontend/src/test/handlers.ts index 1f9c545e..c35ecbca 100644 --- a/frontend/src/test/handlers.ts +++ b/frontend/src/test/handlers.ts @@ -30,18 +30,28 @@ const analysisCacheResult = http.get("*/api/github-link/cache", () => unique_skills: 5, analyzed_at: "2026-01-01T00:00:00Z", languages: { TypeScript: 60, Python: 40 }, - detected_frameworks: { React: 3, FastAPI: 2 }, - detected_devtools: { Docker: 4, "GitHub Actions": 3 }, - detected_infras: { Terraform: 1 }, - contribution_calendar: { - total_contributions: 123, - weeks: [ - [ - { date: "2025-01-06", count: 2, level: 1 }, - { date: "2025-01-07", count: 8, level: 4 }, + contribution_calendars: [ + { + year: 2025, + total_contributions: 123, + weeks: [ + [ + { date: "2025-01-06", count: 2, level: 1 }, + { date: "2025-01-07", count: 8, level: 4 }, + ], ], - ], - }, + }, + { + year: 2024, + total_contributions: 88, + weeks: [ + [ + { date: "2024-01-01", count: 1, level: 1 }, + { date: "2024-01-02", count: 5, level: 3 }, + ], + ], + }, + ], }, }), ); diff --git a/frontend/src/utils/careerReview.test.ts b/frontend/src/utils/careerReview.test.ts index 6418a1b6..645a73e1 100644 --- a/frontend/src/utils/careerReview.test.ts +++ b/frontend/src/utils/careerReview.test.ts @@ -2,24 +2,10 @@ import { describe, expect, it } from "vitest"; import { buildReviewEntries, comparePaths } from "./careerReview"; import type { CareerChange } from "./careerDiff"; -import type { ProofreadIssue } from "../proofread/types"; function change(path: (string | number)[], label: string): CareerChange { return { path, label, kind: "modified", oldValue: "", newValue: "", rollback: (f) => f }; } -function issue(fieldId: string, fieldLabel: string, message: string): ProofreadIssue { - return { - fieldId, - fieldLabel, - ruleId: "prh", - message, - severity: "warning", - line: 1, - column: 1, - index: 0, - excerpt: "", - }; -} describe("comparePaths", () => { it("トップレベルは PDF 順(自己PR が資格より後)", () => { @@ -41,38 +27,33 @@ describe("comparePaths", () => { }); describe("buildReviewEntries", () => { - it("同一パスの差分と校正を 1 エントリに統合する", () => { - const entries = buildReviewEntries( - [change(["career_summary"], "職務要約")], - [issue("career_summary", "職務要約", "javascript => JavaScript")], - ); + it("同一パスの差分を 1 エントリにまとめる", () => { + const entries = buildReviewEntries([ + change(["career_summary"], "職務要約"), + change(["career_summary"], "職務要約"), + ]); expect(entries).toHaveLength(1); - expect(entries[0].changes).toHaveLength(1); - expect(entries[0].issues).toHaveLength(1); + expect(entries[0].changes).toHaveLength(2); }); it("PDF レイアウト順に並ぶ(自己PRが末尾、職歴は資格より前)", () => { - const entries = buildReviewEntries( - [change(["self_pr"], "自己PR"), change(["full_name"], "氏名")], - [ - issue("self_pr", "自己PR", "x"), - issue("qualifications.0.name", "資格1 > 資格名", "y"), - issue("experiences.0.company", "職歴1 > 会社名", "z"), - ], - ); + const entries = buildReviewEntries([ + change(["self_pr"], "自己PR"), + change(["full_name"], "氏名"), + change(["qualifications", 0, "name"], "資格1 > 資格名"), + change(["experiences", 0, "company"], "職歴1 > 会社名"), + ]); const order = entries.map((e) => e.path); expect(order[0]).toBe("full_name"); expect(order[order.length - 1]).toBe("self_pr"); expect(order.indexOf("experiences.0.company")).toBeLessThan(order.indexOf("qualifications.0.name")); }); - it("差分のみ・校正のみの項目もそれぞれエントリになる", () => { - const entries = buildReviewEntries( - [change(["full_name"], "氏名")], - [issue("experiences.0.description", "職歴1 > 詳細", "冗長")], - ); + it("複数フィールドの差分がそれぞれエントリになる", () => { + const entries = buildReviewEntries([ + change(["full_name"], "氏名"), + change(["experiences", 0, "description"], "職歴1 > 詳細"), + ]); expect(entries.map((e) => e.path)).toEqual(["full_name", "experiences.0.description"]); - expect(entries[0].issues).toHaveLength(0); - expect(entries[1].changes).toHaveLength(0); }); }); diff --git a/frontend/src/utils/careerReview.ts b/frontend/src/utils/careerReview.ts index c65e389a..c3fe8b9d 100644 --- a/frontend/src/utils/careerReview.ts +++ b/frontend/src/utils/careerReview.ts @@ -1,15 +1,13 @@ /** - * 保存確認ダイアログ右サイドバーの「レビュー項目」を組み立てる純関数。 + * 保存確認ダイアログ右サイドバーの「変更点リスト」を組み立てる純関数。 * - * 変更点(差分)と校正指摘を**フィールド単位で1本のリストに統合**し、PDF レイアウトと - * 同じ縦順(氏名 → 職務要約 → 職務経歴 → 資格 → 自己PR、各コンテナ内も PDF 準拠)に並べる。 + * 変更点(差分)を**フィールド単位でまとめ**、PDF レイアウトと同じ縦順 + * (氏名 → 職務要約 → 職務経歴 → 資格 → 自己PR、各コンテナ内も PDF 準拠)に並べる。 * これにより左右ペイン(PDF)とサイドバーの並びが一致し、上から順に突合できる。 */ -import { groupIssuesByField } from "../proofread/issueFormat"; -import type { ProofreadIssue } from "../proofread/types"; import type { CareerChange } from "./careerDiff"; -/** 1 フィールド分のレビュー項目(差分と校正をまとめて持つ)。 */ +/** 1 フィールド分のレビュー項目(フィールドの差分をまとめて持つ)。 */ export type ReviewEntry = { /** フィールドのドット区切りパス(スクロール・key 用)。 */ path: string; @@ -17,8 +15,6 @@ export type ReviewEntry = { label: string; /** このフィールドの差分(通常 0〜1 件)。 */ changes: CareerChange[]; - /** このフィールドの校正指摘。 */ - issues: ProofreadIssue[]; }; /** トップレベル項目の並び(PDF レイアウト順)。 */ @@ -93,19 +89,16 @@ export function comparePaths(a: string, b: string): number { } /** - * 差分と校正指摘をフィールド単位に統合し、PDF レイアウト順に並べたレビュー項目を返す。 - * 同一パスの差分・校正は 1 エントリにまとまる。差分のみ・校正のみの項目もそれぞれ 1 エントリ。 + * 差分をフィールド単位にまとめ、PDF レイアウト順に並べたレビュー項目を返す。 + * 同一パスの差分は 1 エントリにまとまる。 */ -export function buildReviewEntries( - changes: CareerChange[], - issues: ProofreadIssue[], -): ReviewEntry[] { +export function buildReviewEntries(changes: CareerChange[]): ReviewEntry[] { const byPath = new Map(); const getOrCreate = (path: string, label: string): ReviewEntry => { let entry = byPath.get(path); if (!entry) { - entry = { path, label, changes: [], issues: [] }; + entry = { path, label, changes: [] }; byPath.set(path, entry); } return entry; @@ -114,9 +107,6 @@ export function buildReviewEntries( for (const change of changes) { getOrCreate(change.path.join("."), change.label).changes.push(change); } - for (const group of groupIssuesByField(issues)) { - getOrCreate(group.fieldId, group.fieldLabel).issues.push(...group.issues); - } return [...byPath.values()].sort((a, b) => comparePaths(a.path, b.path)); } diff --git a/frontend/src/utils/diffHighlight.test.ts b/frontend/src/utils/diffHighlight.test.ts index 27926121..b0051a00 100644 --- a/frontend/src/utils/diffHighlight.test.ts +++ b/frontend/src/utils/diffHighlight.test.ts @@ -144,45 +144,6 @@ describe("foldUnchanged", () => { const html = '
a
'; expect(foldUnchanged(html, buildPathKindMap([]))).toBe(html); }); - - it("校正指摘のある項目は(差分が無くても)畳まずに残す", () => { - const html = - '
A
' + - '
B
'; - // 差分は experiences.0 のみ。experiences.1 は校正指摘があるので畳まれない。 - const map = buildPathKindMap([change(["experiences", 0, "company"], "modified")]); - const proofread = new Set(["experiences.1.company"]); - const out = foldUnchanged(html, map, proofread); - const doc = new DOMParser().parseFromString(out, "text/html"); - // 校正指摘のある experiences.1 は details の外に残る - expect(doc.querySelector('details [data-unit="experiences.1"]')).toBeNull(); - expect(doc.querySelector('[data-unit="experiences.1"]')).not.toBeNull(); - }); -}); - -describe("annotateHtml(校正マーク)", () => { - it("校正指摘のある data-fp に diff-proofread を付ける", () => { - const html = - '
要約
PR
'; - const out = annotateHtml(html, buildPathKindMap([]), new Set(["self_pr"])); - const doc = new DOMParser().parseFromString(out, "text/html"); - expect(doc.querySelector('[data-fp="self_pr"]')?.className).toContain("diff-proofread"); - // 指摘の無い career_summary には付かない - expect(doc.querySelector('[data-fp="career_summary"]')?.className).toBe(""); - }); - - it("差分(黄)と校正(青)は同じノードに併記される", () => { - const html = '
PR
'; - const out = annotateHtml( - html, - buildPathKindMap([change(["self_pr"], "modified")]), - new Set(["self_pr"]), - ); - const cls = new DOMParser().parseFromString(out, "text/html").querySelector('[data-fp="self_pr"]') - ?.className; - expect(cls).toContain("diff-modified"); - expect(cls).toContain("diff-proofread"); - }); }); describe("injectRemovedPlaceholders", () => { diff --git a/frontend/src/utils/diffHighlight.ts b/frontend/src/utils/diffHighlight.ts index 9a8e07e9..146a16d8 100644 --- a/frontend/src/utils/diffHighlight.ts +++ b/frontend/src/utils/diffHighlight.ts @@ -54,16 +54,9 @@ function matchKind(nodePath: string, map: Map): ChangeKind | * * 変更が無ければ sanitize のみ行う。`dangerouslySetInnerHTML` には渡さず、 * iframe の `srcDoc` に埋め込む前提(呼び出し側で隔離)。 - * - * `proofreadFieldIds` を渡すと、その data-fp に一致するノードへ校正マーク(青波線)の - * クラス `diff-proofread` を付ける。編集中ペインで「校正指摘のあるフィールド」を示す用途。 */ -export function annotateHtml( - html: string, - map: Map, - proofreadFieldIds: Set = new Set(), -): string { - if (map.size === 0 && proofreadFieldIds.size === 0) return DOMPurify.sanitize(html); +export function annotateHtml(html: string, map: Map): string { + if (map.size === 0) return DOMPurify.sanitize(html); const doc = new DOMParser().parseFromString(html, "text/html"); doc.body.querySelectorAll("[data-fp]").forEach((node) => { @@ -73,10 +66,6 @@ export function annotateHtml( if (kind) { node.classList.add("diff-mark", KIND_CLASS[kind]); } - // 校正指摘のあるフィールドは青波線(差分の背景色と重ねても潰れない)。 - if (proofreadFieldIds.has(fp)) { - node.classList.add("diff-proofread"); - } }); // DOMPurify は既定で data-* 属性・class を保持する(ALLOW_DATA_ATTR=true)。 return DOMPurify.sanitize(doc.body.innerHTML); @@ -87,14 +76,6 @@ function unitChanged(unitPath: string, map: Map): boolean { return matchKind(unitPath, map) !== null; } -/** 項目(data-unit)配下に校正指摘のあるフィールドが含まれるか。 */ -function unitHasProofread(unitPath: string, proofreadFieldIds: Set): boolean { - for (const fp of proofreadFieldIds) { - if (fp === unitPath || fp.startsWith(`${unitPath}.`)) return true; - } - return false; -} - /** * prefix 直下で「先頭(最小 index)の既存兄弟(data-unit)」を探す。 * @@ -197,29 +178,22 @@ export function injectRemovedPlaceholders(html: string, changes: CareerChange[]) * - `
` はネイティブの開閉なので、スクリプト無効の sandbox iframe 内でも展開できる。 * * 変更が無ければ(map 空)何もしない。annotateHtml の後段に適用する前提。 - * - * `proofreadFieldIds` を渡すと、校正指摘を含む項目は(差分が無くても)畳まずに残す - * (編集中ペインで青マークが折りたたみに隠れないようにする)。 */ -export function foldUnchanged( - html: string, - map: Map, - proofreadFieldIds: Set = new Set(), -): string { +export function foldUnchanged(html: string, map: Map): string { if (map.size === 0) return html; const doc = new DOMParser().parseFromString(html, "text/html"); const units = Array.from(doc.body.querySelectorAll("[data-unit]")); - // 畳み根: 自身が未変更 かつ 校正指摘も無い かつ 最も近い祖先 unit が「変更あり or 不在」のもの。 + // 畳み根: 自身が未変更 かつ 最も近い祖先 unit が「変更あり or 不在」のもの。 const collapseRoots = new Set(); for (const u of units) { const path = u.getAttribute("data-unit"); - if (!path || unitChanged(path, map) || unitHasProofread(path, proofreadFieldIds)) continue; + if (!path || unitChanged(path, map)) continue; const parentUnit = u.parentElement?.closest("[data-unit]"); if (parentUnit) { const parentPath = parentUnit.getAttribute("data-unit"); - if (parentPath && !unitChanged(parentPath, map) && !unitHasProofread(parentPath, proofreadFieldIds)) { + if (parentPath && !unitChanged(parentPath, map)) { continue; // 祖先ごと畳まれる } } diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index d0c6b54e..fb44ea1f 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -1,69 +1,10 @@ /// -import { createReadStream } from "node:fs"; -import { join, normalize } from "node:path"; -import { defineConfig, type Plugin } from "vite"; +import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; -/** - * kuromoji(校正 worker の形態素解析)辞書を dev サーバーで「生の gzip バイト列」として配信する。 - * - * 既定の静的配信は `.gz` 拡張子を見て `Content-Encoding: gzip` を付けるため、ブラウザが - * 自動展開してしまい、kuromoji 側の再 gunzip が「invalid file signature」で失敗する。 - * ここでは Content-Encoding を付けず application/octet-stream で素のまま返し、 - * kuromoji が自前で gunzip できるようにする(本番 Cloudflare Pages 用は public/_headers で同等指定)。 - */ -function kuromojiDictRaw(): Plugin { - const publicDir = join(process.cwd(), "public"); - return { - name: "kuromoji-dict-raw", - configureServer(server) { - server.middlewares.use((req, res, next) => { - const url = req.url?.split("?")[0] ?? ""; - if (!url.startsWith("/kuromoji-dict/") || !url.endsWith(".dat.gz")) { - next(); - return; - } - // 先頭スラッシュを除いて確実に publicDir 相対で解決する(join の絶対パス扱いを回避)。 - // パストラバーサル防止のため normalize 後に publicDir 配下であることを担保する。 - const relativePath = url.replace(/^\/+/, ""); - const filePath = normalize(join(publicDir, relativePath)); - if (!filePath.startsWith(publicDir)) { - res.statusCode = 403; - res.end(); - return; - } - res.setHeader("Content-Type", "application/octet-stream"); - res.setHeader("Cache-Control", "no-cache"); - createReadStream(filePath) - .on("error", () => next()) - .pipe(res); - }); - }, - }; -} - export default defineConfig({ - plugins: [react(), kuromojiDictRaw()], + plugins: [react()], base: "/", - resolve: { - alias: { - // 校正 worker が使う textlint / kuromoji 依存が参照する node 組み込みのうち、 - // 実行時に実際に呼ばれる path / os / assert はブラウザ実装へ差し替える。 - // fs は本機能で経路を踏まない(YAML は ?raw 文字列で渡す)ため、 - // Vite 既定の空モジュール externalize に任せる(別途エイリアスしない)。 - path: "path-browserify", - "node:path": "path-browserify", - os: "os-browserify/browser", - "node:os": "os-browserify/browser", - assert: "assert", - "node:assert": "assert", - }, - }, - // worker は動的 import でルールを分割ロードする(コード分割)。 - // iife/umd は code-splitting 非対応なので ES モジュール形式で出力する。 - worker: { - format: "es", - }, server: { host: "0.0.0.0", port: 5173,