diff --git a/.github/repos/hacktron.yml b/.github/repos/hacktron.yml
new file mode 100644
index 000000000..d7533b77c
--- /dev/null
+++ b/.github/repos/hacktron.yml
@@ -0,0 +1,44 @@
+repository:
+ name: hacktron
+ private: true
+ allow_squash_merge: true
+ allow_merge_commit: true
+ allow_rebase_merge: true
+
+branches:
+ - name: main
+ protection:
+ required_pull_request_reviews:
+ required_approving_review_count: 1
+ dismiss_stale_reviews: true
+ require_code_owner_reviews: true
+ require_last_push_approval: true
+ bypass_pull_request_allowances:
+ users: []
+ teams:
+ - core
+ apps: []
+ required_status_checks: null
+ enforce_admins: true
+ restrictions:
+ users: []
+ teams:
+ - core
+ apps: []
+ required_linear_history: false
+ allow_force_pushes: true
+ allow_deletions: false
+
+ - name: staging
+ protection:
+ required_pull_request_reviews:
+ required_approving_review_count: 1
+ dismiss_stale_reviews: false
+ require_code_owner_reviews: false
+ require_last_push_approval: false
+ required_status_checks: null
+ enforce_admins: false
+ restrictions: null
+ required_linear_history: false
+ allow_force_pushes: false
+ allow_deletions: false
diff --git a/docs/github-settings/4. teams.md b/docs/github-settings/4. teams.md
index e6085ebcd..1bd035969 100644
--- a/docs/github-settings/4. teams.md
+++ b/docs/github-settings/4. teams.md
@@ -48,5 +48,33 @@ teams:
permission: maintain
```
+
+
|
+ exclude array
+Exclude a list of repos for this team. The team is applied to every repo in scope except those whose names match one of these glob patterns.
+ |
+
+```yaml
+teams:
+ - name: SuperFriends
+ permission: maintain
+ exclude:
+ - secret-repo
+```
+
+ |
+|
+ include array
+Include a list of repos for this team. The team is applied only to repos whose names match one of these glob patterns.
+ |
+
+```yaml
+teams:
+ - name: SuperFriends
+ permission: maintain
+ include:
+ - public-*
+```
+
|
diff --git a/docs/sample-settings/settings.yml b/docs/sample-settings/settings.yml
index 1ede6a079..6d12884ec 100644
--- a/docs/sample-settings/settings.yml
+++ b/docs/sample-settings/settings.yml
@@ -161,6 +161,16 @@ teams:
- name: globalteam
permission: push
visibility: closed
+ - name: docs-team
+ permission: pull
+ # You can include a list of repos for this team and only those repos would have this team
+ include:
+ - actions-demo
+ - name: ops-team
+ permission: push
+ # You can exclude a list of repos for this team and all repos except these repos would have this team
+ exclude:
+ - actions-demo
# Branch protection rules
# See https://docs.github.com/en/rest/branches/branch-protection?apiVersion=2026-03-10#update-branch-protection for available options
diff --git a/helm/safe-settings/README.md b/helm/safe-settings/README.md
index 97cd5b01b..d9c181d0e 100644
--- a/helm/safe-settings/README.md
+++ b/helm/safe-settings/README.md
@@ -37,6 +37,7 @@ A Helm chart for Kubernetes
| nodeSelector | object | `{}` | |
| podAnnotations | object | `{}` | |
| podSecurityContext | object | `{}` | |
+| priorityClassName | string | `""` | Priority class name for the pod. |
| replicaCount | int | `1` | |
| resources | object | `{}` | |
| securityContext.allowPrivilegeEscalation | bool | `false` | |
diff --git a/helm/safe-settings/templates/deployment.yaml b/helm/safe-settings/templates/deployment.yaml
index b30e7cace..541a2885d 100644
--- a/helm/safe-settings/templates/deployment.yaml
+++ b/helm/safe-settings/templates/deployment.yaml
@@ -78,3 +78,6 @@ spec:
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
+ {{- if .Values.priorityClassName }}
+ priorityClassName: {{ .Values.priorityClassName }}
+ {{- end }}
diff --git a/helm/safe-settings/values.yaml b/helm/safe-settings/values.yaml
index 52d32c815..320c0dd90 100644
--- a/helm/safe-settings/values.yaml
+++ b/helm/safe-settings/values.yaml
@@ -98,6 +98,8 @@ tolerations: []
affinity: {}
+priorityClassName: ""
+
deploymentConfig:
restrictedRepos:
exclude:
diff --git a/lib/settings.js b/lib/settings.js
index 919e01854..fd7ca2693 100644
--- a/lib/settings.js
+++ b/lib/settings.js
@@ -370,7 +370,7 @@ ${this.results.reduce((x, y) => {
const RepoPlugin = Settings.PLUGINS.repository
const archivePlugin = new Archive(this.nop, this.github, repo, repoConfig, this.log)
- const { shouldArchive, shouldUnarchive } = await archivePlugin.getState()
+ const { isArchived, shouldArchive, shouldUnarchive } = await archivePlugin.getState()
if (shouldUnarchive) {
this.log.debug(`Unarchiving repo ${repo.repo}`)
@@ -378,6 +378,11 @@ ${this.results.reduce((x, y) => {
this.appendToResults(unArchiveResults)
}
+ if (isArchived && !shouldUnarchive) {
+ this.log.debug(`Skipping repo/child plugin updates for archived repo ${repo.repo}`)
+ return
+ }
+
const repoResults = await new RepoPlugin(this.nop, this.github, repo, repoConfig, this.installation_id, this.log, this.errors).sync()
this.appendToResults(repoResults)
diff --git a/package-lock.json b/package-lock.json
index a5daebb9f..dd836a996 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -33,7 +33,7 @@
"eslint-plugin-promise": "^6.6.0",
"http-status-codes": "^2.2.0",
"jest": "^29.5.0",
- "jest-junit": "^16.0.0",
+ "jest-junit": "^17.0.0",
"jest-when": "^3.5.2",
"lockfile-lint": "^4.14.0",
"nock": "^14.0.1",
@@ -8873,19 +8873,19 @@
}
},
"node_modules/jest-junit": {
- "version": "16.0.0",
- "resolved": "https://registry.npmjs.org/jest-junit/-/jest-junit-16.0.0.tgz",
- "integrity": "sha512-A94mmw6NfJab4Fg/BlvVOUXzXgF0XIH6EmTgJ5NDPp4xoKq0Kr7sErb+4Xs9nZvu58pJojz5RFGpqnZYJTrRfQ==",
+ "version": "17.0.0",
+ "resolved": "https://registry.npmjs.org/jest-junit/-/jest-junit-17.0.0.tgz",
+ "integrity": "sha512-RYWCkq4j59gUXj5DsgbIE7xFBZzu1gtibPhyjSjMmGaOTLnqlXhg7x9zuGCwgbCuMAyoyvk0Mi8wSrRR5uOeLA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"mkdirp": "^1.0.4",
"strip-ansi": "^6.0.1",
- "uuid": "^8.3.2",
+ "uuid": "^14.0.0",
"xml": "^1.0.1"
},
"engines": {
- "node": ">=10.12.0"
+ "node": ">=20.0.0"
}
},
"node_modules/jest-leak-detector": {
@@ -9938,9 +9938,19 @@
"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==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz",
+ "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/puzrin"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/nodeca"
+ }
+ ],
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1"
@@ -11849,9 +11859,9 @@
"license": "MIT"
},
"node_modules/qs": {
- "version": "6.15.0",
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz",
- "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==",
+ "version": "6.15.2",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz",
+ "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==",
"license": "BSD-3-Clause",
"dependencies": {
"side-channel": "^1.1.0"
@@ -12515,9 +12525,9 @@
}
},
"node_modules/shell-quote": {
- "version": "1.8.3",
- "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz",
- "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==",
+ "version": "1.8.4",
+ "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.4.tgz",
+ "integrity": "sha512-VsC6n6vz1ihYYyZZwX7YZSF5l5x36ca17OC+a69h94YqB7X6XLwf+5MOgynYir2SLFUbl8gIYvBo8K8RoNQ6bQ==",
"dev": true,
"license": "MIT",
"engines": {
@@ -13650,13 +13660,17 @@
}
},
"node_modules/uuid": {
- "version": "8.3.2",
- "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
- "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.0.tgz",
+ "integrity": "sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg==",
"dev": true,
+ "funding": [
+ "https://github.com/sponsors/broofa",
+ "https://github.com/sponsors/ctavan"
+ ],
"license": "MIT",
"bin": {
- "uuid": "dist/bin/uuid"
+ "uuid": "dist-node/bin/uuid"
}
},
"node_modules/v8-to-istanbul": {
diff --git a/package.json b/package.json
index 1bf4f13e7..302f9f205 100644
--- a/package.json
+++ b/package.json
@@ -49,7 +49,7 @@
"eslint-plugin-promise": "^6.6.0",
"http-status-codes": "^2.2.0",
"jest": "^29.5.0",
- "jest-junit": "^16.0.0",
+ "jest-junit": "^17.0.0",
"jest-when": "^3.5.2",
"lockfile-lint": "^4.14.0",
"nock": "^14.0.1",
diff --git a/schema/dereferenced/repos.json b/schema/dereferenced/repos.json
index 9213456a0..15061706e 100644
--- a/schema/dereferenced/repos.json
+++ b/schema/dereferenced/repos.json
@@ -400,53 +400,76 @@
"type": "array",
"items": {
"description": "A team entry",
- "type": "object",
- "properties": {
- "name": {
- "type": "string",
- "description": "The name of the team."
- },
- "description": {
- "type": "string",
- "description": "The description of the team."
- },
- "maintainers": {
- "type": "array",
- "description": "List GitHub usernames for organization members who will become team maintainers.",
- "items": {
- "type": "string"
- }
- },
- "repo_names": {
- "type": "array",
- "description": "The full name (e.g., \"organization-name/repository-name\") of repositories to add the team to.",
- "items": {
- "type": "string"
- }
- },
- "privacy": {
- "type": "string",
- "description": "The level of privacy this team should have. The options are: \n**For a non-nested team:** \n * `secret` - only visible to organization owners and members of this team. \n * `closed` - visible to all members of this organization. \nDefault: `secret` \n**For a parent or child team:** \n * `closed` - visible to all members of this organization. \nDefault for child team: `closed`",
- "enum": [
- "secret",
- "closed"
- ]
- },
- "notification_setting": {
- "type": "string",
- "description": "The notification setting the team has chosen. The options are: \n * `notifications_enabled` - team members receive notifications when the team is @mentioned. \n * `notifications_disabled` - no one receives notifications. \nDefault: `notifications_enabled`",
- "enum": [
- "notifications_enabled",
- "notifications_disabled"
+ "allOf": [
+ {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "description": "The name of the team."
+ },
+ "description": {
+ "type": "string",
+ "description": "The description of the team."
+ },
+ "maintainers": {
+ "type": "array",
+ "description": "List GitHub usernames for organization members who will become team maintainers.",
+ "items": {
+ "type": "string"
+ }
+ },
+ "repo_names": {
+ "type": "array",
+ "description": "The full name (e.g., \"organization-name/repository-name\") of repositories to add the team to.",
+ "items": {
+ "type": "string"
+ }
+ },
+ "privacy": {
+ "type": "string",
+ "description": "The level of privacy this team should have. The options are: \n**For a non-nested team:** \n * `secret` - only visible to organization owners and members of this team. \n * `closed` - visible to all members of this organization. \nDefault: `secret` \n**For a parent or child team:** \n * `closed` - visible to all members of this organization. \nDefault for child team: `closed`",
+ "enum": [
+ "secret",
+ "closed"
+ ]
+ },
+ "notification_setting": {
+ "type": "string",
+ "description": "The notification setting the team has chosen. The options are: \n * `notifications_enabled` - team members receive notifications when the team is @mentioned. \n * `notifications_disabled` - no one receives notifications. \nDefault: `notifications_enabled`",
+ "enum": [
+ "notifications_enabled",
+ "notifications_disabled"
+ ]
+ },
+ "parent_team_id": {
+ "type": "integer",
+ "description": "The ID of a team to set as the parent team."
+ }
+ },
+ "required": [
+ "name"
]
},
- "parent_team_id": {
- "type": "integer",
- "description": "The ID of a team to set as the parent team."
+ {
+ "type": "object",
+ "properties": {
+ "exclude": {
+ "description": "You can exclude a list of repos for this team and all repos except these repos would have this team",
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "include": {
+ "description": "You can include a list of repos for this team and only those repos would have this team",
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ }
}
- },
- "required": [
- "name"
]
}
},
@@ -1214,53 +1237,76 @@
},
"TeamSettings": {
"description": "A team entry",
- "type": "object",
- "properties": {
- "name": {
- "type": "string",
- "description": "The name of the team."
- },
- "description": {
- "type": "string",
- "description": "The description of the team."
- },
- "maintainers": {
- "type": "array",
- "description": "List GitHub usernames for organization members who will become team maintainers.",
- "items": {
- "type": "string"
- }
- },
- "repo_names": {
- "type": "array",
- "description": "The full name (e.g., \"organization-name/repository-name\") of repositories to add the team to.",
- "items": {
- "type": "string"
- }
- },
- "privacy": {
- "type": "string",
- "description": "The level of privacy this team should have. The options are: \n**For a non-nested team:** \n * `secret` - only visible to organization owners and members of this team. \n * `closed` - visible to all members of this organization. \nDefault: `secret` \n**For a parent or child team:** \n * `closed` - visible to all members of this organization. \nDefault for child team: `closed`",
- "enum": [
- "secret",
- "closed"
- ]
- },
- "notification_setting": {
- "type": "string",
- "description": "The notification setting the team has chosen. The options are: \n * `notifications_enabled` - team members receive notifications when the team is @mentioned. \n * `notifications_disabled` - no one receives notifications. \nDefault: `notifications_enabled`",
- "enum": [
- "notifications_enabled",
- "notifications_disabled"
+ "allOf": [
+ {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "description": "The name of the team."
+ },
+ "description": {
+ "type": "string",
+ "description": "The description of the team."
+ },
+ "maintainers": {
+ "type": "array",
+ "description": "List GitHub usernames for organization members who will become team maintainers.",
+ "items": {
+ "type": "string"
+ }
+ },
+ "repo_names": {
+ "type": "array",
+ "description": "The full name (e.g., \"organization-name/repository-name\") of repositories to add the team to.",
+ "items": {
+ "type": "string"
+ }
+ },
+ "privacy": {
+ "type": "string",
+ "description": "The level of privacy this team should have. The options are: \n**For a non-nested team:** \n * `secret` - only visible to organization owners and members of this team. \n * `closed` - visible to all members of this organization. \nDefault: `secret` \n**For a parent or child team:** \n * `closed` - visible to all members of this organization. \nDefault for child team: `closed`",
+ "enum": [
+ "secret",
+ "closed"
+ ]
+ },
+ "notification_setting": {
+ "type": "string",
+ "description": "The notification setting the team has chosen. The options are: \n * `notifications_enabled` - team members receive notifications when the team is @mentioned. \n * `notifications_disabled` - no one receives notifications. \nDefault: `notifications_enabled`",
+ "enum": [
+ "notifications_enabled",
+ "notifications_disabled"
+ ]
+ },
+ "parent_team_id": {
+ "type": "integer",
+ "description": "The ID of a team to set as the parent team."
+ }
+ },
+ "required": [
+ "name"
]
},
- "parent_team_id": {
- "type": "integer",
- "description": "The ID of a team to set as the parent team."
+ {
+ "type": "object",
+ "properties": {
+ "exclude": {
+ "description": "You can exclude a list of repos for this team and all repos except these repos would have this team",
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "include": {
+ "description": "You can include a list of repos for this team and only those repos would have this team",
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ }
}
- },
- "required": [
- "name"
]
},
"MilestoneSettings": {
diff --git a/schema/dereferenced/settings.json b/schema/dereferenced/settings.json
index 8698b2012..b118f39f7 100644
--- a/schema/dereferenced/settings.json
+++ b/schema/dereferenced/settings.json
@@ -50,6 +50,17 @@
}
}
},
+ "code_security": {
+ "type": "object",
+ "description": "Use the `status` property to enable or disable GitHub Code Security for this repository.",
+ "description": "Use the `status` property to enable or disable GitHub Advanced Security for this repository.\nFor more information, see \"[About GitHub Advanced\nSecurity](/github/getting-started-with-github/learning-about-github/about-github-advanced-security).\"\n\nFor standalone Code Scanning or Secret Protection products, this parameter cannot be used.",
+ "properties": {
+ "status": {
+ "type": "string",
+ "description": "Can be `enabled` or `disabled`."
+ }
+ }
+ },
"code_security": {
"type": "object",
"description": "Use the `status` property to enable or disable GitHub Code Security for this repository.",
@@ -90,6 +101,16 @@
}
}
},
+ "secret_scanning_ai_detection": {
+ "type": "object",
+ "description": "Use the `status` property to enable or disable secret scanning AI detection for this repository. For more information, see \"[Responsible detection of generic secrets with AI](https://docs.github.com/code-security/secret-scanning/using-advanced-secret-scanning-and-push-protection-features/generic-secret-detection/responsible-ai-generic-secrets).\"",
+ "properties": {
+ "status": {
+ "type": "string",
+ "description": "Can be `enabled` or `disabled`."
+ }
+ }
+ },
"secret_scanning_non_provider_patterns": {
"type": "object",
"description": "Use the `status` property to enable or disable secret scanning non-provider patterns for this repository. For more information, see \"[Supported secret scanning patterns](/code-security/secret-scanning/introduction/supported-secret-scanning-patterns#supported-secrets).\"",
@@ -400,62 +421,85 @@
"type": "array",
"items": {
"description": "A team entry",
- "type": "object",
- "properties": {
- "name": {
- "type": "string",
- "description": "The name of the team."
- },
- "description": {
- "type": "string",
- "description": "The description of the team."
- },
- "maintainers": {
- "type": "array",
- "description": "List GitHub usernames for organization members who will become team maintainers.",
- "items": {
- "type": "string"
- }
- },
- "repo_names": {
- "type": "array",
- "description": "The full name (e.g., \"organization-name/repository-name\") of repositories to add the team to.",
- "items": {
- "type": "string"
- }
- },
- "privacy": {
- "type": "string",
- "description": "The level of privacy this team should have. The options are: \n**For a non-nested team:** \n * `secret` - only visible to organization owners and members of this team. \n * `closed` - visible to all members of this organization. \nDefault: `secret` \n**For a parent or child team:** \n * `closed` - visible to all members of this organization. \nDefault for child team: `closed`",
- "enum": [
- "secret",
- "closed"
- ]
- },
- "notification_setting": {
- "type": "string",
- "description": "The notification setting the team has chosen. The options are: \n * `notifications_enabled` - team members receive notifications when the team is @mentioned. \n * `notifications_disabled` - no one receives notifications. \nDefault: `notifications_enabled`",
- "enum": [
- "notifications_enabled",
- "notifications_disabled"
+ "allOf": [
+ {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "description": "The name of the team."
+ },
+ "description": {
+ "type": "string",
+ "description": "The description of the team."
+ },
+ "maintainers": {
+ "type": "array",
+ "description": "List GitHub usernames for organization members who will become team maintainers.",
+ "items": {
+ "type": "string"
+ }
+ },
+ "repo_names": {
+ "type": "array",
+ "description": "The full name (e.g., \"organization-name/repository-name\") of repositories to add the team to.",
+ "items": {
+ "type": "string"
+ }
+ },
+ "privacy": {
+ "type": "string",
+ "description": "The level of privacy this team should have. The options are: \n**For a non-nested team:** \n * `secret` - only visible to organization owners and members of this team. \n * `closed` - visible to all members of this organization. \nDefault: `secret` \n**For a parent or child team:** \n * `closed` - visible to all members of this organization. \nDefault for child team: `closed`",
+ "enum": [
+ "secret",
+ "closed"
+ ]
+ },
+ "notification_setting": {
+ "type": "string",
+ "description": "The notification setting the team has chosen. The options are: \n * `notifications_enabled` - team members receive notifications when the team is @mentioned. \n * `notifications_disabled` - no one receives notifications. \nDefault: `notifications_enabled`",
+ "enum": [
+ "notifications_enabled",
+ "notifications_disabled"
+ ]
+ },
+ "permission": {
+ "type": "string",
+ "description": "**Closing down notice**. The permission that new repositories will be added to the team with when none is specified.",
+ "enum": [
+ "pull",
+ "push"
+ ],
+ "default": "pull"
+ },
+ "parent_team_id": {
+ "type": "integer",
+ "description": "The ID of a team to set as the parent team."
+ }
+ },
+ "required": [
+ "name"
]
},
- "permission": {
- "type": "string",
- "description": "**Closing down notice**. The permission that new repositories will be added to the team with when none is specified.",
- "enum": [
- "pull",
- "push"
- ],
- "default": "pull"
- },
- "parent_team_id": {
- "type": "integer",
- "description": "The ID of a team to set as the parent team."
+ {
+ "type": "object",
+ "properties": {
+ "exclude": {
+ "description": "You can exclude a list of repos for this team and all repos except these repos would have this team",
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "include": {
+ "description": "You can include a list of repos for this team and only those repos would have this team",
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ }
}
- },
- "required": [
- "name"
]
}
},
@@ -2371,53 +2415,76 @@
},
"TeamSettings": {
"description": "A team entry",
- "type": "object",
- "properties": {
- "name": {
- "type": "string",
- "description": "The name of the team."
- },
- "description": {
- "type": "string",
- "description": "The description of the team."
- },
- "maintainers": {
- "type": "array",
- "description": "List GitHub usernames for organization members who will become team maintainers.",
- "items": {
- "type": "string"
- }
- },
- "repo_names": {
- "type": "array",
- "description": "The full name (e.g., \"organization-name/repository-name\") of repositories to add the team to.",
- "items": {
- "type": "string"
- }
- },
- "privacy": {
- "type": "string",
- "description": "The level of privacy this team should have. The options are: \n**For a non-nested team:** \n * `secret` - only visible to organization owners and members of this team. \n * `closed` - visible to all members of this organization. \nDefault: `secret` \n**For a parent or child team:** \n * `closed` - visible to all members of this organization. \nDefault for child team: `closed`",
- "enum": [
- "secret",
- "closed"
- ]
- },
- "notification_setting": {
- "type": "string",
- "description": "The notification setting the team has chosen. The options are: \n * `notifications_enabled` - team members receive notifications when the team is @mentioned. \n * `notifications_disabled` - no one receives notifications. \nDefault: `notifications_enabled`",
- "enum": [
- "notifications_enabled",
- "notifications_disabled"
+ "allOf": [
+ {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "description": "The name of the team."
+ },
+ "description": {
+ "type": "string",
+ "description": "The description of the team."
+ },
+ "maintainers": {
+ "type": "array",
+ "description": "List GitHub usernames for organization members who will become team maintainers.",
+ "items": {
+ "type": "string"
+ }
+ },
+ "repo_names": {
+ "type": "array",
+ "description": "The full name (e.g., \"organization-name/repository-name\") of repositories to add the team to.",
+ "items": {
+ "type": "string"
+ }
+ },
+ "privacy": {
+ "type": "string",
+ "description": "The level of privacy this team should have. The options are: \n**For a non-nested team:** \n * `secret` - only visible to organization owners and members of this team. \n * `closed` - visible to all members of this organization. \nDefault: `secret` \n**For a parent or child team:** \n * `closed` - visible to all members of this organization. \nDefault for child team: `closed`",
+ "enum": [
+ "secret",
+ "closed"
+ ]
+ },
+ "notification_setting": {
+ "type": "string",
+ "description": "The notification setting the team has chosen. The options are: \n * `notifications_enabled` - team members receive notifications when the team is @mentioned. \n * `notifications_disabled` - no one receives notifications. \nDefault: `notifications_enabled`",
+ "enum": [
+ "notifications_enabled",
+ "notifications_disabled"
+ ]
+ },
+ "parent_team_id": {
+ "type": "integer",
+ "description": "The ID of a team to set as the parent team."
+ }
+ },
+ "required": [
+ "name"
]
},
- "parent_team_id": {
- "type": "integer",
- "description": "The ID of a team to set as the parent team."
+ {
+ "type": "object",
+ "properties": {
+ "exclude": {
+ "description": "You can exclude a list of repos for this team and all repos except these repos would have this team",
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "include": {
+ "description": "You can include a list of repos for this team and only those repos would have this team",
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ }
}
- },
- "required": [
- "name"
]
},
"MilestoneSettings": {
diff --git a/schema/dereferenced/suborgs.json b/schema/dereferenced/suborgs.json
index 0267bf7a8..5824a4879 100644
--- a/schema/dereferenced/suborgs.json
+++ b/schema/dereferenced/suborgs.json
@@ -434,53 +434,76 @@
"type": "array",
"items": {
"description": "A team entry",
- "type": "object",
- "properties": {
- "name": {
- "type": "string",
- "description": "The name of the team."
- },
- "description": {
- "type": "string",
- "description": "The description of the team."
- },
- "maintainers": {
- "type": "array",
- "description": "List GitHub usernames for organization members who will become team maintainers.",
- "items": {
- "type": "string"
- }
- },
- "repo_names": {
- "type": "array",
- "description": "The full name (e.g., \"organization-name/repository-name\") of repositories to add the team to.",
- "items": {
- "type": "string"
- }
- },
- "privacy": {
- "type": "string",
- "description": "The level of privacy this team should have. The options are: \n**For a non-nested team:** \n * `secret` - only visible to organization owners and members of this team. \n * `closed` - visible to all members of this organization. \nDefault: `secret` \n**For a parent or child team:** \n * `closed` - visible to all members of this organization. \nDefault for child team: `closed`",
- "enum": [
- "secret",
- "closed"
- ]
- },
- "notification_setting": {
- "type": "string",
- "description": "The notification setting the team has chosen. The options are: \n * `notifications_enabled` - team members receive notifications when the team is @mentioned. \n * `notifications_disabled` - no one receives notifications. \nDefault: `notifications_enabled`",
- "enum": [
- "notifications_enabled",
- "notifications_disabled"
+ "allOf": [
+ {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "description": "The name of the team."
+ },
+ "description": {
+ "type": "string",
+ "description": "The description of the team."
+ },
+ "maintainers": {
+ "type": "array",
+ "description": "List GitHub usernames for organization members who will become team maintainers.",
+ "items": {
+ "type": "string"
+ }
+ },
+ "repo_names": {
+ "type": "array",
+ "description": "The full name (e.g., \"organization-name/repository-name\") of repositories to add the team to.",
+ "items": {
+ "type": "string"
+ }
+ },
+ "privacy": {
+ "type": "string",
+ "description": "The level of privacy this team should have. The options are: \n**For a non-nested team:** \n * `secret` - only visible to organization owners and members of this team. \n * `closed` - visible to all members of this organization. \nDefault: `secret` \n**For a parent or child team:** \n * `closed` - visible to all members of this organization. \nDefault for child team: `closed`",
+ "enum": [
+ "secret",
+ "closed"
+ ]
+ },
+ "notification_setting": {
+ "type": "string",
+ "description": "The notification setting the team has chosen. The options are: \n * `notifications_enabled` - team members receive notifications when the team is @mentioned. \n * `notifications_disabled` - no one receives notifications. \nDefault: `notifications_enabled`",
+ "enum": [
+ "notifications_enabled",
+ "notifications_disabled"
+ ]
+ },
+ "parent_team_id": {
+ "type": "integer",
+ "description": "The ID of a team to set as the parent team."
+ }
+ },
+ "required": [
+ "name"
]
},
- "parent_team_id": {
- "type": "integer",
- "description": "The ID of a team to set as the parent team."
+ {
+ "type": "object",
+ "properties": {
+ "exclude": {
+ "description": "You can exclude a list of repos for this team and all repos except these repos would have this team",
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "include": {
+ "description": "You can include a list of repos for this team and only those repos would have this team",
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ }
}
- },
- "required": [
- "name"
]
}
},
@@ -1248,53 +1271,76 @@
},
"TeamSettings": {
"description": "A team entry",
- "type": "object",
- "properties": {
- "name": {
- "type": "string",
- "description": "The name of the team."
- },
- "description": {
- "type": "string",
- "description": "The description of the team."
- },
- "maintainers": {
- "type": "array",
- "description": "List GitHub usernames for organization members who will become team maintainers.",
- "items": {
- "type": "string"
- }
- },
- "repo_names": {
- "type": "array",
- "description": "The full name (e.g., \"organization-name/repository-name\") of repositories to add the team to.",
- "items": {
- "type": "string"
- }
- },
- "privacy": {
- "type": "string",
- "description": "The level of privacy this team should have. The options are: \n**For a non-nested team:** \n * `secret` - only visible to organization owners and members of this team. \n * `closed` - visible to all members of this organization. \nDefault: `secret` \n**For a parent or child team:** \n * `closed` - visible to all members of this organization. \nDefault for child team: `closed`",
- "enum": [
- "secret",
- "closed"
- ]
- },
- "notification_setting": {
- "type": "string",
- "description": "The notification setting the team has chosen. The options are: \n * `notifications_enabled` - team members receive notifications when the team is @mentioned. \n * `notifications_disabled` - no one receives notifications. \nDefault: `notifications_enabled`",
- "enum": [
- "notifications_enabled",
- "notifications_disabled"
+ "allOf": [
+ {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "description": "The name of the team."
+ },
+ "description": {
+ "type": "string",
+ "description": "The description of the team."
+ },
+ "maintainers": {
+ "type": "array",
+ "description": "List GitHub usernames for organization members who will become team maintainers.",
+ "items": {
+ "type": "string"
+ }
+ },
+ "repo_names": {
+ "type": "array",
+ "description": "The full name (e.g., \"organization-name/repository-name\") of repositories to add the team to.",
+ "items": {
+ "type": "string"
+ }
+ },
+ "privacy": {
+ "type": "string",
+ "description": "The level of privacy this team should have. The options are: \n**For a non-nested team:** \n * `secret` - only visible to organization owners and members of this team. \n * `closed` - visible to all members of this organization. \nDefault: `secret` \n**For a parent or child team:** \n * `closed` - visible to all members of this organization. \nDefault for child team: `closed`",
+ "enum": [
+ "secret",
+ "closed"
+ ]
+ },
+ "notification_setting": {
+ "type": "string",
+ "description": "The notification setting the team has chosen. The options are: \n * `notifications_enabled` - team members receive notifications when the team is @mentioned. \n * `notifications_disabled` - no one receives notifications. \nDefault: `notifications_enabled`",
+ "enum": [
+ "notifications_enabled",
+ "notifications_disabled"
+ ]
+ },
+ "parent_team_id": {
+ "type": "integer",
+ "description": "The ID of a team to set as the parent team."
+ }
+ },
+ "required": [
+ "name"
]
},
- "parent_team_id": {
- "type": "integer",
- "description": "The ID of a team to set as the parent team."
+ {
+ "type": "object",
+ "properties": {
+ "exclude": {
+ "description": "You can exclude a list of repos for this team and all repos except these repos would have this team",
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "include": {
+ "description": "You can include a list of repos for this team and only those repos would have this team",
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ }
}
- },
- "required": [
- "name"
]
},
"MilestoneSettings": {
diff --git a/schema/repos.json b/schema/repos.json
index 3a7c51301..2d7fa869d 100644
--- a/schema/repos.json
+++ b/schema/repos.json
@@ -190,7 +190,30 @@
},
"TeamSettings": {
"description": "A team entry",
- "$ref": "https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/api.github.com/api.github.com.2026-03-10.json#/paths/~1orgs~1{org}~1teams/post/requestBody/content/application~1json/schema"
+ "allOf": [
+ {
+ "$ref": "https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/api.github.com/api.github.com.2026-03-10.json#/paths/~1orgs~1{org}~1teams/post/requestBody/content/application~1json/schema"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "exclude": {
+ "description": "You can exclude a list of repos for this team and all repos except these repos would have this team",
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "include": {
+ "description": "You can include a list of repos for this team and only those repos would have this team",
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ ]
},
"MilestoneSettings": {
"description": "A milestone entry",
diff --git a/schema/settings.json b/schema/settings.json
index 59d662d50..11d749127 100644
--- a/schema/settings.json
+++ b/schema/settings.json
@@ -197,7 +197,30 @@
},
"TeamSettings": {
"description": "A team entry",
- "$ref": "https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/api.github.com/api.github.com.2026-03-10.json#/paths/~1orgs~1{org}~1teams/post/requestBody/content/application~1json/schema"
+ "allOf": [
+ {
+ "$ref": "https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/api.github.com/api.github.com.2026-03-10.json#/paths/~1orgs~1{org}~1teams/post/requestBody/content/application~1json/schema"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "exclude": {
+ "description": "You can exclude a list of repos for this team and all repos except these repos would have this team",
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "include": {
+ "description": "You can include a list of repos for this team and only those repos would have this team",
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ ]
},
"MilestoneSettings": {
"description": "A milestone entry",
diff --git a/schema/suborgs.json b/schema/suborgs.json
index 3a3c79def..23a408d1b 100644
--- a/schema/suborgs.json
+++ b/schema/suborgs.json
@@ -224,7 +224,30 @@
},
"TeamSettings": {
"description": "A team entry",
- "$ref": "https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/api.github.com/api.github.com.2026-03-10.json#/paths/~1orgs~1{org}~1teams/post/requestBody/content/application~1json/schema"
+ "allOf": [
+ {
+ "$ref": "https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/api.github.com/api.github.com.2026-03-10.json#/paths/~1orgs~1{org}~1teams/post/requestBody/content/application~1json/schema"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "exclude": {
+ "description": "You can exclude a list of repos for this team and all repos except these repos would have this team",
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "include": {
+ "description": "You can include a list of repos for this team and only those repos would have this team",
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ ]
},
"MilestoneSettings": {
"description": "A milestone entry",
diff --git a/test/unit/lib/plugins/archive.test.js b/test/unit/lib/plugins/archive.test.js
index 0ed0f38d1..a60eafac4 100644
--- a/test/unit/lib/plugins/archive.test.js
+++ b/test/unit/lib/plugins/archive.test.js
@@ -93,6 +93,56 @@ describe('Archive Plugin', () => {
})
})
+ describe('getState', () => {
+ it('getState when repo is already archived and desired state is not set returns isArchived true shouldArchive false shouldUnarchive false', async () => {
+ // Arrange
+ github.rest.repos.get.mockResolvedValue({ data: { archived: true } })
+ archive = new Archive(false, github, repo, {}, log)
+
+ // Act
+ const result = await archive.getState()
+
+ // Assert
+ expect(result).toEqual({
+ isArchived: true,
+ shouldArchive: false,
+ shouldUnarchive: false
+ })
+ })
+
+ it('getState when repo is not archived and desired state is not set returns isArchived false shouldArchive false shouldUnarchive false', async () => {
+ // Arrange
+ github.rest.repos.get.mockResolvedValue({ data: { archived: false } })
+ archive = new Archive(false, github, repo, {}, log)
+
+ // Act
+ const result = await archive.getState()
+
+ // Assert
+ expect(result).toEqual({
+ isArchived: false,
+ shouldArchive: false,
+ shouldUnarchive: false
+ })
+ })
+
+ it('getState when repo is archived and desired state is false returns isArchived true shouldArchive false shouldUnarchive true', async () => {
+ // Arrange
+ github.rest.repos.get.mockResolvedValue({ data: { archived: true } })
+ archive = new Archive(false, github, repo, { archived: false }, log)
+
+ // Act
+ const result = await archive.getState()
+
+ // Assert
+ expect(result).toEqual({
+ isArchived: true,
+ shouldArchive: false,
+ shouldUnarchive: true
+ })
+ })
+ })
+
describe('sync', () => {
beforeEach(() => {
archive = new Archive(false, github, repo, settings, log)
diff --git a/test/unit/lib/plugins/teams.test.js b/test/unit/lib/plugins/teams.test.js
index de16965a6..1ae9b569c 100644
--- a/test/unit/lib/plugins/teams.test.js
+++ b/test/unit/lib/plugins/teams.test.js
@@ -103,4 +103,51 @@ describe('Teams', () => {
)
}
})
+
+ describe('filtering teams by include/exclude', () => {
+ beforeEach(() => {
+ github.rest.repos.listTeams.mockResolvedValue({ data: [] })
+ })
+
+ it('does not add a team when the repo matches an exclude glob', async () => {
+ const plugin = configure([
+ { name: addedTeamName, permission: 'pull', exclude: ['test*'] }
+ ])
+
+ await plugin.sync()
+
+ expect(github.rest.teams.addOrUpdateRepoPermissionsInOrg).not.toHaveBeenCalled()
+ })
+
+ it('does not add a team when the repo is not in an include glob', async () => {
+ const plugin = configure([
+ { name: addedTeamName, permission: 'pull', include: ['other-*'] }
+ ])
+
+ await plugin.sync()
+
+ expect(github.rest.teams.addOrUpdateRepoPermissionsInOrg).not.toHaveBeenCalled()
+ })
+
+ it('adds a team when the repo matches an include glob', async () => {
+ when(github.rest.teams.getByName)
+ .calledWith({ org, team_slug: addedTeamName })
+ .mockResolvedValue({ data: { id: addedTeamId } })
+
+ const plugin = configure([
+ { name: addedTeamName, permission: 'pull', include: ['test*'] }
+ ])
+
+ await plugin.sync()
+
+ expect(github.rest.teams.addOrUpdateRepoPermissionsInOrg).toHaveBeenCalledWith({
+ org,
+ team_id: addedTeamId,
+ team_slug: addedTeamName,
+ owner: org,
+ repo: 'test',
+ permission: 'pull'
+ })
+ })
+ })
})
diff --git a/test/unit/lib/settings.test.js b/test/unit/lib/settings.test.js
index b36106518..e102379a6 100644
--- a/test/unit/lib/settings.test.js
+++ b/test/unit/lib/settings.test.js
@@ -462,4 +462,91 @@ repository:
);
});
});
+
+ describe('updateRepos - archived repo skipping', () => {
+ const Archive = require('../../../lib/plugins/archive')
+
+ let settings
+ let mockRepoSync
+ let originalRepoPlugin
+
+ beforeEach(() => {
+ // Preserve the original RepoPlugin so it can be restored after each test
+ originalRepoPlugin = Settings.PLUGINS.repository
+
+ // Replace RepoPlugin with a mock constructor whose sync() we can assert on
+ mockRepoSync = jest.fn().mockResolvedValue([])
+ Settings.PLUGINS.repository = jest.fn().mockImplementation(() => ({
+ sync: mockRepoSync
+ }))
+
+ // Build a Settings instance that will enter the `if (repoConfig)` branch:
+ // config.repository must be defined so repoConfig is truthy
+ settings = new Settings(
+ false,
+ stubContext,
+ { owner: 'test-org', repo: 'test-repo' },
+ { repository: { name: 'test-repo' } },
+ 'main'
+ )
+
+ // Pre-set subOrgConfigs so updateRepos() does not call the async getSubOrgConfigs()
+ settings.subOrgConfigs = {}
+
+ // Pre-set repoConfigs so getRepoOverrideConfig() does not throw on undefined
+ settings.repoConfigs = {}
+ })
+
+ afterEach(() => {
+ // Restore the real RepoPlugin and all prototype spies
+ Settings.PLUGINS.repository = originalRepoPlugin
+ jest.restoreAllMocks()
+ })
+
+ it('updateRepos when repo is already archived and not being unarchived does not call RepoPlugin sync', async () => {
+ // Arrange
+ jest.spyOn(Archive.prototype, 'getState').mockResolvedValue({
+ isArchived: true,
+ shouldArchive: false,
+ shouldUnarchive: false
+ })
+
+ // Act
+ await settings.updateRepos({ owner: 'test-org', repo: 'test-repo' })
+
+ // Assert
+ expect(mockRepoSync).not.toHaveBeenCalled()
+ })
+
+ it('updateRepos when repo is archived but is being unarchived calls RepoPlugin sync', async () => {
+ // Arrange
+ jest.spyOn(Archive.prototype, 'getState').mockResolvedValue({
+ isArchived: true,
+ shouldArchive: false,
+ shouldUnarchive: true
+ })
+ jest.spyOn(Archive.prototype, 'sync').mockResolvedValue([])
+
+ // Act
+ await settings.updateRepos({ owner: 'test-org', repo: 'test-repo' })
+
+ // Assert
+ expect(mockRepoSync).toHaveBeenCalledTimes(1)
+ })
+
+ it('updateRepos when repo is not archived calls RepoPlugin sync', async () => {
+ // Arrange
+ jest.spyOn(Archive.prototype, 'getState').mockResolvedValue({
+ isArchived: false,
+ shouldArchive: false,
+ shouldUnarchive: false
+ })
+
+ // Act
+ await settings.updateRepos({ owner: 'test-org', repo: 'test-repo' })
+
+ // Assert
+ expect(mockRepoSync).toHaveBeenCalledTimes(1)
+ })
+ }) // updateRepos - archived repo skipping
}) // Settings Tests