1
0
mirror of synced 2025-12-19 18:10:59 -05:00

Merge pull request #39453 from github/repo-sync

Repo sync
This commit is contained in:
docs-bot
2025-07-22 11:37:06 -07:00
committed by GitHub
21 changed files with 427 additions and 39 deletions

View File

@@ -9,7 +9,6 @@ versions:
children:
- /viewing-github-actions-metrics
- /making-retired-namespaces-available-on-ghecom
- /managing-custom-actions
redirect_from:
- /actions/administering-github-actions
---

View File

@@ -1,14 +1,14 @@
---
title: Developing a third party CLI action
shortTitle: CLI setup action
title: Creating a third party CLI action
shortTitle: Create a CLI action
intro: 'Learn how to develop an action to set up a CLI on {% data variables.product.prodname_actions %} runners.'
redirect_from:
- /actions/creating-actions/developing-a-third-party-cli-action
- /actions/sharing-automations/creating-actions/developing-a-third-party-cli-action
- /actions/how-tos/sharing-automations/creating-actions/developing-a-third-party-cli-action
versions:
fpt: '*'
ghec: '*'
type: tutorial
topics:
- Actions
---
@@ -29,7 +29,7 @@ This article will demonstrate how to write an action that retrieves a specific v
## Prerequisites
You should have an understanding of how to write a custom action. For more information, see [AUTOTITLE](/actions/creating-actions/about-custom-actions). For a more detailed guide on how to write a custom action, see [AUTOTITLE](/actions/creating-actions/creating-a-javascript-action).
You should have an understanding of how to write a custom action. For more information, see [AUTOTITLE](/actions/how-tos/creating-and-publishing-actions/managing-custom-actions).
## Example

View File

@@ -1,18 +1,18 @@
---
title: Creating actions
shortTitle: Create actions
title: Creating and publishing actions
shortTitle: Create and publish actions
intro: 'You can create your own actions, use and customize actions shared by the {% data variables.product.prodname_dotcom %} community, or write and share the actions you build.'
versions:
fpt: '*'
ghes: '*'
ghec: '*'
children:
- /setting-exit-codes-for-actions
- /releasing-and-maintaining-actions
- /publishing-actions-in-github-marketplace
- /developing-a-third-party-cli-action
redirect_from:
- /actions/sharing-automations/creating-actions
- /actions/how-tos/sharing-automations/creating-actions
children:
- /managing-custom-actions
- /creating-a-third-party-cli-action
- /setting-exit-codes-for-actions
- /publishing-actions-in-github-marketplace
- /releasing-and-maintaining-actions
---
{% data reusables.actions.enterprise-github-hosted-runners %}

View File

@@ -1,5 +1,6 @@
---
title: Managing custom actions
shortTitle: Manage custom actions
intro: 'Learn how to create and manage your own actions, and customize actions shared by the {% data variables.product.prodname_dotcom %} community.'
versions:
fpt: '*'
@@ -9,6 +10,8 @@ type: overview
topics:
- Action development
- Fundamentals
redirect_from:
- /actions/how-tos/administering-github-actions/managing-custom-actions
---
## Choosing a location for your action

View File

@@ -7,16 +7,17 @@ redirect_from:
- /actions/building-actions/publishing-actions-in-github-marketplace
- /actions/creating-actions/publishing-actions-in-github-marketplace
- /actions/sharing-automations/creating-actions/publishing-actions-in-github-marketplace
- /actions/how-tos/sharing-automations/creating-actions/publishing-actions-in-github-marketplace
versions:
fpt: '*'
ghec: '*'
type: how_to
shortTitle: Publish in GitHub Marketplace
---
You must accept the terms of service to publish actions in {% data variables.product.prodname_marketplace %}.
## Prerequisites
## About publishing actions
>[!NOTE]
> You must accept the terms of service to publish actions in {% data variables.product.prodname_marketplace %}.
Before you can publish an action, you'll need to create an action in your repository. For more information, see [AUTOTITLE](/actions/creating-actions).

View File

@@ -2,7 +2,6 @@
title: Releasing and maintaining actions
shortTitle: Release and maintain actions
intro: You can leverage automation and open source best practices to release and maintain actions.
type: tutorial
topics:
- Action development
- Actions
@@ -14,6 +13,7 @@ versions:
redirect_from:
- /actions/creating-actions/releasing-and-maintaining-actions
- /actions/sharing-automations/creating-actions/releasing-and-maintaining-actions
- /actions/how-tos/sharing-automations/creating-actions/releasing-and-maintaining-actions
---
{% data reusables.actions.enterprise-github-hosted-runners %}

