Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
8cde457
maintainers: add savtrip
savtrip Oct 23, 2025
1f42945
maintainers: add msgilligan
msgilligan Jul 2, 2025
44853cc
maintainers: add redianthus
redianthus Sep 21, 2025
1cf1500
team-list: rename rocm-maintainers to rocm
LunNova Oct 5, 2025
ce10e18
maintainers/team-list: update marketing team to current standing
djacu Sep 23, 2025
0166600
maintainers.teams: remove release-engineers form feature freez ping
jopejoe1 Sep 20, 2025
8f1c6f9
teams/loongarch64: init
Aleksanaa Jun 22, 2025
6572669
lib.teams: Remove .githubTeams in favor of singular .github
infinisil Oct 1, 2025
6c86bef
team/ocaml: add redianthus
redianthus Oct 7, 2025
1002705
maintainers/scripts/feature-freeze-teams.pl: Use GitHub CLI
infinisil Oct 20, 2025
1de170b
maintainers/scripts/feature-freeze-teams.pl: Output result to stdout
infinisil Oct 20, 2025
2bf8058
lib.teams: Remove unused teams
infinisil Oct 20, 2025
cfacfa0
lib.teams: Remove dummy Nixpkgs committers team and hardcode into fea…
infinisil Oct 20, 2025
f99778e
lib.teams: Some changes based on current GitHub state
infinisil Oct 11, 2025
159b3d1
workflows/team-sync: init
infinisil Oct 11, 2025
2e8ba5b
maintainers/github-teams.json: Automated sync
infinisil Oct 11, 2025
5098220
maintainers/github-teams.json: Manual adjustment of necessary changes
infinisil Oct 11, 2025
2a57eba
lib.teams: Populate fields from synced GitHub state
infinisil Oct 11, 2025
2e0cd80
lib.teams: Add githubMaintainers field
infinisil Oct 11, 2025
1d47e0e
lib.teams: Add githubId from associated github teams
infinisil Oct 22, 2025
86a5657
workflows/teams: rename from team
wolfgangwalther Oct 28, 2025
73f9536
workflows/teams: consistent style with other workflows
wolfgangwalther Oct 28, 2025
9638571
ci/github-script/teams: use consistent style
wolfgangwalther Oct 28, 2025
ca4cada
workflows/team: Prefix PR branch with create-pull-request
infinisil Oct 28, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions .github/workflows/teams.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
name: Teams

on:
schedule:
# Every Tuesday at 19:42 (randomly chosen)
- cron: '42 19 * * 1'
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: pr/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.

2 changes: 2 additions & 0 deletions ci/OWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
85 changes: 85 additions & 0 deletions ci/github-script/get-teams.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
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 org = context.repo.owner

const result = {}

await withRateLimit({ github, core }, async () => {
// 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,
team_slug: team.slug,
role: 'member',
}),
)
const maintainers = makeUserSet(
await github.paginate(github.rest.teams.listMembersInOrg, {
org,
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,
team_slug: team.slug,
}),
)
}
}

const teams = await github.paginate(github.rest.repos.listTeams, {
...context.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)
}
}
11 changes: 11 additions & 0 deletions ci/github-script/run
Original file line number Diff line number Diff line change
Expand Up @@ -83,4 +83,15 @@ 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
await run(getTeams, owner, repo, undefined, { ...options, outFile })
})

await program.parse()
2 changes: 1 addition & 1 deletion lib/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
14 changes: 11 additions & 3 deletions lib/tests/teams.nix
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,19 @@ let
default = false;
};
members = lib.mkOption {
type = types.listOf (types.submodule (import ./maintainer-module.nix { inherit lib; }));
type = types.listOf (types.submodule ./maintainer-module.nix);
default = [ ];
};
githubTeams = lib.mkOption {
type = types.listOf types.str;
github = lib.mkOption {
type = types.str;
default = "";
};
githubId = lib.mkOption {
type = types.int;
default = 0;
};
githubMaintainers = lib.mkOption {
type = types.listOf (types.submodule ./maintainer-module.nix);
default = [ ];
};
};
Expand Down
1 change: 0 additions & 1 deletion maintainers/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,6 @@ team after giving the existing members a few days to respond.
*Important:* If a team says it is a closed group, do not merge additions
to the team without an approval by at least one existing member.


# Maintainer scripts

Various utility scripts, which are mainly useful for nixpkgs maintainers,
Expand Down
59 changes: 59 additions & 0 deletions maintainers/computed-team-list.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Extends ./team-list.nix with synced GitHub information from ./github-teams.json
{ lib }:
let
githubTeams = lib.importJSON ./github-teams.json;
teams = import ./team-list.nix { inherit lib; };

# A fast maintainer id lookup table
maintainersById = lib.pipe lib.maintainers [
lib.attrValues
(lib.groupBy (attrs: toString attrs.githubId))
(lib.mapAttrs (_: lib.head))
];

maintainerSetToList =
githubTeam:
lib.mapAttrsToList (
login: id:
maintainersById.${toString id}
or (throw "lib.teams: No maintainer entry for GitHub user @${login} who's part of the @NixOS/${githubTeam} team")
);

in
# TODO: Consider automatically exposing all GitHub teams under `lib.teams`,
# so that no manual PRs are necessary to add such teams anymore
lib.mapAttrs (
name: attrs:
if attrs ? github then
let
githubTeam =
githubTeams.${attrs.github}
or (throw "lib.teams.${name}: Corresponding GitHub team ${attrs.github} not known yet, make sure to create it and wait for the regular team sync");
in
# TODO: Consider specifying `githubId` in team-list.nix and inferring `github` from it (or even dropping it)
# This would make renames easier because no additional place needs to keep track of them.
# Though in a future where all Nixpkgs GitHub teams are automatically exposed under `lib.teams`,
# this would also not be an issue anymore.
assert lib.assertMsg (!attrs ? githubId)
"lib.teams.${name}: Both `githubId` and `github` is set, but the former is synced from the latter";
assert lib.assertMsg (!attrs ? shortName)
"lib.teams.${name}: Both `shortName` and `github` is set, but the former is synced from the latter";
assert lib.assertMsg (
!attrs ? scope
) "lib.teams.${name}: Both `scope` and `github` is set, but the former is synced from the latter";
assert lib.assertMsg (
!attrs ? members
) "lib.teams.${name}: Both `members` and `github` is set, but the former is synced from the latter";
attrs
// {
githubId = githubTeam.id;
shortName = githubTeam.name;
scope = githubTeam.description;
members =
maintainerSetToList attrs.github githubTeam.maintainers
++ maintainerSetToList attrs.github githubTeam.members;
githubMaintainers = maintainerSetToList attrs.github githubTeam.maintainers;
}
else
attrs
) teams
Loading
Loading