diff --git a/.github/workflows/automatic-approval.yml b/.github/workflows/automatic-approval.yml index 20a8ffc76..5893a5d7d 100644 --- a/.github/workflows/automatic-approval.yml +++ b/.github/workflows/automatic-approval.yml @@ -6,7 +6,7 @@ on: jobs: approve-automatic-prs: runs-on: ubuntu-latest - if: github.actor_id == 158783068 || github.actor_id == 190541745 # Id of renovate bot and crowdin bot see https://api.github.com/users/homarr-renovate%5Bbot%5D and https://api.github.com/users/homarr-crowdin%5Bbot%5D + if: github.actor_id == 158783068 || github.actor_id == 190541745 || github.actor_id == 210161987 # Id of renovate bot and crowdin bot see https://api.github.com/users/homarr-renovate%5Bbot%5D and https://api.github.com/users/homarr-crowdin%5Bbot%5D and https://api.github.com/users/homarr-update-contributors%5Bbot%5D steps: - name: Checkout code uses: actions/checkout@v4 diff --git a/.github/workflows/update-contributors.yml b/.github/workflows/update-contributors.yml new file mode 100644 index 000000000..adfd68ad5 --- /dev/null +++ b/.github/workflows/update-contributors.yml @@ -0,0 +1,70 @@ +name: Update Contributors + +on: + schedule: + - cron: "0 12 * * FRI" # At 12:00 on Friday. + workflow_dispatch: + +env: + GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + CROWDIN_TOKEN: "${{ secrets.CROWDIN_UPDATE_CONTRIBUTORS_TOKEN }}" + +permissions: + contents: write + +jobs: + update-contributors: + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [22] + steps: + - name: Obtain token + id: obtainToken + uses: tibdex/github-app-token@v2 + with: + private_key: ${{ secrets.HOMARR_UPDATE_CONTRIBUTORS_PRIVATE_KEY }} + app_id: ${{ vars.HOMARR_UPDATE_CONTRIBUTORS_APP_ID }} + + - name: Checkout repository + uses: actions/checkout@v4 + env: + GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }} + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + + - name: Run update script + run: node ./scripts/update-contributors.mjs + + - name: Commit changes + env: + GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }} + run: | + git config --global user.email "210161987+homarr-update-contributors[bot]@users.noreply.github.com" + git config --global user.name "Homarr Update Contributors" + git add . + git commit -m "chore: update contributors" + + - name: Create Pull Request + id: create-pull-request + uses: peter-evans/create-pull-request@v7 + with: + token: ${{ steps.obtainToken.outputs.token }} + branch: update-contributors + base: dev + title: "chore: update contributors" + delete-branch: true + body: | + This PR updates the contributors list in the static-data directory. + + - name: Install GitHub CLI + run: sudo apt-get install -y gh + + - name: Enable auto-merge + env: + GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }} + run: | + gh pr merge ${{steps.create-pull-request.outputs.pull-request-number}} --auto --squash diff --git a/scripts/update-contributors.mjs b/scripts/update-contributors.mjs new file mode 100644 index 000000000..fc88d5604 --- /dev/null +++ b/scripts/update-contributors.mjs @@ -0,0 +1,73 @@ +import fs from "fs/promises"; + +const sources = { + crowdin: [ + { projectId: 534422 }, + { projectId: 742587 }, + ], + github: [ + { slug: "ajnart", repository: "homarr" }, + { slug: "homarr-labs", repository: "homarr" }, + ], +}; + +const env = { + GITHUB_TOKEN: process.env.GITHUB_TOKEN, + CROWDIN_TOKEN: process.env.CROWDIN_TOKEN, +}; + +const fetchGithubContributors = async (slug, repository) => { + const url = `https://api.github.com/repos/${slug}/${repository}/contributors?per_page=999`; + const options = { + method: "GET", + headers: { + Authorization: `Bearer ${env.GITHUB_TOKEN}`, + Accept: "application/vnd.github+json", + "X-GitHub-Api-Version": "2022-11-28", + }, + }; + + const response = await fetch(url, options); + const data = await response.json(); + + return data; +}; + +const fetchCrowdinMembers = async (projectId) => { + const url = `https://crowdin.com/api/v2/projects/${projectId}/members`; + const options = { + method: "GET", + headers: { + Accept: "application/json", + Authorization: `Bearer ${env.CROWDIN_TOKEN}`, + }, + }; + + const response = await fetch(url, options); + const data = await response.json(); + + return data.data.flatMap((data) => data.data); +}; + +const distinctBy = (callback) => (value, index, self) => { + return self.findIndex((item) => callback(item) === callback(value)) === index; +}; + +const githubContributors = []; +const crowdinContributors = []; + +for (const { repository, slug } of sources.github) { + githubContributors.push(...(await fetchGithubContributors(slug, repository))); +} +const distinctGithubContributors = githubContributors + .filter(distinctBy((contributor) => contributor.login)) + .sort((a, b) => b.contributions - a.contributions) + .map(({ contributions, ...props }) => props) + .filter((contributor) => !contributor.login.includes("[bot]")); +await fs.writeFile("./static-data/contributors.json", JSON.stringify(distinctGithubContributors)); + +for (const { projectId } of sources.crowdin) { + crowdinContributors.push(...(await fetchCrowdinMembers(projectId))); +} +const distinctCrowdinContributors = crowdinContributors.filter(distinctBy((contributor) => contributor.username)); +await fs.writeFile("./static-data/translators.json", JSON.stringify(distinctCrowdinContributors));