Loading .github/workflows/team.yml 0 → 100644 +81 −0 Original line number Diff line number Diff line name: Teams on: schedule: # Every Tuesday at 19:42 (randomly chosen) - cron: '42 19 * * 1' # Allows manual trigger workflow_dispatch: permissions: {} defaults: run: shell: bash jobs: sync: runs-on: ubuntu-24.04-arm steps: - uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4 id: team-token with: app-id: ${{ vars.OWNER_APP_ID }} private-key: ${{ secrets.OWNER_APP_PRIVATE_KEY }} permission-administration: read permission-members: read - name: Fetch source uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false sparse-checkout: | ci/github-script maintainers/github-teams.json - name: Install dependencies run: npm install bottleneck - name: Synchronise teams uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: github-token: ${{ steps.team-token.outputs.token }} script: | require('./ci/github-script/get-teams.js')({ github, context, core, outFile: "maintainers/github-teams.json" }) # Use a GitHub App to create the PR so that CI gets triggered - uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4 id: sync-token with: app-id: ${{ vars.NIXPKGS_CI_APP_ID }} private-key: ${{ secrets.NIXPKGS_CI_APP_PRIVATE_KEY }} permission-contents: write permission-pull-requests: write - name: Get GitHub App User Git String id: user env: GH_TOKEN: ${{ steps.sync-token.outputs.token }} APP_SLUG: ${{ steps.sync-token.outputs.app-slug }} run: | name="${APP_SLUG}[bot]" userId=$(gh api "/users/$name" --jq .id) email="$userId+$name@users.noreply.github.com" echo "git-string=$name <$email>" >> "$GITHUB_OUTPUT" - name: Create Pull Request uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8 with: token: ${{ steps.sync-token.outputs.token }} add-paths: maintainers/github-teams.json author: ${{ steps.user.outputs.git-string }} committer: ${{ steps.user.outputs.git-string }} commit-message: "maintainers/github-teams.json: Automated sync" branch: github-team-sync title: "maintainers/github-teams.json: Automated sync" body: | This is an automated PR to sync the GitHub teams with access to this repository to the `lib.teams` list. This PR can be merged without taking any further action. ci/OWNERS +2 −0 Original line number Diff line number Diff line Loading @@ -32,6 +32,8 @@ /lib/asserts.nix @infinisil @hsjobeki @Profpatsch /lib/path/* @infinisil @hsjobeki /lib/fileset @infinisil @hsjobeki /maintainers/github-teams.json @infinisil /maintainers/computed-team-list.nix @infinisil ## Standard environment–related libraries /lib/customisation.nix @alyssais @NixOS/stdenv /lib/derivations.nix @alyssais @NixOS/stdenv Loading ci/github-script/get-teams.js 0 → 100755 +82 −0 Original line number Diff line number Diff line const excludeTeams = [ /^voters.*$/, /^nixpkgs-maintainers$/, /^nixpkgs-committers$/, ] module.exports = async ({ github, context, core, outFile }) => { const withRateLimit = require('./withRateLimit.js') const { writeFileSync } = require('node:fs') const result = {} await withRateLimit({ github, core }, async (_stats) => { /// Turn an Array of users into an Object, mapping user.login -> user.id function makeUserSet(users) { // Sort in-place and build result by mutation users.sort((a, b) => (a.login > b.login ? 1 : -1)) return users.reduce((acc, user) => { acc[user.login] = user.id return acc }, {}) } /// Process a list of teams and append to the result variable async function processTeams(teams) { for (const team of teams) { core.notice(`Processing team ${team.slug}`) if (!excludeTeams.some((regex) => team.slug.match(regex))) { const members = makeUserSet( await github.paginate(github.rest.teams.listMembersInOrg, { org: context.repo.owner, team_slug: team.slug, role: 'member', }), ) const maintainers = makeUserSet( await github.paginate(github.rest.teams.listMembersInOrg, { org: context.repo.owner, team_slug: team.slug, role: 'maintainer', }), ) result[team.slug] = { description: team.description, id: team.id, maintainers, members, name: team.name, } } await processTeams( await github.paginate(github.rest.teams.listChildInOrg, { org: context.repo.owner, team_slug: team.slug, }), ) } } const teams = await github.paginate(github.rest.repos.listTeams, { owner: context.repo.owner, repo: context.repo.repo, }) await processTeams(teams) }) // Sort the teams by team name const sorted = Object.keys(result) .sort() .reduce((acc, key) => { acc[key] = result[key] return acc }, {}) const json = `${JSON.stringify(sorted, null, 2)}\n` if (outFile) { writeFileSync(outFile, json) } else { console.log(json) } } ci/github-script/run +12 −0 Original line number Diff line number Diff line Loading @@ -83,4 +83,16 @@ program } }) program .command('get-teams') .description('Fetch the list of teams with GitHub and output it to a file') .argument('<owner>', 'Owner of the GitHub repository to label (Example: NixOS)') .argument('<repo>', 'Name of the GitHub repository to label (Example: nixpkgs)') .argument('[outFile]', 'Path to the output file (Example: github-teams.json). If not set, prints to stdout') .action(async (owner, repo, outFile, options) => { const getTeams = (await import('./get-teams.js')).default // TODO: Refactor this file so we don't need to pass a PR await run(getTeams, owner, repo, undefined, { ...options, outFile }) }) await program.parse() lib/default.nix +1 −1 Original line number Diff line number Diff line Loading @@ -63,7 +63,7 @@ let customisation = callLibs ./customisation.nix; derivations = callLibs ./derivations.nix; maintainers = import ../maintainers/maintainer-list.nix; teams = callLibs ../maintainers/team-list.nix; teams = callLibs ../maintainers/computed-team-list.nix; meta = callLibs ./meta.nix; versions = callLibs ./versions.nix; Loading Loading
.github/workflows/team.yml 0 → 100644 +81 −0 Original line number Diff line number Diff line name: Teams on: schedule: # Every Tuesday at 19:42 (randomly chosen) - cron: '42 19 * * 1' # Allows manual trigger workflow_dispatch: permissions: {} defaults: run: shell: bash jobs: sync: runs-on: ubuntu-24.04-arm steps: - uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4 id: team-token with: app-id: ${{ vars.OWNER_APP_ID }} private-key: ${{ secrets.OWNER_APP_PRIVATE_KEY }} permission-administration: read permission-members: read - name: Fetch source uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false sparse-checkout: | ci/github-script maintainers/github-teams.json - name: Install dependencies run: npm install bottleneck - name: Synchronise teams uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: github-token: ${{ steps.team-token.outputs.token }} script: | require('./ci/github-script/get-teams.js')({ github, context, core, outFile: "maintainers/github-teams.json" }) # Use a GitHub App to create the PR so that CI gets triggered - uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4 id: sync-token with: app-id: ${{ vars.NIXPKGS_CI_APP_ID }} private-key: ${{ secrets.NIXPKGS_CI_APP_PRIVATE_KEY }} permission-contents: write permission-pull-requests: write - name: Get GitHub App User Git String id: user env: GH_TOKEN: ${{ steps.sync-token.outputs.token }} APP_SLUG: ${{ steps.sync-token.outputs.app-slug }} run: | name="${APP_SLUG}[bot]" userId=$(gh api "/users/$name" --jq .id) email="$userId+$name@users.noreply.github.com" echo "git-string=$name <$email>" >> "$GITHUB_OUTPUT" - name: Create Pull Request uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8 with: token: ${{ steps.sync-token.outputs.token }} add-paths: maintainers/github-teams.json author: ${{ steps.user.outputs.git-string }} committer: ${{ steps.user.outputs.git-string }} commit-message: "maintainers/github-teams.json: Automated sync" branch: github-team-sync title: "maintainers/github-teams.json: Automated sync" body: | This is an automated PR to sync the GitHub teams with access to this repository to the `lib.teams` list. This PR can be merged without taking any further action.
ci/OWNERS +2 −0 Original line number Diff line number Diff line Loading @@ -32,6 +32,8 @@ /lib/asserts.nix @infinisil @hsjobeki @Profpatsch /lib/path/* @infinisil @hsjobeki /lib/fileset @infinisil @hsjobeki /maintainers/github-teams.json @infinisil /maintainers/computed-team-list.nix @infinisil ## Standard environment–related libraries /lib/customisation.nix @alyssais @NixOS/stdenv /lib/derivations.nix @alyssais @NixOS/stdenv Loading
ci/github-script/get-teams.js 0 → 100755 +82 −0 Original line number Diff line number Diff line const excludeTeams = [ /^voters.*$/, /^nixpkgs-maintainers$/, /^nixpkgs-committers$/, ] module.exports = async ({ github, context, core, outFile }) => { const withRateLimit = require('./withRateLimit.js') const { writeFileSync } = require('node:fs') const result = {} await withRateLimit({ github, core }, async (_stats) => { /// Turn an Array of users into an Object, mapping user.login -> user.id function makeUserSet(users) { // Sort in-place and build result by mutation users.sort((a, b) => (a.login > b.login ? 1 : -1)) return users.reduce((acc, user) => { acc[user.login] = user.id return acc }, {}) } /// Process a list of teams and append to the result variable async function processTeams(teams) { for (const team of teams) { core.notice(`Processing team ${team.slug}`) if (!excludeTeams.some((regex) => team.slug.match(regex))) { const members = makeUserSet( await github.paginate(github.rest.teams.listMembersInOrg, { org: context.repo.owner, team_slug: team.slug, role: 'member', }), ) const maintainers = makeUserSet( await github.paginate(github.rest.teams.listMembersInOrg, { org: context.repo.owner, team_slug: team.slug, role: 'maintainer', }), ) result[team.slug] = { description: team.description, id: team.id, maintainers, members, name: team.name, } } await processTeams( await github.paginate(github.rest.teams.listChildInOrg, { org: context.repo.owner, team_slug: team.slug, }), ) } } const teams = await github.paginate(github.rest.repos.listTeams, { owner: context.repo.owner, repo: context.repo.repo, }) await processTeams(teams) }) // Sort the teams by team name const sorted = Object.keys(result) .sort() .reduce((acc, key) => { acc[key] = result[key] return acc }, {}) const json = `${JSON.stringify(sorted, null, 2)}\n` if (outFile) { writeFileSync(outFile, json) } else { console.log(json) } }
ci/github-script/run +12 −0 Original line number Diff line number Diff line Loading @@ -83,4 +83,16 @@ program } }) program .command('get-teams') .description('Fetch the list of teams with GitHub and output it to a file') .argument('<owner>', 'Owner of the GitHub repository to label (Example: NixOS)') .argument('<repo>', 'Name of the GitHub repository to label (Example: nixpkgs)') .argument('[outFile]', 'Path to the output file (Example: github-teams.json). If not set, prints to stdout') .action(async (owner, repo, outFile, options) => { const getTeams = (await import('./get-teams.js')).default // TODO: Refactor this file so we don't need to pass a PR await run(getTeams, owner, repo, undefined, { ...options, outFile }) }) await program.parse()
lib/default.nix +1 −1 Original line number Diff line number Diff line Loading @@ -63,7 +63,7 @@ let customisation = callLibs ./customisation.nix; derivations = callLibs ./derivations.nix; maintainers = import ../maintainers/maintainer-list.nix; teams = callLibs ../maintainers/team-list.nix; teams = callLibs ../maintainers/computed-team-list.nix; meta = callLibs ./meta.nix; versions = callLibs ./versions.nix; Loading