View File

@@ -6,11 +6,11 @@ redirect_from:
- /actions/building-actions/setting-exit-codes-for-actions
- /actions/creating-actions/setting-exit-codes-for-actions
- /actions/sharing-automations/creating-actions/setting-exit-codes-for-actions
- /actions/how-tos/sharing-automations/creating-actions/setting-exit-codes-for-actions
versions:
fpt: '*'
ghes: '*'
ghec: '*'
type: how_to
---
{% data reusables.actions.enterprise-github-hosted-runners %}

View File

@@ -9,6 +9,7 @@ versions:
children:
- /writing-workflows
- /managing-workflow-runs-and-deployments
- /creating-and-publishing-actions
- /sharing-automations
- /using-github-hosted-runners
- /managing-self-hosted-runners

View File

@@ -14,7 +14,6 @@ redirect_from:
- /articles/creating-a-github-action
- /actions/sharing-automations
children:
- /creating-actions
- /reuse-workflows
- /creating-workflow-templates-for-your-organization
- /sharing-actions-and-workflows-from-your-private-repository

View File

@@ -29,6 +29,9 @@ You must authorize your {% data variables.product.pat_v1 %} after creation befor
1. In the dropdown menu, to the right of the organization you'd like to authorize the token for, click **Authorize**.
> [!NOTE]
> When authorizing a {% data variables.product.pat_v1 %} for use within an organization that belongs to an enterprise which has both an IP allow list and single sign-on enabled at the enterprise level, your IP must also be allowed at the enterprise level. See [AUTOTITLE](/admin/configuring-settings/hardening-security-for-your-enterprise/restricting-network-traffic-to-your-enterprise-with-an-ip-allow-list).
## Further reading
* [AUTOTITLE](/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token)

View File

@@ -37,6 +37,9 @@ You do not need to authorize SSH certificates signed by your organization's SSH
![Screenshot of the "Authentication Keys" section. Next to a key, a dropdown menu, labeled "Configure SSO," is outlined in orange.](/assets/images/help/settings/ssh-sso-button.png)
1. In the dropdown menu, to the right of the organization you'd like to authorize the SSH key for, click **Authorize**.
> [!NOTE]
> When authorizing an SSH key for use within an organization that belongs to an enterprise which has both an IP allow list and single sign-on enabled at the enterprise level, your IP must also be allowed at the enterprise level. See [AUTOTITLE](/admin/configuring-settings/hardening-security-for-your-enterprise/restricting-network-traffic-to-your-enterprise-with-an-ip-allow-list).
## Further reading
* [AUTOTITLE](/authentication/connecting-to-github-with-ssh/checking-for-existing-ssh-keys)

View File

