diff --git a/.github/README.md b/.github/README.md new file mode 100644 index 0000000..77583e4 --- /dev/null +++ b/.github/README.md @@ -0,0 +1,65 @@ +# CI/CD と開発者向けドキュメント + +この `.github/` ディレクトリには、チーム情報とプロフィール表示を自動で同期するためのワークフローが置かれています。リポジトリ全体の概要は [リポジトリ root の README](../README.md) を参照してください。 + +## Workflow 一覧 + +| Workflow | Trigger | 役割 | +| --- | --- | --- | +| `.github/workflows/tf-plan.yaml` | `terraform/**` を含む PR | `terraform plan` を実行し差分を PR コメントに投稿 | +| `.github/workflows/tf-apply.yaml` | main への push(`terraform/**`) / 手動 | `terraform apply` → 続けて `sync-core-members` を起動 | +| `.github/workflows/sync-core-members.yml` | `tf-apply` からの起動 / 6時間ごとの cron / 手動 / `repository_dispatch` | 最新年度の `core-members-YYYY` team を取得し `data/core_members.yaml` と `profile/README.md` を再生成する PR を自動作成 | +| `.github/workflows/validate-profile.yml` | `data/core_members.yaml` や `profile/README.md` を含む PR | `render_members.py --check` で README の再生成漏れを検出 | + +## 認証 + +- **GitHub API (team / membership の書込)**: GitHub App (`Organization → Members: Read & Write`) の installation token を `actions/create-github-app-token@v2` で発行し Terraform provider に渡します。tfcmt の PR コメント投稿や `sync-core-members` の起動 (`gh workflow run`) には App token ではなく Actions 標準の `GITHUB_TOKEN` を使います(App には repo 権限がないため)。 +- **自動生成 PR (`sync-core-members`)**: 別の GitHub App から発行した token で署名付き commit を作成し PR を出します。`peter-evans/create-pull-request` の `sign-commits: true` を利用するため、App installation token 経由の GraphQL 署名になり、GitHub 上で "Verified" バッジが付きます。App 側の必要権限: + - Organization → Members: Read + - Repository → Contents: Read & Write + - Repository → Pull requests: Read & Write +- **Terraform state**: Google Cloud Storage bucket `ynufes-tech-gh-tf-state` に保存します。GitHub Actions から Workload Identity Federation で SA `gh-actions@ynufes-tech-gh.iam.gserviceaccount.com` に impersonate してアクセスします。 + +### 必要なリポジトリ secrets + +| 名前 | 用途 | +| --- | --- | +| `TF_GH_APP_ID`, `TF_GH_APP_PRIVATE_KEY` | `tf-plan` / `tf-apply` | +| `SYNC_GH_APP_ID`, `SYNC_GH_APP_PRIVATE_KEY` | `sync-core-members`(署名付き PR 作成) | + +## ローカル開発 + +ツールは [aqua](https://aquaproj.github.io/) で管理しています。 + +```bash +aqua i -l +``` + +Python の依存は [uv](https://docs.astral.sh/uv/) で管理しています。 + +```bash +uv sync +``` + +### プロフィール README の再生成チェック + +```bash +uv run scripts/render_members.py --check +``` + +### 現在の Core Members を取得して YAML を更新 (要 `gh` ログイン) + +```bash +uv run scripts/fetch_core_members.py --org ynufes-tech --out data/core_members.yaml +uv run scripts/render_members.py +``` + +### Terraform + +```bash +cd terraform +terraform init +terraform plan +``` + +state backend は GCS です。ローカルから叩く場合は `gcloud auth application-default login` の後、SA への impersonate を許可するか、同 SA の鍵で認証してください。 diff --git a/.github/workflows/sync-core-members.yml b/.github/workflows/sync-core-members.yml new file mode 100644 index 0000000..301eb53 --- /dev/null +++ b/.github/workflows/sync-core-members.yml @@ -0,0 +1,112 @@ +name: Sync Core Members + +on: + schedule: + - cron: "17 */6 * * *" + workflow_dispatch: + repository_dispatch: + types: + - core-members-changed + +permissions: + contents: write + pull-requests: write + +concurrency: + group: sync-core-members + cancel-in-progress: false + +jobs: + sync: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Generate GitHub App token + id: app_token + uses: actions/create-github-app-token@v2 + with: + app-id: ${{ secrets.SYNC_GH_APP_ID }} + private-key: ${{ secrets.SYNC_GH_APP_PRIVATE_KEY }} + owner: ynufes-tech + + - name: Setup uv + uses: astral-sh/setup-uv@v5 + + - name: Snapshot previous members + run: cp data/core_members.yaml /tmp/core_members_prev.yaml + + - name: Fetch current Core Members + env: + GH_TOKEN: ${{ steps.app_token.outputs.token }} + run: | + uv run scripts/fetch_core_members.py \ + --org ynufes-tech \ + --out data/core_members.yaml + + - name: Render profile README + run: uv run scripts/render_members.py + + - name: Build PR body + id: diff + run: | + uv run python - <<'PY' > /tmp/pr_body.md + import pathlib + import yaml + + prev = yaml.safe_load(pathlib.Path("/tmp/core_members_prev.yaml").read_text()) or {} + curr = yaml.safe_load(pathlib.Path("data/core_members.yaml").read_text()) or {} + + def names(section): + return set(section.get("members", []) or []) + + def diff_section(label, prev_section, curr_section): + prev_names = names(prev_section or {}) + curr_names = names(curr_section or {}) + added = sorted(curr_names - prev_names, key=str.lower) + removed = sorted(prev_names - curr_names, key=str.lower) + slug = (curr_section or {}).get("team_slug", "-") + out = [ + f"### {label} (team: `{slug}`)", + f"- 現在のメンバー数: {len(curr_names)}", + ] + if added: + out += ["", "**追加**", *[f"- @{n}" for n in added]] + if removed: + out += ["", "**削除**", *[f"- @{n}" for n in removed]] + if not added and not removed: + out += ["", "差分なし"] + return out + + lines = ["## Core Members / Members 同期", ""] + lines += diff_section( + "Core Members", prev.get("core_members"), curr.get("core_members") + ) + lines += ["", ""] + lines += diff_section( + "Members", prev.get("all_members"), curr.get("all_members") + ) + lines += [ + "", + "このPRは GitHub Actions が自動生成しています。", + "メンバー構成そのものの編集は `terraform/team_memberships.tf` で行ってください。", + ] + print("\n".join(lines)) + PY + + - name: Create pull request + uses: peter-evans/create-pull-request@v7 + with: + token: ${{ steps.app_token.outputs.token }} + branch: automation/core-members-sync + commit-message: "chore: sync Core Members membership" + title: "chore: Core Members のメンバー一覧を同期" + body-path: /tmp/pr_body.md + delete-branch: true + sign-commits: true + committer: "${{ steps.app_token.outputs.app-slug }}[bot] <${{ steps.app_token.outputs.app-slug }}[bot]@users.noreply.github.com>" + author: "${{ steps.app_token.outputs.app-slug }}[bot] <${{ steps.app_token.outputs.app-slug }}[bot]@users.noreply.github.com>" + labels: | + members + automation diff --git a/.github/workflows/tf-apply.yaml b/.github/workflows/tf-apply.yaml new file mode 100644 index 0000000..72e6305 --- /dev/null +++ b/.github/workflows/tf-apply.yaml @@ -0,0 +1,67 @@ +name: Terraform Apply + +on: + push: + branches: + - main + paths: + - terraform/** + - .github/workflows/tf-apply.yaml + workflow_dispatch: + +permissions: + id-token: write + contents: read + actions: write + pull-requests: write + +concurrency: + group: terraform-apply + cancel-in-progress: false + +jobs: + apply: + runs-on: ubuntu-latest + defaults: + run: + working-directory: terraform + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Generate GitHub App token (org admin) + id: app_token + uses: actions/create-github-app-token@v2 + with: + app-id: ${{ secrets.TF_GH_APP_ID }} + private-key: ${{ secrets.TF_GH_APP_PRIVATE_KEY }} + owner: ynufes-tech + + - name: Authenticate to Google Cloud + uses: google-github-actions/auth@v2 + with: + workload_identity_provider: projects/516174501149/locations/global/workloadIdentityPools/default/providers/github + service_account: gh-actions@ynufes-tech-gh.iam.gserviceaccount.com + + - name: Setup aqua + uses: aquaproj/aqua-installer@v3.1.0 + with: + aqua_version: v2.36.1 + + - name: Terraform init + run: terraform init -input=false + + - name: Terraform apply (tfcmt) + env: + TF_VAR_github_token: ${{ steps.app_token.outputs.token }} + # tfcmt のコメント投稿は repo 権限を持つ Actions 標準トークンで行う + # (App token は org Members 権限のみで PR コメントを投稿できない) + GITHUB_TOKEN: ${{ github.token }} + run: tfcmt apply -- terraform apply -input=false -auto-approve + + - name: Trigger profile sync + env: + # workflow_dispatch は GITHUB_TOKEN 起動でも新しい run が作られる + # (App token には Actions: write がないため使えない) + GH_TOKEN: ${{ github.token }} + run: gh workflow run sync-core-members.yml -R ${{ github.repository }} diff --git a/.github/workflows/tf-plan.yaml b/.github/workflows/tf-plan.yaml new file mode 100644 index 0000000..65528ec --- /dev/null +++ b/.github/workflows/tf-plan.yaml @@ -0,0 +1,58 @@ +name: Terraform Plan + +on: + pull_request: + paths: + - terraform/** + - .github/workflows/tf-plan.yaml + +permissions: + id-token: write + contents: read + pull-requests: write + +jobs: + plan: + runs-on: ubuntu-latest + defaults: + run: + working-directory: terraform + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Generate GitHub App token (org admin) + id: app_token + uses: actions/create-github-app-token@v2 + with: + app-id: ${{ secrets.TF_GH_APP_ID }} + private-key: ${{ secrets.TF_GH_APP_PRIVATE_KEY }} + owner: ynufes-tech + + - name: Authenticate to Google Cloud + uses: google-github-actions/auth@v2 + with: + workload_identity_provider: projects/516174501149/locations/global/workloadIdentityPools/default/providers/github + service_account: gh-actions@ynufes-tech-gh.iam.gserviceaccount.com + + - name: Setup aqua + uses: aquaproj/aqua-installer@v3.1.0 + with: + aqua_version: v2.36.1 + + - name: Terraform fmt + run: terraform fmt -check -recursive + + - name: Terraform init + run: terraform init -input=false + + - name: Terraform validate + run: terraform validate -no-color + + - name: Terraform plan (tfcmt) + env: + TF_VAR_github_token: ${{ steps.app_token.outputs.token }} + # tfcmt の PR コメント投稿は repo 権限を持つ Actions 標準トークンで行う + # (App token は org Members 権限のみで PR コメントを投稿できない) + GITHUB_TOKEN: ${{ github.token }} + run: tfcmt plan -patch -- terraform plan -input=false -no-color -out=tfplan diff --git a/.github/workflows/validate-profile.yml b/.github/workflows/validate-profile.yml new file mode 100644 index 0000000..32fc651 --- /dev/null +++ b/.github/workflows/validate-profile.yml @@ -0,0 +1,23 @@ +name: Validate Profile Render + +on: + pull_request: + paths: + - data/core_members.yaml + - scripts/render_members.py + - profile/README.md + - .github/workflows/validate-profile.yml + workflow_dispatch: + +jobs: + validate-render: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup uv + uses: astral-sh/setup-uv@v5 + + - name: Verify README is rendered + run: uv run scripts/render_members.py --check diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..00da56f --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +.venv/ +__pycache__/ +*.pyc + +# Terraform +terraform/.terraform/ +terraform/tfplan +terraform/*.tfplan +terraform/*.tfstate +terraform/*.tfstate.* diff --git a/.tfcmt.yaml b/.tfcmt.yaml new file mode 100644 index 0000000..ddb788e --- /dev/null +++ b/.tfcmt.yaml @@ -0,0 +1,2 @@ +--- +ci: github-actions diff --git a/README.md b/README.md new file mode 100644 index 0000000..9325edf --- /dev/null +++ b/README.md @@ -0,0 +1,47 @@ +# ynufes-tech/.github + +`ynufes-tech` Org のチームの管理と、プロフィールページを管理するレポジトリです。 + +- `profile/README.md`: Organization プロフィールとして表示される README +- `terraform/`: Organization のチームを Terraform で管理 +- `scripts/` + `.github/workflows/`: team のメンバー情報を取得し、プロフィールのメンバー一覧を自動更新するスクリプト + +## 全体の流れ + +メンバーは Terraform コード(`terraform/team_memberships.tf`)で管理しています。メンバー情報を更新するにはこのファイルを編集しPRを作成してください。 +Mergeされるとチーム情報とプロフィール表示の両方が自動で更新されます。 + +```mermaid +flowchart TB + subgraph Phase1["1. GitHub チーム情報の更新"] + direction TB + PR1["① Terraform コードを編集
(team / メンバーの追加・削除)"] + Review1["② Review & Merge"] + Apply["③ GitHub Actions: Terraform apply
GitHub team / メンバーシップを更新"] + Teams[("GitHub Organization の
team / 所属")] + PR1 --> Review1 ==> Apply --> Teams + end + + subgraph Phase2["2. Org README の更新"] + direction TB + Sync["④ GitHub Actions: team メンバーを取得して
Org README を再生成してPRを作成"] + Review2["⑤ 自動生成された
Org README 更新PRをMerge"] + OrgReadme[("Org README メンバー一覧")] + Sync ==> Review2 --> OrgReadme + end + + Teams --> Sync +``` + +## チームについて + +| team | 用途 | +| --- | --- | +| `core-members-YYYY` | 年度ごとの現役メンバー。Organization README のメンバー一覧表示は最新年度が対象 | +| `all-members` | 技術部局に所属したことのあるメンバー | + +チーム情報の変更は必ず `terraform/team_memberships.tf` を編集する PR で行ってください。 + +## CI/CD とローカル開発 + +ワークフローの詳細・必要な secrets・ローカル開発手順は [.github/README.md](.github/README.md) を参照してください。 diff --git a/aqua.yaml b/aqua.yaml new file mode 100644 index 0000000..6576bb8 --- /dev/null +++ b/aqua.yaml @@ -0,0 +1,7 @@ +--- +registries: + - type: standard + ref: v4.360.0 +packages: + - name: hashicorp/terraform@v1.9.5 + - name: suzuki-shunsuke/tfcmt@v4.14.0 diff --git a/data/core_members.yaml b/data/core_members.yaml new file mode 100644 index 0000000..9af07fc --- /dev/null +++ b/data/core_members.yaml @@ -0,0 +1,31 @@ +# このファイルは GitHub Actions (`sync-core-members.yml`) が自動生成しています。 +# 手動で編集しないでください。 +# メンバーの追加・削除は `terraform/team_memberships.tf` を編集する PR で行ってください。 +core_members: + team_slug: core-members-2026 + year: 2026 + members: + - Hasegawa18 + - huugayamashita + - Issy0507 + - MTowa + - somanosuke +all_members: + team_slug: all-members + members: + - aguri1102 + - akari4455 + - fujisawakanon + - Hasegawa18 + - HashikawaNana + - hayato19 + - hinata0629 + - huugayamashita + - Issy0507 + - ma2192006 + - MTowa + - Sakiho-Ishii + - Shion1305 + - shuma7 + - somanosuke + - tomoyahiroe diff --git a/profile/README.md b/profile/README.md index 034c5a6..c619799 100644 --- a/profile/README.md +++ b/profile/README.md @@ -11,8 +11,19 @@ # Members -[](https://github.com/hayato19) [](https://github.com/hinata0629) [](https://github.com/shuma7) [](https://github.com/HashikawaNana) [](https://github.com/shion1305) [](https://github.com/tomoyahiroe) + +> [!IMPORTANT] +> 以下のメンバーアイコン一覧は GitHub team から自動生成しています。手動で編集しないでください。 +> メンバーの追加・削除は Terraform (`terraform/team_memberships.tf`) で管理しています。 +### Core Members / 現役メンバー `core-members-2026` + +[Hasegawa18](https://github.com/Hasegawa18) [huugayamashita](https://github.com/huugayamashita) [Issy0507](https://github.com/Issy0507) [MTowa](https://github.com/MTowa) [somanosuke](https://github.com/somanosuke) + +### Members (OB/OG含む) + +[aguri1102](https://github.com/aguri1102) [akari4455](https://github.com/akari4455) [fujisawakanon](https://github.com/fujisawakanon) [Hasegawa18](https://github.com/Hasegawa18) [HashikawaNana](https://github.com/HashikawaNana) [hayato19](https://github.com/hayato19) [hinata0629](https://github.com/hinata0629) [huugayamashita](https://github.com/huugayamashita) [Issy0507](https://github.com/Issy0507) [ma2192006](https://github.com/ma2192006) [MTowa](https://github.com/MTowa) [Sakiho-Ishii](https://github.com/Sakiho-Ishii) [Shion1305](https://github.com/Shion1305) [shuma7](https://github.com/shuma7) [somanosuke](https://github.com/somanosuke) [tomoyahiroe](https://github.com/tomoyahiroe) + # Our Tech Stack diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..055a275 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,11 @@ +[project] +name = "ynufes-github-profile-automation" +version = "0.1.0" +description = "Automation scripts for ynufes-tech .github profile members" +requires-python = ">=3.12" +dependencies = [ + "pyyaml>=6.0.3", +] + +[tool.uv] +package = false diff --git a/scripts/fetch_core_members.py b/scripts/fetch_core_members.py new file mode 100644 index 0000000..7c4c577 --- /dev/null +++ b/scripts/fetch_core_members.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import argparse +import re +import subprocess +from dataclasses import dataclass +from pathlib import Path + +import yaml + +HEADER_COMMENT = ( + "# このファイルは GitHub Actions (`sync-core-members.yml`) が自動生成しています。\n" + "# 手動で編集しないでください。\n" + "# メンバーの追加・削除は `terraform/team_memberships.tf` を編集する PR で行ってください。\n" +) + + +TEAM_SLUG_PATTERN = re.compile(r"^core-members-(\d{4})$") +ALL_MEMBERS_SLUG = "all-members" + + +@dataclass +class Args: + org: str + out: Path + + +def parse_args() -> Args: + parser = argparse.ArgumentParser( + description=( + "ynufes-tech org の `core-members-YYYY`(最新年度)と `all-members` の " + "メンバー一覧を取得し YAML に書き出します" + ) + ) + parser.add_argument("--org", default="ynufes-tech") + parser.add_argument("--out", default="data/core_members.yaml") + ns = parser.parse_args() + return Args(org=ns.org, out=Path(ns.out)) + + +def gh_api_paginate(path: str, jq: str | None = None) -> str: + cmd = ["gh", "api", "--paginate", path] + if jq is not None: + cmd += ["--jq", jq] + # stderr は capture せず CI ログにそのまま流す(gh のエラー内容を隠さない) + result = subprocess.run(cmd, check=True, text=True, stdout=subprocess.PIPE) + return result.stdout + + +def list_team_slugs(org: str) -> list[str]: + raw = gh_api_paginate(f"/orgs/{org}/teams", jq=".[].slug") + return [line.strip() for line in raw.splitlines() if line.strip()] + + +def core_member_years(slugs: list[str]) -> list[int]: + years = { + int(m.group(1)) for slug in slugs if (m := TEAM_SLUG_PATTERN.match(slug)) + } + return sorted(years, reverse=True) + + +def fetch_team_members(org: str, slug: str) -> list[str]: + raw = gh_api_paginate( + f"/orgs/{org}/teams/{slug}/members", + jq=".[].login", + ) + logins = [line.strip() for line in raw.splitlines() if line.strip()] + logins.sort(key=str.lower) + return logins + + +def pick_core_members(org: str, years: list[int]) -> dict: + # 次年度 team がメンバー未定のまま先行作成されても sync が止まらないよう、 + # メンバーが 1 人以上いる最新年度を採用する + for year in years: + slug = f"core-members-{year}" + logins = fetch_team_members(org, slug) + if logins: + return { + "team_slug": slug, + "year": year, + "members": logins, + } + raise SystemExit( + "すべての `core-members-YYYY` team のメンバーが 0 件でした。 " + "README 誤空化を防ぐため処理を中止します。" + ) + + +def main() -> None: + args = parse_args() + + slugs = list_team_slugs(args.org) + + years = core_member_years(slugs) + if not years: + raise SystemExit( + f"`core-members-YYYY` 形式の team が org `{args.org}` に見つかりません。 " + "Terraform apply 後に再実行してください。" + ) + + if ALL_MEMBERS_SLUG not in slugs: + raise SystemExit( + f"team `{ALL_MEMBERS_SLUG}` が org `{args.org}` に見つかりません。" + ) + + core = pick_core_members(args.org, years) + + all_logins = fetch_team_members(args.org, ALL_MEMBERS_SLUG) + if not all_logins: + raise SystemExit( + f"team `{ALL_MEMBERS_SLUG}` のメンバーが 0 件でした。 " + "README 誤空化を防ぐため処理を中止します。" + ) + + payload = { + "core_members": core, + "all_members": { + "team_slug": ALL_MEMBERS_SLUG, + "members": all_logins, + }, + } + + args.out.parent.mkdir(parents=True, exist_ok=True) + body = yaml.safe_dump( + payload, + allow_unicode=True, + sort_keys=False, + default_flow_style=False, + ) + args.out.write_text(HEADER_COMMENT + body, encoding="utf-8") + + +if __name__ == "__main__": + main() diff --git a/scripts/render_members.py b/scripts/render_members.py new file mode 100755 index 0000000..c524c39 --- /dev/null +++ b/scripts/render_members.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import argparse +import re +from pathlib import Path +from typing import Any + +import yaml + +START_MARKER = "" +END_MARKER = "" + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description=( + "data/core_members.yaml から profile/README.md のメンバーアイコンを再生成します" + ) + ) + parser.add_argument("--members-file", default="data/core_members.yaml") + parser.add_argument("--readme-file", default="profile/README.md") + parser.add_argument("--check", action="store_true") + return parser.parse_args() + + +def load_payload(path: Path) -> dict[str, Any]: + raw = yaml.safe_load(path.read_text(encoding="utf-8")) + if not isinstance(raw, dict): + raise SystemExit(f"{path} の形式が不正です (mapping ではありません)") + return raw + + +def extract_usernames(entries: Any) -> list[str]: + if not isinstance(entries, list): + return [] + + usernames: list[str] = [] + for entry in entries: + if not isinstance(entry, str): + continue + username = entry.strip() + if not username: + continue + usernames.append(username) + + usernames.sort(key=str.lower) + return usernames + + +def build_icons_line(usernames: list[str]) -> str: + if not usernames: + return "_メンバーはまだ登録されていません。_" + + icons = [ + f'[{username}](https://github.com/{username})' + for username in usernames + ] + return " ".join(icons) + + +def build_generated_block(payload: dict[str, Any]) -> str: + core = payload.get("core_members", {}) or {} + all_members = payload.get("all_members", {}) or {} + + core_slug = str(core.get("team_slug") or "core-members-YYYY") + all_slug = str(all_members.get("team_slug") or "all-members") + + core_rows = extract_usernames(core.get("members")) + all_rows = extract_usernames(all_members.get("members")) + + return "\n".join( + [ + "> [!IMPORTANT]", + "> 以下のメンバーアイコン一覧は GitHub team から自動生成しています。手動で編集しないでください。", + "> メンバーの追加・削除は Terraform (`terraform/team_memberships.tf`) で管理しています。", + "", + f"### Core Members / 現役メンバー `{core_slug}`", + "", + build_icons_line(core_rows), + "", + "### Members (OB/OG含む)", + "", + build_icons_line(all_rows), + ] + ) + + +def render(readme_text: str, generated_block: str) -> str: + pattern = re.compile( + rf"({re.escape(START_MARKER)}\n)(.*?)(\n{re.escape(END_MARKER)})", + re.DOTALL, + ) + if not pattern.search(readme_text): + raise ValueError( + "README にメンバーブロックのマーカーがありません: " + f"{START_MARKER} ... {END_MARKER}" + ) + return pattern.sub( + lambda m: m.group(1) + generated_block + m.group(3), readme_text + ) + + +def main() -> None: + args = parse_args() + payload = load_payload(Path(args.members_file)) + generated_block = build_generated_block(payload) + + readme_path = Path(args.readme_file) + before = readme_path.read_text(encoding="utf-8") + after = render(before, generated_block) + + if args.check: + if before != after: + raise SystemExit( + "profile/README.md が最新ではありません。 " + "次を実行してください: uv run scripts/render_members.py" + ) + return + + if before != after: + readme_path.write_text(after, encoding="utf-8") + + +if __name__ == "__main__": + main() diff --git a/terraform/.terraform.lock.hcl b/terraform/.terraform.lock.hcl new file mode 100644 index 0000000..bc5d376 --- /dev/null +++ b/terraform/.terraform.lock.hcl @@ -0,0 +1,25 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/integrations/github" { + version = "6.13.0" + constraints = "~> 6.0" + hashes = [ + "h1:2kD+4leuV8tBBXv+EPeehmfW6cDhIzVki61OXsGCtRI=", + "h1:y0Sujto8gttV86innNp/LTMzq7CqsFpBs7XKH8AlMl4=", + "zh:0ab29fc21699f34345cf0bbbe44745fd1b143b7c73b410c1dc4abe05ffad0a84", + "zh:1aed10d06755d420bb3a893bf548ab2932297a9d094c04c5a8501e949ca186ed", + "zh:2a6a11c21eae408055f45b9533c07afd2e845f6d496fd1b645aec2e873012103", + "zh:5dd05dee677f6ebdbed00cbb1b9be444ab2d1062d345cbc9ec50a47cb41b8622", + "zh:6b757d034831243d67ddda869eac4368cef539848bd97511f4d68f1aa38a9c88", + "zh:947c9b5b238f0364c57a705beabd24d3eea3159a6f3a24c07e3fbb13657ffae0", + "zh:a676549a98164b61630658cbeb6c17820331ca04a049dc9b5095996a0c31ffbe", + "zh:a8a81b7fe41dd61eb6a6fa5e08a4dd9ee070e862868252a7fd4cfce30364efee", + "zh:c26a9bca4865665084e7f59b1402d7aff34ee63a418d7401a0658fa280cad4d4", + "zh:c638d8d0762e62ea188f86302954ef4c92803f2160f0a45fca0cd13974bd3725", + "zh:e739a0b7e81ca816944a18a38e679f4015edf8be7ac319815cdea865ba7727d7", + "zh:ec099487ea3de8999c84b3b791e242d728461e51fe344832b37bd8d521201c77", + "zh:f016ff9e2daab5b88185cec0795213049d105439ffd585d3309a714514ccae13", + "zh:fbd1fee2c9df3aa19cf8851ce134dea6e45ea01cb85695c1726670c285797e25", + ] +} diff --git a/terraform/backend.tf b/terraform/backend.tf new file mode 100644 index 0000000..3ef8e6f --- /dev/null +++ b/terraform/backend.tf @@ -0,0 +1,6 @@ +terraform { + backend "gcs" { + bucket = "ynufes-tech-gh-tf-state" + prefix = "github-org" + } +} diff --git a/terraform/imports.tf b/terraform/imports.tf new file mode 100644 index 0000000..2bd1ccb --- /dev/null +++ b/terraform/imports.tf @@ -0,0 +1,59 @@ +import { + id = "9608186" + to = github_team.core_members_2024 +} + +import { + id = "12233900" + to = github_team.core_members_2025 +} + +import { + id = "15547740" + to = github_team.core_members_2026 +} + +import { + id = "16613363" + to = github_team.all_members +} + +import { + id = "6360571" + to = github_team.core_members_2022 +} + +import { + id = "7550421" + to = github_team.core_members_2023 +} + +import { + id = "9608186" + to = github_team_members.core_members_2024 +} + +import { + id = "12233900" + to = github_team_members.core_members_2025 +} + +import { + id = "15547740" + to = github_team_members.core_members_2026 +} + +import { + id = "16613363" + to = github_team_members.all_members +} + +import { + id = "6360571" + to = github_team_members.core_members_2022 +} + +import { + id = "7550421" + to = github_team_members.core_members_2023 +} diff --git a/terraform/locals.tf b/terraform/locals.tf new file mode 100644 index 0000000..9dd5e5d --- /dev/null +++ b/terraform/locals.tf @@ -0,0 +1,61 @@ +locals { + core_members_2022 = [ + "Shion1305", + ] + + core_members_2023 = [ + "HashikawaNana", + "Shion1305", + "hayato19", + "hinata0629", + "shuma7", + "tomoyahiroe", + ] + + core_members_2024 = [ + "aguri1102", + "akari4455", + "fujisawakanon", + "hayato19", + "hinata0629", + "ma2192006", + "shuma7", + ] + + core_members_2025 = [ + "aguri1102", + "akari4455", + "fujisawakanon", + "Hasegawa18", + "huugayamashita", + "Issy0507", + "ma2192006", + "Sakiho-Ishii", + "somanosuke", + ] + + core_members_2026 = [ + "Hasegawa18", + "huugayamashita", + "Issy0507", + "MTowa", + "somanosuke", + ] + + all_members_maintainers = [ + "Shion1305", + ] + + all_members_union = toset(concat( + local.core_members_2022, + local.core_members_2023, + local.core_members_2024, + local.core_members_2025, + local.core_members_2026, + )) + + all_members = { + for username in local.all_members_union : + username => contains(local.all_members_maintainers, username) ? "maintainer" : "member" + } +} diff --git a/terraform/provider.tf b/terraform/provider.tf new file mode 100644 index 0000000..e5d99ac --- /dev/null +++ b/terraform/provider.tf @@ -0,0 +1,9 @@ +variable "github_token" { + type = string + sensitive = true +} + +provider "github" { + owner = "ynufes-tech" + token = var.github_token +} diff --git a/terraform/team_memberships.tf b/terraform/team_memberships.tf new file mode 100644 index 0000000..5b1c414 --- /dev/null +++ b/terraform/team_memberships.tf @@ -0,0 +1,71 @@ +resource "github_team_members" "core_members_2022" { + team_id = github_team.core_members_2022.id + + dynamic "members" { + for_each = toset(local.core_members_2022) + content { + username = members.value + role = "member" + } + } +} + +resource "github_team_members" "core_members_2023" { + team_id = github_team.core_members_2023.id + + dynamic "members" { + for_each = toset(local.core_members_2023) + content { + username = members.value + role = "member" + } + } +} + +resource "github_team_members" "core_members_2024" { + team_id = github_team.core_members_2024.id + + dynamic "members" { + for_each = toset(local.core_members_2024) + content { + username = members.value + role = "member" + } + } +} + +resource "github_team_members" "core_members_2025" { + team_id = github_team.core_members_2025.id + + dynamic "members" { + for_each = toset(local.core_members_2025) + content { + username = members.value + role = "member" + } + } +} + +resource "github_team_members" "core_members_2026" { + team_id = github_team.core_members_2026.id + + dynamic "members" { + for_each = toset(local.core_members_2026) + content { + username = members.value + role = "member" + } + } +} + +resource "github_team_members" "all_members" { + team_id = github_team.all_members.id + + dynamic "members" { + for_each = local.all_members + content { + username = members.key + role = members.value + } + } +} diff --git a/terraform/teams.tf b/terraform/teams.tf new file mode 100644 index 0000000..6e37c3c --- /dev/null +++ b/terraform/teams.tf @@ -0,0 +1,35 @@ +resource "github_team" "core_members_2024" { + name = "Core Members 2024" + description = "2024年度現役" + privacy = "closed" +} + +resource "github_team" "core_members_2025" { + name = "Core Members 2025" + description = "2025年度現役" + privacy = "closed" +} + +resource "github_team" "core_members_2026" { + name = "Core Members 2026" + description = "2026年度現役" + privacy = "closed" +} + +resource "github_team" "all_members" { + name = "All Members" + description = "技術部局 全メンバー" + privacy = "closed" +} + +resource "github_team" "core_members_2022" { + name = "Core Members 2022" + description = "2022年度現役" + privacy = "closed" +} + +resource "github_team" "core_members_2023" { + name = "Core Members 2023" + description = "2023年度現役" + privacy = "closed" +} diff --git a/terraform/versions.tf b/terraform/versions.tf new file mode 100644 index 0000000..5d14d23 --- /dev/null +++ b/terraform/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.6" + + required_providers { + github = { + source = "integrations/github" + version = "~> 6.0" + } + } +} diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..432f4e0 --- /dev/null +++ b/uv.lock @@ -0,0 +1,60 @@ +version = 1 +revision = 3 +requires-python = ">=3.12" + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + +[[package]] +name = "ynufes-github-profile-automation" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "pyyaml" }, +] + +[package.metadata] +requires-dist = [{ name = "pyyaml", specifier = ">=6.0.3" }]