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