@@ -33,7 +33,13 @@ For specific ecosystems, you can configure {% data variables.product.prodname_de
## Configuring private registries
You configure {% data variables.product.prodname_dependabot %}'s access to private registries in the `dependabot.yml` file.
{% ifversion org-private-registry %}
You can configure {% data variables.product.prodname_dependabot %}'s access to private registries at the org-level. For more information on how to configure that, see [AUTOTITLE](/code-security/securing-your-organization/enabling-security-features-in-your-organization/giving-org-access-private-registries).
{% endif %}
You can also configure {% data variables.product.prodname_dependabot %}'s access to private registries in the `dependabot.yml` file.
The top-level `registries` key is optional and specifies authentication details.
{% data reusables.dependabot.dependabot-updates-registries %}

View File

@@ -59,7 +59,7 @@ Any private registries used by the build must also be accessible to the workflow
## {% data variables.product.prodname_dependabot %} updates access to private registries
{% data variables.product.prodname_dependabot %} uses any private registries defined in the `dependabot.yml` file. It does not have access to the organization-level private registries used by {% data variables.product.prodname_code_scanning %} default setup.
{% data variables.product.prodname_dependabot %} can use any of the org-level private registries, as well as uses any private registries defined in the `dependabot.yml` file in the repo.
{% data variables.product.prodname_dependabot %} cannot check for security or version updates for code stored in a private registry unless it can access the registry. If you do not configure access to the private registry, then {% data variables.product.prodname_dependabot %} cannot raise pull requests to update any of the dependencies stored in the registry.

View File

@@ -50,7 +50,7 @@ Content exclusions also apply to {% data variables.copilot.copilot_code-review_s
{% data reusables.copilot.content-exclusion-limitations %}
Currently, content exclusions do not apply to symbolic links (symlinks).
Currently, content exclusions do not apply to symbolic links (symlinks) and repositories located on remote filesystems.
### Data sent to {% data variables.product.prodname_dotcom %}

View File

@@ -49,4 +49,6 @@ Used for:
{% data variables.product.prodname_copilot %} uses {% data variables.copilot.copilot_gemini_flash %} and {% data variables.copilot.copilot_gemini_25_pro %} hosted on Google Cloud Platform (GCP). When using {% data variables.copilot.copilot_gemini %} models, prompts and metadata are sent to GCP, which makes the [following data commitment](https://cloud.google.com/vertex-ai/generative-ai/docs/data-governance): _{% data variables.copilot.copilot_gemini %} doesn't use your prompts, or its responses, as data to train its models._
To provide better service quality and reduce latency, {% data variables.product.github %} uses [prompt caching](https://cloud.google.com/vertex-ai/generative-ai/docs/data-governance#customer_data_retention_and_achieving_zero_data_retention).
When using {% data variables.copilot.copilot_gemini %} models, input prompts and output completions continue to run through {% data variables.product.prodname_copilot %}'s content filters for public code matching, when applied, along with those for harmful or offensive content.

View File

@@ -1 +1,2 @@
>[!NOTE] Repository policies are currently in {% data variables.release-phases.public_preview %} and subject to change.
>You can have up to 75 total policies and rulesets per organization, and up to 75 total policies and rulesets per enterprise.

View File

@@ -1,2 +1,2 @@
> [!NOTE]
> {% ifversion ghes > 3.16 %}Only changes made to a ruleset after you have upgraded to {% data variables.product.prodname_ghe_server %} 3.17.0, or a later version, are included in the ruleset history.{% endif %}
> {% ifversion ghes > 3.16 %}[!NOTE]
> Only changes made to a ruleset after you have upgraded to {% data variables.product.prodname_ghe_server %} 3.17.0, or a later version, are included in the ruleset history.{% endif %}

View File

@@ -2,7 +2,8 @@ If your support request is outside of the scope of what our team can help you wi
* Third party integrations, such as Jira{% ifversion ghes %}
* Hardware setup
* Hypervisor-related issues, such as OS disk detection, network setup, boot failure, VM console access, etc{% endif %}
* Hypervisor-related issues, such as OS disk detection, network setup, boot failure, VM console access, etc.
* Migration assistance between specific hardware platforms or storage providers{% endif %}
* CI/CD, such as Jenkins
* Azure DevOps (please contact Azure Support)
* Writing scripts

42
package-lock.json generated
View File

@@ -4783,19 +4783,44 @@
}
},
"node_modules/@types/request/node_modules/form-data": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz",
"integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==",
"version": "2.5.5",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.5.tgz",
"integrity": "sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A==",
"dev": true,
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.6",
"mime-types": "^2.1.12"
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
"hasown": "^2.0.2",
"mime-types": "^2.1.35",
"safe-buffer": "^5.2.1"
},
"engines": {
"node": ">= 0.12"
}
},
"node_modules/@types/request/node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/@types/semver": {
"version": "7.5.8",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz",
@@ -8957,14 +8982,15 @@
}
},
"node_modules/form-data": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
"integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
"hasown": "^2.0.2",
"mime-types": "^2.1.12"
},
"engines": {

View File

@@ -103,11 +103,25 @@ export async function syncGitHubAppsData(openApiSource, sourceSchemas, progAcces
const excludedActors = progActorResources[permissionName]['excluded_actors']
const additionalPermissions =
progAccessData[operation.operationId].permissions.length > 1 ||
progAccessData[operation.operationId].permissions.some(
(permissionSet) => Object.keys(permissionSet).length > 1,
const additionalPermissions = calculateAdditionalPermissions(
progAccessData[operation.operationId].permissions,
)
// Filter out metadata permissions when combined with other permissions
// The metadata permission is automatically granted with any other repository permission,
// so documenting it for operations that require additional permissions is misleading.
// This fixes the issue where mutating operations (PUT, DELETE) incorrectly appeared
// to only need metadata access when they actually require write permissions.
// See: https://github.com/github/docs-engineering/issues/5212
if (
shouldFilterMetadataPermission(
permissionName,
progAccessData[operation.operationId].permissions,
)
) {
continue
}
// github app permissions
if (!isActorExcluded(excludedActors, 'server_to_server', actorTypeMap)) {
const serverToServerPermissions = githubAppsData['server-to-server-permissions']
@@ -332,6 +346,28 @@ function sentenceCase(str) {
return str.charAt(0).toUpperCase() + str.slice(1)
}
/**
* Calculates whether an operation has additional permissions beyond a single permission.
*/
export function calculateAdditionalPermissions(permissionSets) {
return (
permissionSets.length > 1 ||
permissionSets.some((permissionSet) => Object.keys(permissionSet).length > 1)
)
}
/**
* Determines whether a metadata permission should be filtered out when it has additional permissions.
* Prevents misleading documentation where mutating operations appear to only need metadata access.
*/
export function shouldFilterMetadataPermission(permissionName, permissionSets) {
if (permissionName !== 'metadata') {
return false
}
return calculateAdditionalPermissions(permissionSets)
}
export function isActorExcluded(excludedActors, actorType, actorTypeMap = {}) {
if (!excludedActors || !Array.isArray(excludedActors)) {
return false
@@ -358,7 +394,6 @@ export function isActorExcluded(excludedActors, actorType, actorTypeMap = {}) {
return false
}
function addAppData(storage, category, data) {
if (!storage[category]) {
storage[category] = []

View File

@@ -0,0 +1,308 @@
import { describe, expect, test } from 'vitest'
import { shouldFilterMetadataPermission, calculateAdditionalPermissions } from '../scripts/sync.js'
describe('metadata permissions filtering', () => {
// Mock data structure representing operations with metadata permissions
const mockOperationsWithMetadata = [
{
operationId: 'repos/enable-automated-security-fixes',
permissionSets: [{ metadata: 'read', administration: 'write' }],
},
{
operationId: 'repos/get-readme',
permissionSets: [{ metadata: 'read' }],
},
{
operationId: 'orgs/update-webhook',
permissionSets: [{ metadata: 'read', organization_administration: 'write' }],
},
{
operationId: 'repos/get-content',
permissionSets: [{ contents: 'read' }],
},
]
// Mock programmatic access data
const mockProgAccessData = {
'repos/enable-automated-security-fixes': {
userToServerRest: true,
serverToServer: true,
permissions: [{ metadata: 'read', administration: 'write' }],
},
'repos/get-readme': {
userToServerRest: true,
serverToServer: true,
permissions: [{ metadata: 'read' }],
},
'orgs/update-webhook': {
userToServerRest: true,
serverToServer: true,
permissions: [{ metadata: 'read', organization_administration: 'write' }],
},
'repos/get-content': {
userToServerRest: true,
serverToServer: true,
permissions: [{ contents: 'read' }],
},
}
// Mock actor resources
const mockProgActorResources = {
metadata: {
title: 'Metadata',
visibility: 'public',
},
administration: {
title: 'Administration',
visibility: 'public',
},
organization_administration: {
title: 'Organization administration',
visibility: 'public',
},
contents: {
title: 'Contents',
visibility: 'public',
},
}
test('calculateAdditionalPermissions works correctly', () => {
// Single permission set with multiple permissions
expect(calculateAdditionalPermissions([{ metadata: 'read', admin: 'write' }])).toBe(true)
// Single permission set with single permission
expect(calculateAdditionalPermissions([{ metadata: 'read' }])).toBe(false)
// Multiple permission sets
expect(calculateAdditionalPermissions([{ metadata: 'read' }, { admin: 'write' }])).toBe(true)
// Empty permission sets
expect(calculateAdditionalPermissions([])).toBe(false)
})
test('identifies metadata with additional permissions correctly', () => {
// Case 1: metadata + administration (should be filtered)
const metadataWithAdmin = [{ metadata: 'read', administration: 'write' }]
expect(shouldFilterMetadataPermission('metadata', metadataWithAdmin)).toBe(true)
// Case 2: metadata only (should NOT be filtered)
const metadataOnly = [{ metadata: 'read' }]
expect(shouldFilterMetadataPermission('metadata', metadataOnly)).toBe(false)
// Case 3: non-metadata permission (should NOT be filtered)
const nonMetadata = [{ contents: 'read' }]
expect(shouldFilterMetadataPermission('contents', nonMetadata)).toBe(false)
})
test('filters metadata operations with additional permissions', () => {
const filteredOperations = []
const metadataPermissions = []
for (const operation of mockOperationsWithMetadata) {
const progData = mockProgAccessData[operation.operationId]
for (const permissionSet of progData.permissions) {
for (const [permissionName] of Object.entries(permissionSet)) {
if (mockProgActorResources[permissionName]?.visibility === 'private') continue
const additionalPermissions = calculateAdditionalPermissions(progData.permissions)
// Apply metadata filtering logic
if (shouldFilterMetadataPermission(permissionName, progData.permissions)) {
// Skip this metadata permission as it has additional permissions
continue
}
if (permissionName === 'metadata') {
metadataPermissions.push({
operationId: operation.operationId,
additionalPermissions,
})
} else {
filteredOperations.push({
operationId: operation.operationId,
permission: permissionName,
additionalPermissions,
})
}
}
}
}
// Should only have metadata permission from the metadata-only operation
expect(metadataPermissions).toHaveLength(1)
expect(metadataPermissions[0].operationId).toBe('repos/get-readme')
expect(metadataPermissions[0].additionalPermissions).toBe(false)
// Should have other permissions from operations with additional permissions
const adminPermission = filteredOperations.find((op) => op.permission === 'administration')
expect(adminPermission).toBeDefined()
expect(adminPermission.operationId).toBe('repos/enable-automated-security-fixes')
expect(adminPermission.additionalPermissions).toBe(true)
const orgAdminPermission = filteredOperations.find(
(op) => op.permission === 'organization_administration',
)
expect(orgAdminPermission).toBeDefined()
expect(orgAdminPermission.operationId).toBe('orgs/update-webhook')
expect(orgAdminPermission.additionalPermissions).toBe(true)
})
test('preserves non-metadata permissions regardless of additional permissions', () => {
const nonMetadataOperations = mockOperationsWithMetadata.filter(
(op) =>
!mockProgAccessData[op.operationId].permissions.some((permSet) => 'metadata' in permSet),
)
expect(nonMetadataOperations).toHaveLength(1)
expect(nonMetadataOperations[0].operationId).toBe('repos/get-content')
// Verify contents permission would be preserved
const contentsPermissionSet = mockProgAccessData['repos/get-content'].permissions[0]
expect('contents' in contentsPermissionSet).toBe(true)
expect('metadata' in contentsPermissionSet).toBe(false)
})
test('handles edge cases in permission sets', () => {
// Empty permission set
expect(shouldFilterMetadataPermission('metadata', [])).toBe(false)
// Permission set with empty object (edge case)
const edgeCase1 = [{ metadata: 'read' }, {}]
expect(shouldFilterMetadataPermission('metadata', edgeCase1)).toBe(true)
// Multiple permission sets with metadata in different sets
const edgeCase2 = [{ metadata: 'read' }, { admin: 'write' }]
expect(shouldFilterMetadataPermission('metadata', edgeCase2)).toBe(true)
})
test('filters metadata permissions that match the GitHub issue examples', () => {
// These are examples from the GitHub issue that should be filtered out
// PUT /orgs/{org}/actions/permissions/repositories/{repository_id}
const putActionsPermissions = [{ metadata: 'read', organization_administration: 'write' }]
// DELETE /orgs/{org}/actions/permissions/repositories/{repository_id}
const deleteActionsPermissions = [{ metadata: 'read', organization_administration: 'write' }]
// These should be filtered out because they have metadata + additional permissions
expect(shouldFilterMetadataPermission('metadata', putActionsPermissions)).toBe(true)
expect(shouldFilterMetadataPermission('metadata', deleteActionsPermissions)).toBe(true)
// But the organization_administration permissions should NOT be filtered
expect(
shouldFilterMetadataPermission('organization_administration', putActionsPermissions),
).toBe(false)
expect(
shouldFilterMetadataPermission('organization_administration', deleteActionsPermissions),
).toBe(false)
})
test('preserves metadata permissions that are standalone', () => {
// Example of a metadata-only permission that should be preserved
const metadataOnlyPermissions = [{ metadata: 'read' }]
// This should NOT be filtered out
expect(shouldFilterMetadataPermission('metadata', metadataOnlyPermissions)).toBe(false)
})
test('handles complex permission structures from real data', () => {
// Multiple permission sets (should filter metadata)
const multiplePermissionSets = [{ metadata: 'read' }, { administration: 'write' }]
expect(shouldFilterMetadataPermission('metadata', multiplePermissionSets)).toBe(true)
// Single permission set with multiple permissions (should filter metadata)
const multiplePermissionsInSet = [
{ metadata: 'read', contents: 'write', pull_requests: 'write' },
]
expect(shouldFilterMetadataPermission('metadata', multiplePermissionsInSet)).toBe(true)
// Multiple permission sets where metadata is not in the first set
const metadataInSecondSet = [{ administration: 'write' }, { metadata: 'read' }]
expect(shouldFilterMetadataPermission('metadata', metadataInSecondSet)).toBe(true)
})
test('validates filtering logic against known problematic endpoints', () => {
// Based on the issue description, these types of operations should have
// their metadata permissions filtered out:
// Runner group operations
const runnerGroupPermissions = [{ metadata: 'read', organization_administration: 'write' }]
// Organization secrets operations
const orgSecretsPermissions = [{ metadata: 'read', organization_secrets: 'write' }]
// Repository operations with admin permissions
const repoAdminPermissions = [{ metadata: 'read', administration: 'write' }]
// All of these should filter out metadata
expect(shouldFilterMetadataPermission('metadata', runnerGroupPermissions)).toBe(true)
expect(shouldFilterMetadataPermission('metadata', orgSecretsPermissions)).toBe(true)
expect(shouldFilterMetadataPermission('metadata', repoAdminPermissions)).toBe(true)
// But should preserve the actual required permissions
expect(
shouldFilterMetadataPermission('organization_administration', runnerGroupPermissions),
).toBe(false)
expect(shouldFilterMetadataPermission('organization_secrets', orgSecretsPermissions)).toBe(
false,
)
expect(shouldFilterMetadataPermission('administration', repoAdminPermissions)).toBe(false)
})
test('verifies consistency with additional-permissions flag calculation', () => {
const testCases = [
// Single permission, single set - no additional permissions
{ permissionSets: [{ metadata: 'read' }], expected: false },
// Multiple permissions, single set - has additional permissions
{ permissionSets: [{ metadata: 'read', admin: 'write' }], expected: true },
// Single permission, multiple sets - has additional permissions
{ permissionSets: [{ metadata: 'read' }, { admin: 'write' }], expected: true },
// Multiple permissions, multiple sets - has additional permissions
{
permissionSets: [{ metadata: 'read', contents: 'read' }, { admin: 'write' }],
expected: true,
},
]
for (const testCase of testCases) {
const additionalPermissions = calculateAdditionalPermissions(testCase.permissionSets)
const shouldFilter = shouldFilterMetadataPermission('metadata', testCase.permissionSets)
// The filtering logic should match the additional permissions calculation
expect(shouldFilter).toBe(additionalPermissions)
expect(additionalPermissions).toBe(testCase.expected)
}
})
test('validates filtering logic matches expected behavior from issue', () => {
// Based on the GitHub issue, these operations should be filtered out from metadata:
// - PUT /orgs/{org}/actions/permissions/repositories/{repository_id}
// - DELETE /orgs/{org}/actions/permissions/repositories/{repository_id}
// Because they have metadata + organization_administration permissions
const mockMutatingOperation = {
operationId: 'actions/set-selected-repositories-enabled-github-actions-organization',
permissionSets: [{ metadata: 'read', organization_administration: 'write' }],
}
const progData = {
userToServerRest: true,
serverToServer: true,
permissions: [{ metadata: 'read', organization_administration: 'write' }],
}
// This should be filtered out from metadata permissions
expect(shouldFilterMetadataPermission('metadata', progData.permissions)).toBe(true)
// But organization_administration permission should still be included
expect(
shouldFilterMetadataPermission('organization_administration', progData.permissions),
).toBe(false)
// Verify additional permissions flag is set correctly
expect(calculateAdditionalPermissions(progData.permissions)).toBe(true)
})
})