diff --git a/content/admin/concepts/enterprise-fundamentals/roles-in-an-enterprise.md b/content/admin/concepts/enterprise-fundamentals/roles-in-an-enterprise.md index ef9d201595a3..1a0f90697bad 100644 --- a/content/admin/concepts/enterprise-fundamentals/roles-in-an-enterprise.md +++ b/content/admin/concepts/enterprise-fundamentals/roles-in-an-enterprise.md @@ -2,8 +2,7 @@ title: Roles in an enterprise intro: 'Learn how roles allow you to control people''s access to your enterprise''s settings and resources.' versions: - ghec: '*' - ghes: '*' + feature: enterprise-custom-roles shortTitle: Roles topics: - Enterprise @@ -15,34 +14,30 @@ contentType: concepts ## What are roles? -A role is a **set of permissions** that you can assign to individuals or teams. A permission is the ability to perform a specific action, such as changing billing settings. - -A user in an enterprise has a role for both the enterprise account itself and for each individual organization in the enterprise. +Roles allow you to delegate administrative duties and manage access securely at every level of your enterprise. -* The enterprise-level role defines the user's access to enterprise settings, and to internal repositories across the enterprise. -* Organization-level roles define the user's access to organization settings and repositories in that organization. +A role is a **set of permissions** that you can assign to individuals or teams. A permission is the ability to perform a specific action, such as changing billing settings. -## Predefined and custom roles for organizations +A user in an enterprise has roles for both the enterprise account and organizations where they have access. -Organization roles can be **predefined** or **custom**. +* The enterprise-level roles define the user's access to enterprise settings. +* Organization-level roles define the user's access to organization settings and repositories in an organization. -* Predefined roles, such as organization owner or billing manager, grant blanket permissions to users or teams. They may contain more permissions than someone needs to do their job. -* Custom roles include fine-grained permissions for organization settings and repository access. They allow you to follow the principle of least privilege by giving teams just the access they need to do their jobs. For example, you could allow a team to view your audit logs without allowing them to change policies. +## Predefined and custom roles -We recommend using custom roles wherever possible. However, if a predefined role meets your needs, this is the quickest way to grant permissions. +Organization and enterprise roles can be **predefined** or **custom**. Enterprise custom roles are in {% data variables.release-phases.public_preview %}. -## Who can assign roles? +* Predefined roles, such as enterprise owner, organization owner, or billing manager, are available for all accounts. They grant a predefined set of permissions to users or teams and may contain more permissions than someone needs to do their job. +* Custom roles include your choice of fine-grained permissions. They can include access to account settings and (for organization custom roles) repository access, allowing you to provide teams with just the access they need to do their jobs. For example, you could allow a team to view your enterprise's audit logs without allowing them to change any settings. -Enterprise roles are assigned when a user is invited to the enterprise (personal accounts) or provisioned from an identity provider.{% ifversion ent-owner-custom-org-roles %} Enterprise owners can also create custom organization roles to be used across organizations, but these roles can only be assigned by organization administrators.{% endif %} +To follow the principle of least privilege access, we recommend using custom roles if they allow for the permissions you require. However, not all capabilities of predefined roles can currently be replicated in custom roles. -Organization administrators can grant organization roles and create custom organization roles, but can't affect roles at the enterprise level. +## Who manages roles? -## Further reading +Enterprise owners can create custom enterprise roles and assign enterprise roles to users and teams. They can also create custom organization roles to be used across organizations, but these roles can only be assigned by organization owners. -Review the predefined roles and fine-grained permissions available with custom organization roles, and plan out what roles will be required for your teams to do their jobs on {% data variables.product.github %}. +Organization owners can grant organization roles and create custom organization roles, but cannot edit roles or change role assignments that are defined at the enterprise level. -* [AUTOTITLE](/admin/managing-accounts-and-repositories/managing-users-in-your-enterprise/abilities-of-roles) -* [AUTOTITLE](/organizations/managing-peoples-access-to-your-organization-with-roles/roles-in-an-organization#about-organization-roles) -* [AUTOTITLE](/organizations/managing-peoples-access-to-your-organization-with-roles/about-custom-organization-roles#permissions-for-organization-access) +## Next steps -To ensure continued access, we recommend giving the enterprise owner role to at least two people, and the organization owner role to at least two people per organization. However, you should grant most teams only the minimum level of access they require. +Now that you understand roles, plan which roles will be required for your teams to do their jobs on {% data variables.product.github %}. See [AUTOTITLE](/admin/managing-accounts-and-repositories/managing-roles-in-your-enterprise/identify-role-requirements). diff --git a/content/admin/concepts/enterprise-fundamentals/teams-in-an-enterprise.md b/content/admin/concepts/enterprise-fundamentals/teams-in-an-enterprise.md index 2dc8451c4b5d..00289e8e4ee9 100644 --- a/content/admin/concepts/enterprise-fundamentals/teams-in-an-enterprise.md +++ b/content/admin/concepts/enterprise-fundamentals/teams-in-an-enterprise.md @@ -2,7 +2,7 @@ title: Teams in an enterprise intro: 'Learn how teams simplify administration of user access, licensing, and communication.' versions: - ghec: '*' + feature: enterprise-teams shortTitle: Teams topics: - Enterprise @@ -16,10 +16,11 @@ contentType: concepts Teams are **groups of users** in an enterprise or organization. By creating teams, you can manage users at scale and simplify access, licensing, and communication. For example, you could create an auditor team for users who need access to audit logs, or a {% data variables.product.prodname_copilot_short %} team for users who receive {% data variables.product.prodname_copilot_short %} licenses. -Administrators can create teams in an enterprise account or in organizations within an enterprise. +**Enterprise teams** are managed at the enterprise level and can include users from across the enterprise and its organizations. With enterprise teams, you can centralize administration and manage organization access, roles, and licensing at scale. -* **Enterprise teams** are managed by enterprise owners and can include users from across the enterprise and its organizations. Currently, enterprise teams are used to manage {% data variables.product.prodname_copilot %} licenses for directly assigned users. {% data variables.product.company_short %} plans to expand the capabilities in the near future to include organization and role assignment. -* **Organization teams** are managed by organization administrators and can only include members of a single organization. Organization administrators can grant teams access to organization repositories, and organization members can mention teams in issues and discussions or add them as reviewers on pull requests. +**Organization teams** are managed at the organization level and can only include members of a single organization. There are certain features of organization teams that are not currently supported for enterprise teams, such as CODEOWNER status. + +>[!NOTE] Enterprise teams are in public preview and subject to change. ## Can I manage teams from an identity provider? @@ -41,22 +42,29 @@ Team sync with personal accounts is only available with organization teams, and ## What kind of team should I use? -To simplify administration at scale, {% data variables.product.company_short %} recommends using enterprise teams wherever possible. However, you may need to create organization teams if the functionality you need is not covered by enterprise teams. {% data variables.product.company_short %} plans to address some of these limitations in the near future. +To simplify administration at scale, {% data variables.product.company_short %} recommends using enterprise teams for any use cases that apply to the enterprise account or to multiple organizations. Organization teams are useful when the need for the team is scoped to a single organization and the team can be managed by an organization administrator. + +You may need to create organization teams if the functionality you need is not covered by enterprise teams. {% data variables.product.company_short %} plans to address some limitations in the near future. -Unlike organization teams, enterprise teams currently do **not** support: +{% data reusables.enterprise.enterprise-teams-can %} + +However, unlike organization teams, enterprise teams currently do **not** support: -* Repository or organization access * `@-mentions` of the team name in organizations +* Review requests of the team in pull requests +* Adding the team to a project board * Team sync if you use {% data variables.product.prodname_ghe_cloud %} with personal accounts * CODEOWNER status * Secret teams * Nested teams * Team maintainers -In addition, enterprise teams are currently limited to 50 teams for a single enterprise and 500 users to each team. +{% data reusables.enterprise.enterprise-teams-limits %} For more information about the capabilities of organization teams, see [AUTOTITLE](/organizations/organizing-members-into-teams/about-teams). -## Further reading +## Next steps + +If your needs are covered by enterprise teams, create a team. See [AUTOTITLE](/admin/managing-accounts-and-repositories/managing-users-in-your-enterprise/create-enterprise-teams). -* [AUTOTITLE](/organizations/organizing-members-into-teams/about-teams) +If you need to create an organization team, an organization owner must do this from the organization settings. See [AUTOTITLE](/organizations/organizing-members-into-teams/creating-a-team). diff --git a/content/admin/guides.md b/content/admin/guides.md index 264f3f8c3a2c..71d9467e8881 100644 --- a/content/admin/guides.md +++ b/content/admin/guides.md @@ -119,7 +119,7 @@ includeGuides: - /admin/managing-accounts-and-repositories/managing-organizations-in-your-enterprise/managing-projects-using-jira - /admin/managing-accounts-and-repositories/managing-users-in-your-enterprise/inviting-people-to-manage-your-enterprise - /admin/managing-accounts-and-repositories/managing-users-in-your-enterprise/managing-support-entitlements-for-your-enterprise - - /admin/managing-accounts-and-repositories/managing-users-in-your-enterprise/abilities-of-roles + - /admin/managing-accounts-and-repositories/managing-roles-in-your-enterprise/abilities-of-roles - /admin/managing-accounts-and-repositories/managing-users-in-your-enterprise/viewing-and-managing-a-users-saml-access-to-your-enterprise - /admin/managing-accounts-and-repositories/managing-users-in-your-enterprise/viewing-people-in-your-enterprise - /admin/user-management/managing-repositories-in-your-enterprise/migrating-to-internal-repositories diff --git a/content/admin/index.md b/content/admin/index.md index b85a9d0cc6b1..db63292688de 100644 --- a/content/admin/index.md +++ b/content/admin/index.md @@ -73,7 +73,7 @@ featuredLinks: startHere: - '/admin/concepts/identity-and-access-management\identity-and-access-management-fundamentals' - '{% ifversion ghec %}/admin/concepts/identity-and-access-management/enterprise-types-for-github-enterprise-cloud{% endif %}' - - '{% ifversion ghec %}/admin/managing-accounts-and-repositories/managing-users-in-your-enterprise/abilities-of-roles{% endif %}' + - '{% ifversion ghec %}/admin/managing-accounts-and-repositories/managing-roles-in-your-enterprise/abilities-of-roles{% endif %}' - /admin/managing-accounts-and-repositories/managing-organizations-in-your-enterprise/best-practices-for-structuring-organizations-in-your-enterprise - '{% ifversion ghes %}/admin/getting-started-with-enterprise/about-upgrades-to-new-releases{% endif %}' - '{% ifversion ghes %}/billing/how-tos/set-up-payment/manage-enterprise-invoice{% endif %}' diff --git a/content/admin/managing-accounts-and-repositories/index.md b/content/admin/managing-accounts-and-repositories/index.md index 0c10f62c35ce..f71b6520fae2 100644 --- a/content/admin/managing-accounts-and-repositories/index.md +++ b/content/admin/managing-accounts-and-repositories/index.md @@ -18,4 +18,5 @@ children: - /managing-users-in-your-enterprise - /managing-organizations-in-your-enterprise - /managing-repositories-in-your-enterprise + - /managing-roles-in-your-enterprise --- diff --git a/content/admin/managing-accounts-and-repositories/managing-organizations-in-your-enterprise/custom-organization-roles.md b/content/admin/managing-accounts-and-repositories/managing-organizations-in-your-enterprise/custom-organization-roles.md deleted file mode 100644 index 4b136bdcb734..000000000000 --- a/content/admin/managing-accounts-and-repositories/managing-organizations-in-your-enterprise/custom-organization-roles.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -title: Creating custom organization roles in an enterprise -intro: Create roles with fine-grained permissions for a consistent experience across your organizations. -versions: - feature: ent-owner-custom-org-roles -type: how_to -topics: - - Enterprise - - Organizations -shortTitle: Custom organization roles ---- - -To define consistent sets of permissions for settings and repositories, you can create custom organization roles for use in all of the enterprise's organizations. This allows centralized management of common roles such as "Developer" or "SRE team." - -Custom organization roles created at the enterprise level use the same organization and repository permissions and base roles as roles created at the organization level. There is no difference in how these roles function or what they can allow. For more information, see [AUTOTITLE](/organizations/managing-peoples-access-to-your-organization-with-roles/about-custom-organization-roles). - -Enterprise owners can create and edit custom organization roles, but cannot assign them. Organization owners can assign custom roles in an organization. - ->[!NOTE] An enterprise can create up to 20 custom organization roles. This limit applies to the enterprise: each organization can also create up to 20 custom organization roles. - -{% data reusables.enterprise-accounts.access-enterprise %} -{% data reusables.enterprise-accounts.people-tab %} -1. In the left sidebar, select **Organization roles**. -1. Click **Create custom role**. -1. Enter the details, then click **Create role**. diff --git a/content/admin/managing-accounts-and-repositories/managing-organizations-in-your-enterprise/index.md b/content/admin/managing-accounts-and-repositories/managing-organizations-in-your-enterprise/index.md index 1b8f15370945..dea7c99852f4 100644 --- a/content/admin/managing-accounts-and-repositories/managing-organizations-in-your-enterprise/index.md +++ b/content/admin/managing-accounts-and-repositories/managing-organizations-in-your-enterprise/index.md @@ -13,7 +13,7 @@ redirect_from: - /github/setting-up-and-managing-your-enterprise-account/managing-unowned-organizations-in-your-enterprise-account - /github/setting-up-and-managing-your-enterprise/managing-unowned-organizations-in-your-enterprise-account - /admin/managing-accounts-and-repositories/managing-organizations-in-your-enterprise/continuous-integration-using-jenkins -intro: 'You can use organizations to group users within your company, such as divisions or groups working on similar projects, and manage access to repositories.' +intro: You can use organizations to group users within your company, such as divisions or groups working on similar projects, and manage access to repositories. versions: ghec: '*' ghes: '*' @@ -25,7 +25,6 @@ children: - /configuring-visibility-for-organization-membership - /preventing-users-from-creating-organizations - /requiring-two-factor-authentication-for-an-organization - - /custom-organization-roles - /managing-your-role-in-an-organization-owned-by-your-enterprise - /managing-requests-for-copilot-business-from-organizations-in-your-enterprise - /removing-organizations-from-your-enterprise @@ -33,3 +32,4 @@ children: - /managing-projects-using-jira shortTitle: Manage organizations --- + diff --git a/content/admin/managing-accounts-and-repositories/managing-roles-in-your-enterprise/abilities-of-roles.md b/content/admin/managing-accounts-and-repositories/managing-roles-in-your-enterprise/abilities-of-roles.md new file mode 100644 index 000000000000..d1b112773085 --- /dev/null +++ b/content/admin/managing-accounts-and-repositories/managing-roles-in-your-enterprise/abilities-of-roles.md @@ -0,0 +1,159 @@ +--- +title: Abilities of roles in an enterprise +intro: Learn which roles you can assign to control access to your enterprise's settings and data. +shortTitle: Predefined roles +redirect_from: + - /github/setting-up-and-managing-your-enterprise/managing-users-in-your-enterprise/roles-in-an-enterprise + - /github/setting-up-and-managing-your-enterprise-account/roles-for-an-enterprise-account + - /articles/permission-levels-for-a-business-account + - /articles/roles-for-an-enterprise-account + - /github/setting-up-and-managing-your-enterprise/roles-in-an-enterprise + - /admin/user-management/managing-users-in-your-enterprise/roles-in-an-enterprise + - /admin/managing-accounts-and-repositories/managing-users-in-your-enterprise/roles-in-an-enterprise + - /admin/managing-accounts-and-repositories/managing-users-in-your-enterprise/abilities-of-roles +versions: + ghec: '*' + ghes: '*' +topics: + - Enterprise +allowTitleToDifferFromFilename: true +contentType: reference +--- + +## About roles in an enterprise + +{% data variables.product.github %} offers a range of predefined and custom roles for access to enterprise settings and resources. + +| Role | Description | +| ---- | ----------- | +| Enterprise owner | Can manage all enterprise settings, members, and policies. | +| {% ifversion ghec %} | +| Billing manager | Can manage enterprise billing settings. | +| {% endif %} | +| {% ifversion enterprise-app-manager %} | +| App manager | Can manage {% data variables.product.prodname_github_app %} registrations that are owned by the enterprise. | +| {% endif %} | +| {% ifversion ent-security-manager %} | +| Security manager | Can view security results and manage security settings for the enterprise ({% data variables.release-phases.public_preview %}). | +| {% endif %} | +| User | A regular enterprise member with no administrative access.{% ifversion unaffiliated-users %} Includes organization members and unaffiliated users. | +| {% endif %} | +| {% ifversion guest-collaborators %} | +| Guest collaborator | Can be granted access to repositories or organizations, but has limited access by default ({% data variables.product.prodname_emus %} only). | +| {% endif %} | +| {% ifversion enterprise-custom-roles %} | +| Custom roles | Define your own set of permissions for access to enterprise settings. | +| {% endif %} | + +People with collaborator access to repositories are listed in your enterprise's "People" tab, but are not enterprise members and do not have access to the enterprise. See {% ifversion ghec %}[AUTOTITLE](/organizations/managing-peoples-access-to-your-organization-with-roles/roles-in-an-organization#outside-collaborators-or-repository-collaborators).{% else %}[AUTOTITLE](/organizations/managing-peoples-access-to-your-organization-with-roles/roles-in-an-organization#outside-collaborators).{% endif %} + +## Enterprise owners + +Enterprise owners have complete control over the enterprise and can take every action, including: + +* Managing administrators +* {% ifversion ghec %}Adding and removing {% elsif ghes %}Managing{% endif %} organizations{% ifversion remove-enterprise-members %} +* Removing enterprise members from all organizations{% endif %} +* Managing enterprise settings +* Enforcing policy across organizations{% ifversion ghec %} +* Managing billing settings{% endif %} +* Managing security settings + +Enterprise owners do not have access to organization settings or content by default, but they can gain access by joining any organization. See [AUTOTITLE](/admin/user-management/managing-organizations-in-your-enterprise/managing-your-role-in-an-organization-owned-by-your-enterprise). + +{% ifversion ghec %} + +## Billing managers + +Billing managers only have access to your enterprise's billing settings. They can view and manage: + +* User licenses +* Usage-based billing +* Other billing settings + +Billing managers do not have access to organization settings or content by default except for internal repositories within an enterprise in which they are a member. + +{% endif %} + +{% ifversion enterprise-app-manager %} + +## App managers + +{% data variables.product.prodname_github_app %} managers: + +* Can view, create, edit, and delete {% data variables.product.prodname_github_app %} registrations that are owned by the enterprise. For the specific app settings that {% data variables.product.prodname_github_app %} managers can control, see [AUTOTITLE](/apps/maintaining-github-apps/modifying-a-github-app). +* Cannot install and uninstall {% data variables.product.prodname_github_apps %} on an enterprise or organization. + +App managers can also be assigned to individual apps. See [AUTOTITLE](/admin/managing-your-enterprise-account/adding-and-removing-github-app-managers-in-your-enterprise). + +{% endif %} + +{% ifversion ent-security-manager %} + +## Security managers + +> [!NOTE] +> The enterprise security manager role is in {% data variables.release-phases.public_preview %} and subject to change. The [AUTOTITLE](/free-pro-team@latest/site-policy/github-terms/github-pre-release-license-terms) apply to your use of this role. + +Security managers have the permissions required to effectively manage use of security features and alerts for the enterprise. They can view, manage, and assign: + +* Security configurations at the enterprise and organization level +* Use of {% data variables.product.prodname_GH_secret_protection %} and {% data variables.product.prodname_GH_code_security %} at the enterprise and organization level +* Security alerts and dashboards for all repositories in organizations in the enterprise +* Security campaigns for organizations +* Repository settings for security features + +In addition, they have read access for code in all repositories and write access for all security alerts in the enterprise. + +{% endif %} + +## Users + +Users have no administrative access to the enterprise by default. They cannot access or configure enterprise settings, unless you assign them a custom role that grants this access. + +{% ifversion unaffiliated-users %} + +### Organization members + +{% endif %} + +If a user is a member or owner of any organization, they are listed as an **organization member** on your enterprise's "People" page. In addition to their access to organizations where they are members, these users can access all repositories with "internal" visibility in any organization in the enterprise. See [AUTOTITLE](/repositories/creating-and-managing-repositories/about-repositories#about-internal-repositories). + +{% ifversion unaffiliated-users %} + +### Unaffiliated users + +If a user is not a member of any organization, they are listed as an **unaffiliated user**. These users: + +* Do not consume a {% data variables.product.prodname_enterprise %} license. +* Cannot access private or internal repositories. +* Can be added as members of enterprise teams. +* Can receive a {% data variables.product.prodname_copilot_short %} license directly from your enterprise. + +{% endif %} + +{% ifversion guest-collaborators %} + +## Guest collaborators + +{% data reusables.emus.guest-collaborators-note %} + +{% data reusables.emus.about-guest-collaborators %} + +You may need to update your IdP application to use guest collaborators. See [AUTOTITLE](/admin/managing-accounts-and-repositories/managing-users-in-your-enterprise/enabling-guest-collaborators). + +{% endif %} + +{% ifversion enterprise-custom-roles %} + +## Custom roles + +With custom roles, you can define your own sets of permissions. This allows you to delegate administrative duties securely or grant extra privileges to help non-administrators be productive. + +To create a custom enterprise role, see [AUTOTITLE](/admin/managing-accounts-and-repositories/managing-roles-in-your-enterprise/create-custom-roles). + +## Next steps + +When you have decided which roles your users require, assign the roles to them. See [AUTOTITLE](/admin/managing-accounts-and-repositories/managing-users-in-your-enterprise/assign-roles). + +{% endif %} diff --git a/content/admin/managing-accounts-and-repositories/managing-roles-in-your-enterprise/assign-roles.md b/content/admin/managing-accounts-and-repositories/managing-roles-in-your-enterprise/assign-roles.md new file mode 100644 index 000000000000..e1f199501e13 --- /dev/null +++ b/content/admin/managing-accounts-and-repositories/managing-roles-in-your-enterprise/assign-roles.md @@ -0,0 +1,52 @@ +--- +title: Assigning roles to people in an enterprise +intro: Assign roles to users and teams to govern what people can do in your enterprise. +versions: + feature: enterprise-custom-roles +type: how_to +topics: + - Enterprise +shortTitle: Assign roles +redirect_from: + - /admin/managing-accounts-and-repositories/managing-users-in-your-enterprise/assign-roles +--- + +Enterprise owners can assign custom and predefined **enterprise roles** to users and teams. Some roles can be assigned to enterprise teams, whereas other roles are only available for individual users. Find the section below for the role you want to assign. + +For more information about using roles effectively, see [AUTOTITLE](/admin/managing-accounts-and-repositories/managing-roles-in-your-enterprise/identify-role-requirements). + +## Assigning app managers, security managers, and custom roles + +>[!NOTE] These roles are in public preview and subject to change. + +These roles can be assigned to existing users and teams in your enterprise settings, including {% data variables.enterprise.prodname_managed_users %}. + +Before you assign a role, you may need to create a team. Teams are the best way to manage role assignments at scale. The enterprise security manager role can **only** be assigned to a team, not to individual users. See [AUTOTITLE](/admin/managing-accounts-and-repositories/managing-users-in-your-enterprise/create-enterprise-teams). + +{% data reusables.enterprise-accounts.access-enterprise %} +{% data reusables.enterprise-accounts.people-tab %} +1. In the left sidebar, click **{% octicon "globe" aria-hidden="true" aria-label="globe" %} Enterprise roles**, then click **Role assignments**. +1. Click **Assign role**. +1. Choose the user or team and the role they should receive, then click **Assign role**. + +## Assigning enterprise owners, billing managers, and guest collaborators + +These predefined roles are chosen when you invite a user to your enterprise or provision a {% data variables.enterprise.prodname_managed_user %} from your identity provider (IdP). + +These roles cannot currently be assigned to enterprise teams, but they can be changed for existing users. + +### Assigning these roles to new users + +* If you {% ifversion ghes %}have enabled SCIM provisioning{% else %}use **{% data variables.product.prodname_emus %}**{% endif %}, roles are assigned from your IdP via the SCIM `roles` attribute. +* If you use an **enterprise with personal accounts**, you can invite someone as a user or administrator. See [AUTOTITLE](/admin/managing-accounts-and-repositories/managing-users-in-your-enterprise/invite-users-directly) or [AUTOTITLE](/admin/user-management/managing-users-in-your-enterprise/inviting-people-to-manage-your-enterprise). + +### Assigning these roles to existing administrators + +You can change an administrator's role or convert them to a regular member once they have joined your enterprise. + +* If you {% ifversion ghes %}provisioned the user via SCIM{% else %}use **{% data variables.product.prodname_emus %}**{% endif %}, you must do this from your IdP via the SCIM `roles` attribute. +* {% ifversion ghes %}For all other accounts{% else %}If you use an **enterprise with personal accounts**{% endif %}, you can change the role on your enterprise's "Administrators" page, using the **{% octicon "kebab-horizontal" aria-label="Administrator" %}** menu next to the user's name. See [AUTOTITLE](/admin/managing-accounts-and-repositories/managing-users-in-your-enterprise/viewing-people-in-your-enterprise#viewing-enterprise-administrators). + +## Assigning roles in an organization + +Enterprise owners cannot assign organization-level roles from the enterprise settings. An organization administrator must do this. See [AUTOTITLE](/organizations/managing-peoples-access-to-your-organization-with-roles/using-organization-roles#assigning-an-organization-role). diff --git a/content/admin/managing-accounts-and-repositories/managing-roles-in-your-enterprise/create-custom-roles.md b/content/admin/managing-accounts-and-repositories/managing-roles-in-your-enterprise/create-custom-roles.md new file mode 100644 index 000000000000..9930d2c0bfb9 --- /dev/null +++ b/content/admin/managing-accounts-and-repositories/managing-roles-in-your-enterprise/create-custom-roles.md @@ -0,0 +1,55 @@ +--- +title: Creating custom roles in an enterprise +intro: Create roles with fine-grained permissions for consistent access to settings and resources. +versions: + feature: ent-owner-custom-org-roles +type: how_to +topics: + - Enterprise + - Organizations +shortTitle: Create custom roles +redirect_from: + - /admin/managing-accounts-and-repositories/managing-organizations-in-your-enterprise/custom-organization-roles +--- + +>[!NOTE] The ability for enterprise owners to create custom roles for an organization or enterprise is in public preview and subject to change. + +To tailor access management to your company's needs, you can create custom roles for your{% ifversion enterprise-custom-roles %} enterprise account and{% endif %} organizations. + +Custom roles are sets of permissions for settings and resources that you can assign to users and teams.{% ifversion enterprise-custom-roles %} To learn best practices for using roles on {% data variables.product.github %}, see [AUTOTITLE](/admin/managing-accounts-and-repositories/managing-roles-in-your-enterprise/identify-role-requirements).{% endif %} + +{% ifversion enterprise-custom-roles %} + +## Creating enterprise custom roles + +Enterprise custom roles grant access to a subset of enterprise settings, such as viewing audit logs and creating organizations. {% data variables.product.github %} plans to expand the list of available permissions over time. + +{% data reusables.enterprise-accounts.access-enterprise %} +{% data reusables.enterprise-accounts.people-tab %} +1. In the left sidebar, click **{% octicon "globe" aria-hidden="true" aria-label="globe" %} Enterprise roles**, then click **Role management**. +1. Click **Create custom role**. +1. Enter the details, then click **Create role**. + +{% endif %} + +## Creating organization custom roles + +Organization custom roles grant access to organization settings and repositories. Custom organization roles created at the enterprise level use the same permissions and base roles as roles created at the organization level. For more information, see [AUTOTITLE](/organizations/managing-peoples-access-to-your-organization-with-roles/about-custom-organization-roles). + +Enterprise owners can create and edit custom organization roles, but cannot assign them. Organization owners can assign custom roles in an organization. + +>[!NOTE] An enterprise can create up to 20 custom organization roles. This limit applies to the enterprise: each organization can also create up to 20 custom organization roles. + +{% data reusables.enterprise-accounts.access-enterprise %} +{% data reusables.enterprise-accounts.people-tab %} +1. In the left sidebar, select **Organization roles**. +1. Click **Create custom role**. +1. Enter the details, then click **Create role**. + +{% ifversion enterprise-teams %} + +## Next steps + +You can manage role assignments at scale using teams. Learn about teams in your enterprise and organizations in [AUTOTITLE](/admin/concepts/enterprise-fundamentals/teams-in-an-enterprise). + +{% endif %} diff --git a/content/admin/managing-accounts-and-repositories/managing-roles-in-your-enterprise/identify-role-requirements.md b/content/admin/managing-accounts-and-repositories/managing-roles-in-your-enterprise/identify-role-requirements.md new file mode 100644 index 000000000000..663fa3b409ad --- /dev/null +++ b/content/admin/managing-accounts-and-repositories/managing-roles-in-your-enterprise/identify-role-requirements.md @@ -0,0 +1,77 @@ +--- +title: Identifying the roles required by your enterprise +intro: Plan which roles your teams need to stay productive and secure. +shortTitle: Identify role requirements +versions: + feature: enterprise-custom-roles +topics: + - Enterprise +allowTitleToDifferFromFilename: true +contentType: tutorials +--- + +Roles control people's access to settings and resources in your enterprise and organizations. For an introduction to roles, see [AUTOTITLE](/admin/concepts/enterprise-fundamentals/roles-in-an-enterprise). + +By using roles effectively, you can: + +* Delegate administrative duties and manage access securely at every level of your enterprise. +* Harden security by reducing the number of people with blanket administrative access in your enterprise. +* Ensure everyone has the permissions they need to be independent and productive. + +## 1. Review available roles and permissions + +This guide helps you understand best practices for roles, so you can plan which roles are required in your enterprise and organizations. You will then be able to create a team structure that uses roles effectively. + +As you think about tasks that would benefit from a specific role, refer to the available predefined roles and custom permissions to see if a granular role for this task is currently possible. If not, you will need to rely on a role with more blanket access, such as enterprise owner. + +>[!NOTE] Enterprise custom roles currently only cover a limited subset of enterprise settings, but {% data variables.product.company_short %} plans to expand the list of permissions over time. + +| Role type | More information | +| --------- | ---------------- | +| Predefined enterprise roles | [AUTOTITLE](/admin/managing-accounts-and-repositories/managing-users-in-your-enterprise/abilities-of-roles) | +| Predefined organization roles | [AUTOTITLE](/organizations/managing-peoples-access-to-your-organization-with-roles/roles-in-an-organization) +| Custom enterprise roles | Review the list of available permissions at `github.com/enterprises/ENTERPRISE/enterprise_roles/new`, where ENTERPRISE is the name of your enterprise account. | +| Custom organization roles | [AUTOTITLE](/organizations/managing-peoples-access-to-your-organization-with-roles/about-custom-organization-roles) | + +## 2. Identify two owners per account + +Decide who will serve as enterprise owners and organization owners. The "owner" role has full administrative access to an enterprise or organization account. + +We recommend having at least two owners per account. Although it is good practice to limit the number of people with this level of access, if an account only has one owner, the account's resources can become inaccessible if the owner is unreachable. + +## 3. Identify roles for administrative duties + +Identify predefined or custom roles that will help you delegate time-consuming administrative duties to other teams. This will help enterprise owners to focus on urgent or strategic work. + +It is unlikely that you can granularly assign every administrative duty in your enterprise to a specific team, so we recommend focusing on the most frequent and time-consuming tasks. Some examples of how you might use roles to delegate common tasks are: + +* **Auditing**: Use a custom role to give a team access to your audit logs without allowing them to access any other settings. +* **Authentication**: Use a custom role to give your identity provider administrators permission to manage SSO settings on {% data variables.product.github %}, so they can configure authentication independently. +* **Security**: Use the enterprise security manager role to give security teams access to alerts and security data across the enterprise and organizations. + +Some administrative tasks are more sensitive than others. For example, if your enterprise uses enterprise teams to manage licensing, access, and roles, then being able to change membership of a team is a powerful action that you may want to restrict to a small group of people. + +## 4. Identify base permissions for non-administrators + +Consider if there are permissions that every member of your enterprise would benefit from. These can be added to a custom role that you assign to everyone. + +For example, regular users have limited visibility of your enterprise account by default. If you want more transparency, you may want to allow all employees to: + +* View other enterprise members and administrators so they know where to go for help +* View audit logs to see what people are doing in the enterprise + +## 5. Delegate work to apps + +Not all tasks are best-suited to humans. Identify frequent, time-consuming, and easily automated tasks, and plan to delegate these tasks to {% data variables.product.prodname_github_apps %}. + +{% data variables.product.prodname_github_apps %} provide scoped tokens for use in scripts and workflows. Although they use a different permissions system from the roles you assign to users, you can think about apps like humans with a role on {% data variables.product.github %}: + +* They have fine-grained permissions for specific tasks. +* They have scoped access to specific repositories and accounts. +* They have their own identity, which you can trace in audit logs. + +For more information about what apps can do, see [AUTOTITLE](/apps/creating-github-apps/about-creating-github-apps/about-creating-github-apps#understanding-what-type-of-github-app-to-build). + +## Next steps + +Now that you've planned which roles will help your teams be productive and secure on GitHub, create custom roles for the permissions you need. Later, you will create teams to manage role assignments at scale. See [AUTOTITLE](/admin/managing-accounts-and-repositories/managing-roles-in-your-enterprise/create-custom-roles). diff --git a/content/admin/managing-accounts-and-repositories/managing-roles-in-your-enterprise/index.md b/content/admin/managing-accounts-and-repositories/managing-roles-in-your-enterprise/index.md new file mode 100644 index 000000000000..2459e306f9ab --- /dev/null +++ b/content/admin/managing-accounts-and-repositories/managing-roles-in-your-enterprise/index.md @@ -0,0 +1,16 @@ +--- +title: Managing roles in your enterprise +intro: Roles grant access to settings and resources. +versions: + ghec: '*' + ghes: '*' +topics: + - Enterprise +children: + - /identify-role-requirements + - /create-custom-roles + - /assign-roles + - /abilities-of-roles +shortTitle: Manage roles +--- + diff --git a/content/admin/managing-accounts-and-repositories/managing-users-in-your-enterprise/abilities-of-roles.md b/content/admin/managing-accounts-and-repositories/managing-users-in-your-enterprise/abilities-of-roles.md deleted file mode 100644 index 45776e529574..000000000000 --- a/content/admin/managing-accounts-and-repositories/managing-users-in-your-enterprise/abilities-of-roles.md +++ /dev/null @@ -1,110 +0,0 @@ ---- -title: Abilities of roles in an enterprise -intro: Learn which roles you can assign to control access to your enterprise's settings and data. -shortTitle: Capabilities of roles -redirect_from: - - /github/setting-up-and-managing-your-enterprise/managing-users-in-your-enterprise/roles-in-an-enterprise - - /github/setting-up-and-managing-your-enterprise-account/roles-for-an-enterprise-account - - /articles/permission-levels-for-a-business-account - - /articles/roles-for-an-enterprise-account - - /github/setting-up-and-managing-your-enterprise/roles-in-an-enterprise - - /admin/user-management/managing-users-in-your-enterprise/roles-in-an-enterprise - - /admin/managing-accounts-and-repositories/managing-users-in-your-enterprise/roles-in-an-enterprise -versions: - ghec: '*' - ghes: '*' -topics: - - Enterprise -allowTitleToDifferFromFilename: true -contentType: reference ---- - -## About roles in an enterprise - -All users that are part of your enterprise have one of the following roles. - -* **Enterprise owner:** Can manage all enterprise settings, members, and policies -{%- ifversion ghec %} -* **Billing manager:** Can manage enterprise billing settings -{%- endif %} -* **Enterprise member:** Is a member or owner of any organization in the enterprise -{%- ifversion guest-collaborators %} -* **Guest collaborator:** Can be granted access to repositories or organizations, but has limited access by default ({% data variables.product.prodname_emus %} only) -{%- endif %} -{%- ifversion unaffiliated-users %} -* **Unaffiliated user:** Has been added to the enterprise but isn't a member of any organizations -{%- endif %} - -{% ifversion ghec %}For information about which users consume a license, see [AUTOTITLE](/billing/managing-the-plan-for-your-github-account/about-per-user-pricing#people-that-consume-a-license).{% endif %} - -People with collaborator access to repositories are listed in your enterprise's "People" tab, but are not enterprise members and do not have access to the enterprise. See {% ifversion ghec %}[AUTOTITLE](/organizations/managing-peoples-access-to-your-organization-with-roles/roles-in-an-organization#outside-collaborators-or-repository-collaborators).{% else %}[AUTOTITLE](/organizations/managing-peoples-access-to-your-organization-with-roles/roles-in-an-organization#outside-collaborators).{% endif %} - -## Enterprise owners - -Enterprise owners have complete control over the enterprise and can take every action, including: - -* Managing administrators -* {% ifversion ghec %}Adding and removing {% elsif ghes %}Managing{% endif %} organizations{% ifversion remove-enterprise-members %} -* Removing enterprise members from all organizations{% endif %} -* Managing enterprise settings -* Enforcing policy across organizations{% ifversion ghec %} -* Managing billing settings{% endif %} - -For security, we recommend making **only a few people** enterprise owners. - -Enterprise owners do not have access to organization settings or content by default, but they can gain access by joining any organization. See [AUTOTITLE](/admin/user-management/managing-organizations-in-your-enterprise/managing-your-role-in-an-organization-owned-by-your-enterprise). - -{% ifversion ghec %} - -## Billing managers - -Billing managers only have access to your enterprise's billing settings. They can view and manage: - -* User licenses -* Usage-based billing -* Other billing settings - -Billing managers do not have access to organization settings or content by default except for internal repositories within an enterprise in which they are a member. - -{% endif %} - -## Enterprise members - -Members of organizations owned by your enterprise are automatically members of the enterprise. - -Enterprise members: - -* Cannot access or configure enterprise settings. -* Can access all repositories with "internal" visibility across any organization in the enterprise. See [AUTOTITLE](/repositories/creating-and-managing-repositories/about-repositories#about-internal-repositories). -* May have different levels of access to various organizations and repositories. To view the resources someone has access to, see [AUTOTITLE](/admin/user-management/managing-users-in-your-enterprise/viewing-people-in-your-enterprise). - -{% ifversion guest-collaborators %} - -## Guest collaborators - -{% data reusables.emus.guest-collaborators-note %} - -{% data reusables.emus.about-guest-collaborators %} - -You may need to update your IdP application to use guest collaborators. See [AUTOTITLE](/admin/managing-accounts-and-repositories/managing-users-in-your-enterprise/enabling-guest-collaborators). - -{% endif %} - -{% ifversion unaffiliated-users %} - -## Unaffiliated users - -Unaffiliated users are people who have been added to your enterprise but aren't members of any organizations. These users: - -* Do not consume a standard {% data variables.product.prodname_enterprise %} license. -* Cannot access private or internal repositories. -* Can be added as members of organizations or enterprise teams. -* Can receive a {% data variables.product.prodname_copilot_short %} license directly from your enterprise. - -You can add unaffiliated users from your identity provider (for {% data variables.product.prodname_emus %}) or by inviting users at the enterprise level (for personal accounts). For personal accounts, see [AUTOTITLE](/admin/managing-accounts-and-repositories/managing-users-in-your-enterprise/invite-users-directly). - -{% endif %} - -## Next steps - -When you have decided which roles your users require, assign the roles to them. See [AUTOTITLE](/admin/managing-accounts-and-repositories/managing-users-in-your-enterprise/assign-roles). diff --git a/content/admin/managing-accounts-and-repositories/managing-users-in-your-enterprise/assign-roles.md b/content/admin/managing-accounts-and-repositories/managing-users-in-your-enterprise/assign-roles.md deleted file mode 100644 index 8fea2e45bef3..000000000000 --- a/content/admin/managing-accounts-and-repositories/managing-users-in-your-enterprise/assign-roles.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -title: Assigning roles to users in an enterprise -intro: Assign roles to govern what people can do in your enterprise. -versions: - ghec: '*' - ghes: '*' -type: how_to -shortTitle: Assign roles ---- - -Users in an enterprise have roles for the enterprise and for organizations where they have access. For more information, see [AUTOTITLE](/admin/overview/about-roles). - -## Assigning enterprise roles - -{% ifversion ghec %} -If you use an **enterprise with personal accounts**: - -* People become enterprise members when they are added as a member or owner of an organization. See [AUTOTITLE](/organizations/managing-membership-in-your-organization/inviting-users-to-join-your-organization). -* You can invite someone to become an enterprise owner or billing manager. See [AUTOTITLE](/admin/user-management/managing-users-in-your-enterprise/inviting-people-to-manage-your-enterprise). -* You can add people as unaffiliated users without adding them to the enterprise. See [AUTOTITLE](/admin/managing-accounts-and-repositories/managing-users-in-your-enterprise/invite-users-directly). - -If you use an **{% data variables.enterprise.prodname_emu_enterprise %}**: - -* You must provision all users through your identity provider (IdP). -* You select each user's enterprise role using your IdP. The role cannot be changed on {% data variables.product.prodname_dotcom %}. -* To assign the guest collaborator role, you may need to update your IdP. - -{% elsif ghes %} - -When a user has joined your {% data variables.product.prodname_ghe_server %} instance, you can: - -* Add the user to an organization. See [AUTOTITLE](/organizations/managing-membership-in-your-organization/adding-people-to-your-organization). -* Invite the user to become an enterprise owner. See [AUTOTITLE](/admin/user-management/managing-users-in-your-enterprise/inviting-people-to-manage-your-enterprise). - -If you provision users with SCIM, you assign each user's enterprise role on your identity provider (IdP). The role cannot be changed on {% data variables.product.prodname_dotcom %}. - -{% endif %} - -## Assigning organization roles - -Organization administrators can assign roles to users and teams in their organization. See [AUTOTITLE](/organizations/managing-peoples-access-to-your-organization-with-roles/using-organization-roles#assigning-an-organization-role). diff --git a/content/admin/managing-accounts-and-repositories/managing-users-in-your-enterprise/create-enterprise-teams.md b/content/admin/managing-accounts-and-repositories/managing-users-in-your-enterprise/create-enterprise-teams.md index 4f5013c94ede..911133de91c3 100644 --- a/content/admin/managing-accounts-and-repositories/managing-users-in-your-enterprise/create-enterprise-teams.md +++ b/content/admin/managing-accounts-and-repositories/managing-users-in-your-enterprise/create-enterprise-teams.md @@ -9,15 +9,21 @@ topics: - User account shortTitle: Create enterprise teams permissions: Enterprise owners -product: '{% data reusables.copilot.direct-assignment-rollout %}' +redirect_from: + - /admin/user-management/managing-users-in-your-enterprise/managing-organization-members-in-your-enterprise + - /admin/managing-accounts-and-repositories/managing-users-in-your-enterprise/managing-organization-members-in-your-enterprise --- -You can create groups of users in your enterprise with enterprise teams. This allows you to simplify licensing by managing {% data variables.product.prodname_copilot_short %} access with team membership. +>[!NOTE] Enterprise teams are in public preview and subject to change. -**Current limitations:** You can create up to 50 teams for a single enterprise and add up to 500 users to each team. +To simplify administration at scale, you can create enterprise teams. {% data reusables.enterprise.enterprise-teams-can %} + +Adding a user to a team grants them the privileges associated with the team. Removing a user from a team removes those privileges, but does not remove the user from the enterprise account. + +{% data reusables.enterprise.enterprise-teams-limits %} -## 1. Find the enterprise teams page +## 1. Navigate to the enterprise teams page {% data reusables.enterprise-accounts.access-enterprise %} {% data reusables.enterprise-accounts.people-tab %} @@ -25,11 +31,16 @@ You can create groups of users in your enterprise with enterprise teams. This al ## 2. Create a team -1. Navigate to the enterprise teams page. See [1. Find the enterprise teams page](#1-find-the-enterprise-teams-page). -1. Click **Create Enterprise team**. -1. Choose the team's name, description, and organization access, then click **Create Enterprise team**. +1. On the enterprise teams page, click **Create Enterprise team**. +1. Choose the team's name, description, and organization access. -Once you have created a team, you can manage the team's membership and licenses. + When you give a team access to organizations, members of the team are added directly to those organizations, without an invitation, and receive the same access as other organization members. + + * Unaffiliated users and outside collaborators in the team become standard enterprise members, meaning they have access to your enterprise's internal repositories and consume a {% data variables.product.prodname_enterprise %} license. + * Team members receive the base level of repository permissions for the organization. + * Organization administrators can give the team additional repository access and assign them organization-level roles, but **cannot** remove any permissions granted by enterprise administrators. + +1. Click **Create Enterprise team**. ## 3. Add users @@ -37,25 +48,21 @@ There are multiple ways to add users to an enterprise team. * [Adding users manually](#adding-users-manually) * [Syncing with an IdP group](#syncing-with-an-idp-group) ({% data variables.product.prodname_emus %} only) -* Using the API +* Using the [AUTOTITLE](/rest/enterprise-teams/enterprise-team-members) -Enterprise teams can contain organization members and unaffiliated users. +Enterprise teams can contain organization members, unaffiliated users, and outside collaborators. ### Adding users manually -1. Navigate to the enterprise teams page. See [1. Find the enterprise teams page](#1-find-the-enterprise-teams-page). -1. Click the team you want to add users to. +1. On the enterprise teams page, click the team you want to add users to. 1. Click **Add members**, then search for and select the users you want to add. 1. Click **Add**. -You can remove users from an enterprise team at any time using the **{% octicon "kebab-horizontal" aria-hidden="true" aria-label="More member actions" %}** menu next to the user's name in the member list. This action does not remove a user from the enterprise account. - ### Syncing with an IdP group If you use {% data variables.product.prodname_emus %}, you can sync membership of an enterprise team to a group in your identity provider. That way, any changes made to the group in the IdP (such as adding or removing a user) will be synced to the enterprise team via SCIM. For details and requirements, see [AUTOTITLE](/admin/managing-iam/provisioning-user-accounts-with-scim/managing-team-memberships-with-identity-provider-groups). -1. Navigate to the enterprise teams page. See [1. Find the enterprise teams page](#1-find-the-enterprise-teams-page). -1. Click the team you want to sync. +1. On the enterprise teams page, click the team you want to sync. 1. Ensure the team contains no manually assigned users. You can remove users by using the **{% octicon "kebab-horizontal" aria-hidden="true" aria-label="More member actions" %}** menu next to the user's name in the member list. 1. Next to the team's name, click **{% octicon "pencil" aria-hidden="true" aria-label="pencil" %} Edit**. 1. Under "Manage members", click **Identity provider group**. @@ -77,3 +84,7 @@ For example: You can assign {% data variables.product.prodname_copilot %} licenses to an enterprise team. This allows you to manage {% data variables.product.prodname_copilot_short %} access through team membership, independent of organizations. Once you have assigned licenses to a team, users will gain or lose access to {% data variables.product.prodname_copilot_short %} when they are added or removed from the team. For instructions, see [AUTOTITLE](/copilot/how-tos/administer-copilot/manage-for-enterprise/manage-access/grant-access#assigning-licenses-to-users-or-teams). + +## 5. Assign roles + +You can assign custom enterprise roles and certain predefined roles to enterprise teams. This allows you to delegate administrative duties to specific teams or provide non-administrators with permissions that will help them work independently. See [AUTOTITLE](/admin/managing-accounts-and-repositories/managing-roles-in-your-enterprise/assign-roles). diff --git a/content/admin/managing-accounts-and-repositories/managing-users-in-your-enterprise/index.md b/content/admin/managing-accounts-and-repositories/managing-users-in-your-enterprise/index.md index ba97a322df72..a983fbfb11a6 100644 --- a/content/admin/managing-accounts-and-repositories/managing-users-in-your-enterprise/index.md +++ b/content/admin/managing-accounts-and-repositories/managing-users-in-your-enterprise/index.md @@ -17,14 +17,11 @@ versions: topics: - Enterprise children: - - /abilities-of-roles - /best-practices-for-user-security - /create-enterprise-teams - /invite-users-directly - - /assign-roles - /inviting-people-to-manage-your-enterprise - /managing-invitations-to-organizations-within-your-enterprise - - /managing-organization-members-in-your-enterprise - /about-reserved-usernames-for-github-enterprise-server - /promoting-or-demoting-a-site-administrator - /managing-support-entitlements-for-your-enterprise diff --git a/content/admin/managing-accounts-and-repositories/managing-users-in-your-enterprise/managing-organization-members-in-your-enterprise.md b/content/admin/managing-accounts-and-repositories/managing-users-in-your-enterprise/managing-organization-members-in-your-enterprise.md deleted file mode 100644 index ef3e8c4d21a7..000000000000 --- a/content/admin/managing-accounts-and-repositories/managing-users-in-your-enterprise/managing-organization-members-in-your-enterprise.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -title: Managing organization members in your enterprise -intro: You can add or remove members from an organization in bulk. -permissions: Enterprise owners can add or remove organization members in bulk. -versions: - feature: enterprise-manage-organization-members -type: how_to -topics: - - Enterprise - - Organizations -shortTitle: Managing organization members -redirect_from: - - /admin/user-management/managing-users-in-your-enterprise/managing-organization-members-in-your-enterprise ---- - -Enterprise members that are added to an organization via the bulk method will not receive an email inviting them to the organization. They are added immediately as a member to the selected organizations. - -Members can also be added or removed from an organization at the organization level. For more information, see {% ifversion ghec %}[AUTOTITLE](/organizations/managing-membership-in-your-organization/inviting-users-to-join-your-organization){% else %}[AUTOTITLE](/organizations/managing-membership-in-your-organization/adding-people-to-your-organization){% endif %} and [AUTOTITLE](/organizations/managing-membership-in-your-organization/removing-a-member-from-your-organization). - -{% data reusables.enterprise-accounts.access-enterprise %} -{% data reusables.enterprise-accounts.people-tab %} -1. Select the checkbox next to each user you want to add or remove. -1. At the top of the member list, select the **X user(s) selected** dropdown menu, then click **Add to organizations** or **Remove from organizations**. - - > [!NOTE] - > * Users will be added as organization members. If the user is already an organization member or organization owner, the privileges will not be modified. - > * Organization owners cannot be removed from the organization via the bulk method. - - ![Screenshot of the list of enterprise members. A dropdown menu, labeled "1 user selected...", is expanded and highlighted with an orange outline.](/assets/images/help/business-accounts/enterprise-add-or-remove-from-org.png) - -1. In the popup, select the organizations you want to add or remove the user from. - - > [!NOTE] - > You can only select organizations where you're an organization owner. - -1. To confirm, click **Add user** or **Remove user**. -1. Optionally, to add or remove multiple users at the same time, select multiple checkboxes. Use the dropdown to select **Add to organizations** or **Remove from organizations**. diff --git a/content/admin/managing-github-apps-for-your-enterprise/adding-and-removing-github-app-managers-in-your-enterprise.md b/content/admin/managing-github-apps-for-your-enterprise/adding-and-removing-github-app-managers-in-your-enterprise.md index cc29110f55d4..d4738b1b72af 100644 --- a/content/admin/managing-github-apps-for-your-enterprise/adding-and-removing-github-app-managers-in-your-enterprise.md +++ b/content/admin/managing-github-apps-for-your-enterprise/adding-and-removing-github-app-managers-in-your-enterprise.md @@ -1,6 +1,6 @@ --- title: Adding and removing GitHub App managers in your enterprise -intro: Enterprise owners can grant or revoke access for a user to manage individual {% data variables.product.prodname_github_apps %} owned by the enterprise. +intro: Enterprise owners can grant or revoke access for a user to manage {% data variables.product.prodname_github_apps %} owned by the enterprise. versions: feature: enterprise-app-manager type: how_to @@ -16,7 +16,12 @@ contentType: other ## About {% data variables.product.prodname_github_app %} managers -Enterprise owners can designate other users in their enterprise as {% data variables.product.prodname_github_app %} managers for individual apps. {% data variables.product.prodname_github_app %} managers can manage the settings of specific {% data variables.product.prodname_github_app %} registrations that are owned by the enterprise. The {% data variables.product.prodname_github_app %} manager role does not grant recipients access to install and uninstall {% data variables.product.prodname_github_apps %} on an enterprise or organization. For more information about the specific app settings that {% data variables.product.prodname_github_app %} managers can control, see [AUTOTITLE](/apps/maintaining-github-apps/modifying-a-github-app). +Enterprise owners can designate other users in their enterprise as {% data variables.product.prodname_github_app %} managers for apps. + +An app manager: + +* Can manage the settings for a {% data variables.product.prodname_github_app %} registration that is owned by the enterprise. For the specific app settings that {% data variables.product.prodname_github_app %} managers can control, see [AUTOTITLE](/apps/maintaining-github-apps/modifying-a-github-app). +* Cannot install and uninstall {% data variables.product.prodname_github_apps %} on an enterprise or organization. When an enterprise app manager adds permissions to a {% data variables.product.prodname_github_app %}, the update is automatically accepted in all organizations where the app manager is also an organization owner. When an enterprise owner adds permissions to a {% data variables.product.prodname_github_app %}, the update is automatically accepted in all organizations regardless of their organization membership. @@ -42,6 +47,10 @@ The user must be a member of the enterprise to be granted {% data variables.prod 1. In the left sidebar, click **App managers**. 1. Under "App managers", next to the person you want to remove {% data variables.product.prodname_github_app %} manager permissions from, click **Revoke**. +## Granting the ability to manage all enterprise-owned apps + +Enterprise app manager is a predefined role that grants access to all app registrations owned by the enterprise. See [AUTOTITLE](/admin/managing-accounts-and-repositories/managing-roles-in-your-enterprise/assign-roles). + ## Further reading * [AUTOTITLE](/admin/managing-your-enterprise-account/creating-github-apps-for-your-enterprise) diff --git a/content/copilot/concepts/billing/copilot-requests.md b/content/copilot/concepts/billing/copilot-requests.md index 0480d36fe319..bf068186bbce 100644 --- a/content/copilot/concepts/billing/copilot-requests.md +++ b/content/copilot/concepts/billing/copilot-requests.md @@ -93,7 +93,7 @@ Each model has a premium request multiplier, based on its complexity and resourc {% data variables.copilot.copilot_gpt_5_mini %}, {% data variables.copilot.copilot_gpt_41 %} and {% data variables.copilot.copilot_gpt_4o %} are the included models, and do not consume any premium requests if you are on a **paid plan**. -If you use **{% data variables.copilot.copilot_free_short %}**, you have access to a limited number of models, and each model will consume one premium request when used. For example, if you make a request using the {% data variables.copilot.copilot_gemini_flash %} model, your interaction will consume **one premium request**, not 0.25 premium requests. +If you use **{% data variables.copilot.copilot_free_short %}**, you have access to a limited number of models, and each model will consume one premium request when used. {% data reusables.copilot.model-multipliers %} @@ -101,6 +101,6 @@ If you use **{% data variables.copilot.copilot_free_short %}**, you have access Premium request usage is based on the model’s multiplier and the feature you’re using. For example: -* **Using {% data variables.copilot.copilot_claude_opus %} in {% data variables.copilot.copilot_chat_short %}**: With a 10× multiplier, one interaction counts as 10 premium requests. +* **Using {% data variables.copilot.copilot_claude_opus_41 %} in {% data variables.copilot.copilot_chat_short %}**: With a 10× multiplier, one interaction counts as 10 premium requests. * **Using {% data variables.copilot.copilot_gpt_5_mini %} on {% data variables.copilot.copilot_free_short %}**: Each interaction counts as 1 premium request. * **Using {% data variables.copilot.copilot_gpt_5_mini %} on a paid plan**: No premium requests are consumed. diff --git a/content/copilot/how-tos/troubleshoot-copilot/troubleshoot-common-issues.md b/content/copilot/how-tos/troubleshoot-copilot/troubleshoot-common-issues.md index 6761b8ef8a1b..f279fb32e186 100644 --- a/content/copilot/how-tos/troubleshoot-copilot/troubleshoot-common-issues.md +++ b/content/copilot/how-tos/troubleshoot-copilot/troubleshoot-common-issues.md @@ -67,7 +67,7 @@ This is a known issue and our team is working towards a fix. For more informatio This error suggests that you have exceeded the rate limit for {% data variables.product.prodname_copilot_short %} requests. {% data variables.product.github %} uses rate limits to ensure everyone has fair access to the {% data variables.product.prodname_copilot_short %} service and to protect against abuse. -Most people see rate limiting for preview models, like OpenAI’s {% data variables.copilot.copilot_o3 %} and {% data variables.copilot.copilot_o4_mini %}, which are rate-limited due to limited capacity. +Most people see rate limiting for preview models, due to limited capacity. Service-level request rate limits ensure high service quality for all {% data variables.product.prodname_copilot_short %} users and should not affect typical or even deeply engaged {% data variables.product.prodname_copilot_short %} usage. We are aware of some use cases that are affected by it. {% data variables.product.github %} is iterating on {% data variables.product.prodname_copilot_short %}’s rate-limiting heuristics to ensure it doesn’t block legitimate use cases. diff --git a/content/copilot/reference/ai-models/model-comparison.md b/content/copilot/reference/ai-models/model-comparison.md index 8d38b2a40017..541cb4417fb9 100644 --- a/content/copilot/reference/ai-models/model-comparison.md +++ b/content/copilot/reference/ai-models/model-comparison.md @@ -33,17 +33,12 @@ Use this table to find a suitable model quickly, see more detail in the sections | {% data variables.copilot.copilot_gpt_5_codex %} | General-purpose coding and writing | Fast, accurate code completions and explanations | Agent mode | [{% data variables.copilot.copilot_gpt_5_codex %} model card](https://cdn.openai.com/pdf/97cc5669-7a25-4e63-b15f-5fd5bdc4d149/gpt-5-codex-system-card.pdf) | | {% data variables.copilot.copilot_gpt_5_mini %} | General-purpose coding and writing | Fast, accurate code completions and explanations | Agent mode, reasoning, vision | [{% data variables.copilot.copilot_gpt_5_mini %} model card](https://cdn.openai.com/gpt-5-system-card.pdf) | | {% data variables.copilot.copilot_gpt_5 %} | Deep reasoning and debugging | Multi-step problem solving and architecture-level code analysis | Reasoning | [{% data variables.copilot.copilot_gpt_5 %} model card](https://cdn.openai.com/gpt-5-system-card.pdf) | -| {% data variables.copilot.copilot_o3 %} | Deep reasoning and debugging | Multi-step problem solving and architecture-level code analysis | Reasoning | [{% data variables.copilot.copilot_o3 %} model card](https://openai.com/index/o3-o4-mini-system-card/) | -| {% data variables.copilot.copilot_o4_mini %} | Fast help with simple or repetitive tasks | Fast, reliable answers to lightweight coding questions | Lower latency | [{% data variables.copilot.copilot_o4_mini %} model card](https://openai.com/index/o3-o4-mini-system-card/) | | {% data variables.copilot.copilot_claude_haiku_45 %} | Fast help with simple or repetitive tasks | Fast, reliable answers to lightweight coding questions | Agent mode | Not available | | {% data variables.copilot.copilot_claude_sonnet_45 %} | General-purpose coding and agent tasks | Complex problem-solving challenges, sophisticated reasoning | Agent mode | [{% data variables.copilot.copilot_claude_sonnet_45 %} model card](https://assets.anthropic.com/m/12f214efcc2f457a/original/Claude-Sonnet-4-5-System-Card.pdf) | | {% data variables.copilot.copilot_claude_opus_41 %} | Deep reasoning and debugging | Complex problem-solving challenges, sophisticated reasoning | Reasoning, vision | [{% data variables.copilot.copilot_claude_opus_41 %} model card](https://assets.anthropic.com/m/4c024b86c698d3d4/original/Claude-4-1-System-Card.pdf) | -| {% data variables.copilot.copilot_claude_opus %} | Deep reasoning and debugging | Complex problem-solving challenges, sophisticated reasoning | Reasoning, vision | [{% data variables.copilot.copilot_claude_opus %} model card](https://www-cdn.anthropic.com/6be99a52cb68eb70eb9572b4cafad13df32ed995.pdf) | | {% data variables.copilot.copilot_claude_sonnet_35 %} | Fast help with simple or repetitive tasks | Quick responses for code, syntax, and documentation | Agent mode, vision | [{% data variables.copilot.copilot_claude_sonnet_35 %} model card](https://www-cdn.anthropic.com/fed9cc193a14b84131812372d8d5857f8f304c52/Model_Card_Claude_3_Addendum.pdf) | -| {% data variables.copilot.copilot_claude_sonnet_37 %} | Deep reasoning and debugging | Structured reasoning across large, complex codebases | Agent mode, vision | [{% data variables.copilot.copilot_claude_sonnet_37 %} model card](https://assets.anthropic.com/m/785e231869ea8b3b/original/claude-3-7-sonnet-system-card.pdf) | | {% data variables.copilot.copilot_claude_sonnet_40 %} | Deep reasoning and debugging | Performance and practicality, perfectly balanced for coding workflows | Agent mode, vision | [{% data variables.copilot.copilot_claude_sonnet_40 %} model card](https://www-cdn.anthropic.com/6be99a52cb68eb70eb9572b4cafad13df32ed995.pdf) | | {% data variables.copilot.copilot_gemini_25_pro %} | Deep reasoning and debugging | Complex code generation, debugging, and research workflows | Reasoning, vision | [{% data variables.copilot.copilot_gemini_25_pro %} model card](https://storage.googleapis.com/model-cards/documents/gemini-2.5-pro.pdf) | -| {% data variables.copilot.copilot_gemini_flash %} | Working with visuals (diagrams, screenshots) | Real-time responses and visual reasoning for UI and diagram-based tasks | Vision | [{% data variables.copilot.copilot_gemini_flash %} model card](https://storage.googleapis.com/model-cards/documents/gemini-2-flash.pdf) | | {% data variables.copilot.copilot_grok_code %} | General-purpose coding and writing | Fast, accurate code completions and explanations | Agent mode | [{% data variables.copilot.copilot_grok_code %} model card](https://data.x.ai/2025-08-20-grok-4-model-card.pdf) | | {% data variables.copilot.copilot_qwen_25 %} | General-purpose coding and writing | Code generation, reasoning, and code repair / debugging | Reasoning | [{% data variables.copilot.copilot_qwen_25 %} model card](https://arxiv.org/pdf/2409.12186) | @@ -55,9 +50,6 @@ Use these models for common development tasks that require a balance of quality, |-------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------| | {% data variables.copilot.copilot_gpt_5_codex %} | Delivers higher-quality code on complex engineering tasks like features, tests, debugging, refactors, and reviews without lengthy instructions. | | {% data variables.copilot.copilot_gpt_5_mini %} | Reliable default for most coding and writing tasks. Fast, accurate, and works well across languages and frameworks. | -| {% data variables.copilot.copilot_claude_sonnet_37 %} | Produces clear, structured output. Follows formatting instructions and maintains consistent style. | -| {% data variables.copilot.copilot_gemini_flash %} | Fast and cost-effective. Well suited for quick questions, short code snippets, and lightweight writing tasks. | -| {% data variables.copilot.copilot_o4_mini %} | Optimized for speed and cost efficiency. Ideal for real-time suggestions with low usage overhead. | | {% data variables.copilot.copilot_grok_code %} | Specialized for coding tasks. Performs well on code generation, and debugging across multiple languages. | ### When to use these models @@ -81,10 +73,8 @@ These models are optimized for speed and responsiveness. They’re ideal for qui | Model | Why it's a good fit | |-------------------------------------------------------|------------------------------------------------------------------------------------------------------------| -| {% data variables.copilot.copilot_o4_mini %} | A quick and cost-effective model for repetitive or simple coding tasks. Offers clear, concise suggestions. | | {% data variables.copilot.copilot_claude_haiku_45 %} | Balances fast responses with quality output. Ideal for small tasks and lightweight code explanations. | | {% data variables.copilot.copilot_claude_sonnet_35 %} | Balances fast responses with quality output. Ideal for small tasks and lightweight code explanations. | -| {% data variables.copilot.copilot_gemini_flash %} | Extremely low latency and multimodal support (where available). Great for fast, interactive feedback. | ### When to use these models @@ -110,11 +100,8 @@ These models are designed for tasks that require step-by-step reasoning, complex |-------------------------------------------------------|---------------------------------------------------------------------------------------------------------------| | {% data variables.copilot.copilot_gpt_5_mini %} | Delivers deep reasoning and debugging with faster responses and lower resource usage than GPT-5. Ideal for interactive sessions and step-by-step code analysis. | | {% data variables.copilot.copilot_gpt_5 %} | Great at complex reasoning, code analysis, and technical decision-making. | -| {% data variables.copilot.copilot_o3 %} | Strong at algorithm design, system debugging, and architecture decisions. Balances performance and reasoning. | -| {% data variables.copilot.copilot_claude_sonnet_37 %} | Provides hybrid reasoning that adapts to both fast tasks and deeper thinking. | | {% data variables.copilot.copilot_claude_sonnet_40 %} | Improves on 3.7 with more reliable completions and smarter reasoning under pressure. | | {% data variables.copilot.copilot_claude_opus_41 %} | Anthropic’s most powerful model. Improves on {% data variables.copilot.copilot_claude_opus %}. | -| {% data variables.copilot.copilot_claude_opus %} | Strong at strategy, debugging, and multi-layered logic. | | {% data variables.copilot.copilot_gemini_25_pro %} | Advanced reasoning across long contexts and scientific or technical analysis. | ### When to use these models @@ -139,9 +126,7 @@ Use these models when you want to ask questions about screenshots, diagrams, UI | Model | Why it's a good fit | |-------|---------------------| | {% data variables.copilot.copilot_gpt_5_mini %} | Reliable default for most coding and writing tasks. Fast, accurate, and supports multimodal input for visual reasoning tasks. Works well across languages and frameworks. | -| {% data variables.copilot.copilot_claude_opus %} | Anthropic’s most powerful model. Strong at strategy, debugging, and multi-layered logic. | | {% data variables.copilot.copilot_claude_sonnet_40 %} | Improves on 3.7 with more reliable completions and smarter reasoning under pressure. | -| {% data variables.copilot.copilot_gemini_flash %} | Fast, multimodal model optimized for real-time interaction. Useful for feedback on diagrams, visual prototypes, and UI layouts. | | {% data variables.copilot.copilot_gemini_25_pro %} | Deep reasoning and debugging, ideal for complex code generation, debugging, and research workflows. | ### When to use these models diff --git a/content/copilot/reference/ai-models/model-hosting.md b/content/copilot/reference/ai-models/model-hosting.md index 822d57575240..3930e1016551 100644 --- a/content/copilot/reference/ai-models/model-hosting.md +++ b/content/copilot/reference/ai-models/model-hosting.md @@ -22,12 +22,10 @@ Used for: * {% data variables.copilot.copilot_gpt_5_codex %} (supported in {% data variables.product.prodname_vscode %} v1.104.1 or higher) * {% data variables.copilot.copilot_gpt_5_mini %} * {% data variables.copilot.copilot_gpt_5 %} -* {% data variables.copilot.copilot_o3 %} -* {% data variables.copilot.copilot_o4_mini %} {% data variables.copilot.copilot_gpt_41 %} is hosted by {% data variables.product.github %}'s Azure tenant when used in {% data variables.product.prodname_copilot %}. -{% data variables.copilot.copilot_gpt_5_codex %}, {% data variables.copilot.copilot_gpt_5 %}, {% data variables.copilot.copilot_gpt_5_mini %}, {% data variables.copilot.copilot_o3 %} and {% data variables.copilot.copilot_o4_mini %} models are hosted by OpenAI and {% data variables.product.github %}'s Azure tenant. OpenAI makes the [following data commitment](https://openai.com/enterprise-privacy/): _We [OpenAI] do not train our models on your business data by default_. {% data variables.product.github %} maintains a [zero data retention agreement](https://platform.openai.com/docs/guides/your-data) with OpenAI. +{% data variables.copilot.copilot_gpt_5_codex %}, {% data variables.copilot.copilot_gpt_5 %}, and {% data variables.copilot.copilot_gpt_5_mini %} models are hosted by OpenAI and {% data variables.product.github %}'s Azure tenant. OpenAI makes the [following data commitment](https://openai.com/enterprise-privacy/): _We [OpenAI] do not train our models on your business data by default_. {% data variables.product.github %} maintains a [zero data retention agreement](https://platform.openai.com/docs/guides/your-data) with OpenAI. When using OpenAI's models, input requests and output responses 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. @@ -38,13 +36,10 @@ Used for: * {% data variables.copilot.copilot_claude_haiku_45 %} * {% data variables.copilot.copilot_claude_sonnet_45 %} * {% data variables.copilot.copilot_claude_opus_41 %} -* {% data variables.copilot.copilot_claude_opus %} * {% data variables.copilot.copilot_claude_sonnet_35 %} -* {% data variables.copilot.copilot_claude_sonnet_37 %} -* {% data variables.copilot.copilot_claude_sonnet_37 %} Thinking * {% data variables.copilot.copilot_claude_sonnet_40 %} -{% data variables.copilot.copilot_claude_haiku_45 %} and {% data variables.copilot.copilot_claude_opus_41 %} are hosted by Anthropic PBC. {% data variables.copilot.copilot_claude_opus %} and {% data variables.copilot.copilot_claude_sonnet_40 %} are hosted by Anthropic PBC and Google Cloud Platform. {% data variables.copilot.copilot_claude_sonnet_45 %} and {% data variables.copilot.copilot_claude_sonnet_37 %} are hosted by Amazon Web Services, Anthropic PBC, and Google Cloud Platform. {% data variables.copilot.copilot_claude_sonnet_35 %} is hosted exclusively by Amazon Web Services. {% data variables.product.github %} has provider agreements in place to ensure data is not used for training. Additional details for each provider are included below: +{% data variables.copilot.copilot_claude_haiku_45 %} and {% data variables.copilot.copilot_claude_opus_41 %} are hosted by Anthropic PBC. {% data variables.copilot.copilot_claude_sonnet_40 %} is hosted by Anthropic PBC and Google Cloud Platform. {% data variables.copilot.copilot_claude_sonnet_45 %} is hosted by Amazon Web Services, Anthropic PBC, and Google Cloud Platform. {% data variables.copilot.copilot_claude_sonnet_35 %} is hosted exclusively by Amazon Web Services. {% data variables.product.github %} has provider agreements in place to ensure data is not used for training. Additional details for each provider are included below: * Amazon Bedrock: Amazon makes the [following data commitments](https://docs.aws.amazon.com/bedrock/latest/userguide/data-protection.html): _Amazon Bedrock doesn't store or log your prompts and completions. Amazon Bedrock doesn't use your prompts and completions to train any AWS models and doesn't distribute them to third parties_. * Anthropic PBC: {% data variables.product.github %} maintains a [zero data retention agreement](https://privacy.anthropic.com/en/articles/8956058-i-have-a-zero-retention-agreement-with-anthropic-what-products-does-it-apply-to) with Anthropic. @@ -59,9 +54,8 @@ When using {% data variables.copilot.copilot_claude %}, input prompts and output Used for: * {% data variables.copilot.copilot_gemini_25_pro %} -* {% data variables.copilot.copilot_gemini_flash %} -{% 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._ +{% data variables.product.prodname_copilot %} uses {% 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). diff --git a/content/copilot/reference/ai-models/supported-models.md b/content/copilot/reference/ai-models/supported-models.md index 5556c5fd004d..531da2799a94 100644 --- a/content/copilot/reference/ai-models/supported-models.md +++ b/content/copilot/reference/ai-models/supported-models.md @@ -39,25 +39,25 @@ This table lists the AI models available in {% data variables.product.prodname_c {% rowheaders %} -| Model name | Provider | Release status | Agent mode | Ask mode | Edit mode | -|----------------------------------------------------------------|-----------|-----------------------------------------------------------------|---------------------------------------------|----------------------|---------------| -| {% data variables.copilot.copilot_gpt_41 %} | OpenAI | GA | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | -| {% data variables.copilot.copilot_gpt_5_codex %} | OpenAI | {% data variables.release-phases.public_preview_caps %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | -| {% data variables.copilot.copilot_gpt_5_mini %} | OpenAI | GA | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | -| {% data variables.copilot.copilot_gpt_5 %} | OpenAI | GA | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | -| {% data variables.copilot.copilot_o3 %} | OpenAI | {% data variables.release-phases.closing_down_caps %}: 2025-10-23 | {% octicon "x" aria-label="Not included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | -| {% data variables.copilot.copilot_o4_mini %} | OpenAI | {% data variables.release-phases.closing_down_caps %}: 2025-10-23 | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | -| {% data variables.copilot.copilot_claude_haiku_45 %} | Anthropic | GA | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | -| {% data variables.copilot.copilot_claude_sonnet_45 %} | Anthropic | GA | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | -| {% data variables.copilot.copilot_claude_opus_41 %} | Anthropic | GA | {% octicon "x" aria-label="Not included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | -| {% data variables.copilot.copilot_claude_opus %} | Anthropic | {% data variables.release-phases.closing_down_caps %}: 2025-10-23 | {% octicon "x" aria-label="Not included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | -| {% data variables.copilot.copilot_claude_sonnet_35 %} | Anthropic | {% data variables.release-phases.closing_down_caps %}: 2025-11-06 | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | -| {% data variables.copilot.copilot_claude_sonnet_37 %} | Anthropic | {% data variables.release-phases.closing_down_caps %}: 2025-10-23 | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | -| {% data variables.copilot.copilot_claude_sonnet_37 %} Thinking | Anthropic | {% data variables.release-phases.closing_down_caps %}: 2025-10-23 | {% octicon "x" aria-label="Not included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | -| {% data variables.copilot.copilot_claude_sonnet_40 %} | Anthropic | GA | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | -| {% data variables.copilot.copilot_gemini_25_pro %} | Google | GA | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | -| {% data variables.copilot.copilot_gemini_flash %} | Google | {% data variables.release-phases.closing_down_caps %}: 2025-10-23 | {% octicon "x" aria-label="Not included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | -| {% data variables.copilot.copilot_grok_code %} | xAI | GA | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | +| Model name | Provider | Release status | Agent mode | Ask mode | Edit mode | +|--------------------------------------------------------|-----------|----------------------------|------------|----------|-----------| +| {% for model in tables.copilot.model-release-status %} | +| {{ model.name }} | {{ model.provider }} | {{ model.release_status }} | {% if model.agent_mode == true %}{% octicon "check" aria-label="Included" %}{% else %}{% octicon "x" aria-label="Not included" %}{% endif %} | {% if model.ask_mode == true %}{% octicon "check" aria-label="Included" %}{% else %}{% octicon "x" aria-label="Not included" %}{% endif %} | {% if model.edit_mode == true %}{% octicon "check" aria-label="Included" %}{% else %}{% octicon "x" aria-label="Not included" %}{% endif %} | +| {% endfor %} | + +{% endrowheaders %} + +## Model retirement history + +The following table lists AI models that have been retired from {% data variables.product.prodname_copilot_short %}, along with their retirement dates and suggested alternatives. + +{% rowheaders %} + +| Model name | Retired date | Suggested alternative | +|-------------------------------------------------------------|-----------------------------|-----------------------------------| +| {% for model in tables.copilot.model-deprecation-history %} | +| {{ model.name }} | {{ model.retirement_date }} | {{ model.suggested_alternative }} | +| {% endfor %} | {% endrowheaders %} @@ -71,25 +71,11 @@ The following table shows which models are available in each client. {% rowheaders %} -| Model | {% data variables.product.prodname_dotcom_the_website %} | {% data variables.product.prodname_vscode %} | {% data variables.product.prodname_vs %} | Eclipse | Xcode | JetBrains IDEs | -|----------------------------------------------------------------|----------------------------------------------------------|---------------------------------------------------|---------------------------------------------|---------------------------------------------|---------------------------------------------|---------------------------------------------| -| {% data variables.copilot.copilot_gpt_41 %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | -| {% data variables.copilot.copilot_gpt_5_codex %} | {% octicon "x" aria-label="Not included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "x" aria-label="Not included" %} | {% octicon "x" aria-label="Not included" %} | {% octicon "x" aria-label="Not included" %} | {% octicon "x" aria-label="Not included" %} | -| {% data variables.copilot.copilot_gpt_5_mini %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | -| {% data variables.copilot.copilot_gpt_5 %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | -| {% data variables.copilot.copilot_o3 %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "x" aria-label="Not included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | -| {% data variables.copilot.copilot_o4_mini %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "x" aria-label="Not included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | -| {% data variables.copilot.copilot_claude_haiku_45 %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | -| {% data variables.copilot.copilot_claude_sonnet_45 %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | -| {% data variables.copilot.copilot_claude_opus_41 %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | -| {% data variables.copilot.copilot_claude_opus %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | -| {% data variables.copilot.copilot_claude_sonnet_35 %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | -| {% data variables.copilot.copilot_claude_sonnet_37 %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | -| {% data variables.copilot.copilot_claude_sonnet_37 %} Thinking | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | -| {% data variables.copilot.copilot_claude_sonnet_40 %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | -| {% data variables.copilot.copilot_gemini_25_pro %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | -| {% data variables.copilot.copilot_gemini_flash %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | -| {% data variables.copilot.copilot_grok_code %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | +| Model | {% data variables.product.prodname_dotcom_the_website %} | {% data variables.product.prodname_vscode %} | {% data variables.product.prodname_vs %} | Eclipse | Xcode | JetBrains IDEs | +|--------|-----------------------------------------------------------|-----------------------------------------------|-------------------------------------------|----------|--------|----------------| +| {% for model in tables.copilot.model-supported-clients %} | +| {{ model.name }} | {% if model.dotcom == true %}{% octicon "check" aria-label="Included" %}{% else %}{% octicon "x" aria-label="Not included" %}{% endif %} | {% if model.vscode == true %}{% octicon "check" aria-label="Included" %}{% else %}{% octicon "x" aria-label="Not included" %}{% endif %} | {% if model.vs == true %}{% octicon "check" aria-label="Included" %}{% else %}{% octicon "x" aria-label="Not included" %}{% endif %} | {% if model.eclipse == true %}{% octicon "check" aria-label="Included" %}{% else %}{% octicon "x" aria-label="Not included" %}{% endif %} | {% if model.xcode == true %}{% octicon "check" aria-label="Included" %}{% else %}{% octicon "x" aria-label="Not included" %}{% endif %} | {% if model.jetbrains == true %}{% octicon "check" aria-label="Included" %}{% else %}{% octicon "x" aria-label="Not included" %}{% endif %} | +| {% endfor %} | {% endrowheaders %} diff --git a/content/copilot/tutorials/compare-ai-models.md b/content/copilot/tutorials/compare-ai-models.md index 04adc10a165f..10616b3c1444 100644 --- a/content/copilot/tutorials/compare-ai-models.md +++ b/content/copilot/tutorials/compare-ai-models.md @@ -79,9 +79,9 @@ def grant_editor_access(user_id, doc_id): * {% data variables.copilot.copilot_gpt_41 %} can recognize the pattern and provide a clear, concise explanation. * The task doesn't require deep reasoning or complex logic. -## {% data variables.copilot.copilot_o4_mini %} +## {% data variables.copilot.copilot_gpt_5_mini %} -OpenAI {% data variables.copilot.copilot_o4_mini %} is a fast, cost-efficient model designed for simple or repetitive coding tasks. It delivers reliable, concise answers with very low latency, making it ideal for real-time suggestions and lightweight development workflows. {% data variables.copilot.copilot_o4_mini %} is optimized for speed and responsiveness, so you can quickly iterate on small code changes or get instant feedback on straightforward prompts. +OpenAI {% data variables.copilot.copilot_gpt_5_mini %} is a fast, cost-efficient model designed for simple or repetitive coding tasks. It delivers reliable, concise answers with very low latency, making it ideal for real-time suggestions and lightweight development workflows. {% data variables.copilot.copilot_gpt_5_mini %} is optimized for speed and responsiveness, so you can quickly iterate on small code changes or get instant feedback on straightforward prompts. ### Example scenario @@ -117,15 +117,15 @@ active_users_sorted = sorted(active_users, key=lambda user: user["signup_date"]) print(active_users_sorted) ``` -### Why {% data variables.copilot.copilot_o4_mini %} is a good fit +### Why {% data variables.copilot.copilot_gpt_5_mini %} is a good fit * The task is straightforward and benefits from fast, low-latency responses. -* {% data variables.copilot.copilot_o4_mini %} is optimized for cost and speed, making it ideal for quick edits, prototyping, and utility code. +* {% data variables.copilot.copilot_gpt_5_mini %} is optimized for cost and speed, making it ideal for quick edits, prototyping, and utility code. * Use this model when you want reliable answers for simple coding questions without waiting for unnecessary depth. -## {% data variables.copilot.copilot_gemini_flash %} +## {% data variables.copilot.copilot_gpt_5 %} -{% data reusables.copilot.model-use-cases.gemini-20-flash %} +{% data reusables.copilot.model-use-cases.gpt-5 %} ### Example scenario @@ -169,7 +169,7 @@ class Cart: return Order("", None, 0) ``` -### Why {% data variables.copilot.copilot_gemini_flash %} is a good fit +### Why {% data variables.copilot.copilot_gpt_5 %} is a good fit * It can interpret visual assets, such as UML diagrams, wireframes, or flowcharts, to generate code scaffolding or suggest architecture. * It can be useful for reviewing screenshots of UI layouts or form designs and generating. @@ -189,9 +189,9 @@ For a complete walkthrough of the scenario, see [AUTOTITLE](/copilot/tutorials/w * It performs well on everyday coding tasks like test generation, boilerplate scaffolding, and validation logic. * The task leans into multi-step reasoning, but still stays within the confidence zone of a less advanced model because the logic isn’t too deep. -## {% data variables.copilot.copilot_claude_sonnet_37 %} +## {% data variables.copilot.copilot_claude_sonnet_45 %} -{% data reusables.copilot.model-use-cases.claude-37-sonnet %} +{% data reusables.copilot.model-use-cases.claude-sonnet-45 %} ### Example scenario @@ -199,9 +199,9 @@ Consider a scenario where you're modernizing a legacy COBOL application by rewri For a complete walkthrough of the scenario, see [AUTOTITLE](/copilot/tutorials/modernizing-legacy-code-with-github-copilot). -### Why {% data variables.copilot.copilot_claude_sonnet_37 %} is a good fit +### Why {% data variables.copilot.copilot_claude_sonnet_45 %} is a good fit -* {% data variables.copilot.copilot_claude_sonnet_37 %} handles complex context well, making it suited for workflows that span multiple files or languages. +* {% data variables.copilot.copilot_claude_sonnet_45 %} handles complex context well, making it suited for workflows that span multiple files or languages. * Its hybrid reasoning architecture allows it to switch between quick answers and deeper, step-by-step problem-solving. ## Further reading diff --git a/content/get-started/learning-about-github/access-permissions-on-github.md b/content/get-started/learning-about-github/access-permissions-on-github.md index dc5ec91de099..b4fa2688b176 100644 --- a/content/get-started/learning-about-github/access-permissions-on-github.md +++ b/content/get-started/learning-about-github/access-permissions-on-github.md @@ -38,17 +38,9 @@ Organization members can have _owner_{% ifversion fpt or ghec %}, _billing manag ## Enterprise accounts -{% ifversion fpt %} -{% data reusables.gated-features.enterprise-accounts %} +Enterprise accounts have a range of predefined roles that define users' access to the enterprise settings. You can also create custom enterprise roles to define your own sets of fine-grained permissions. -For more information about permissions for enterprise accounts, see [the {% data variables.product.prodname_ghe_cloud %} documentation](/enterprise-cloud@latest/get-started/learning-about-github/access-permissions-on-github). -{% else %} -_Enterprise owners_ have ultimate power over the enterprise account and can take every action in the enterprise account. _Billing managers_ can manage your enterprise account's billing settings. Members and outside collaborators of organizations owned by your enterprise account are automatically members of the enterprise account, although they have no access to the enterprise account itself or its settings. For more information, see [AUTOTITLE](/admin/managing-accounts-and-repositories/managing-users-in-your-enterprise/roles-in-an-enterprise). - -{% ifversion ghec %} -If an enterprise uses {% data variables.product.prodname_emus %}, members are provisioned as new personal accounts on {% data variables.product.github %} and are fully managed by the identity provider. The {% data variables.enterprise.prodname_managed_users %} have read-only access to repositories that are not a part of their enterprise and cannot interact with users that are not also members of the enterprise. Within the organizations owned by the enterprise, the {% data variables.enterprise.prodname_managed_users %} can be granted the same granular access levels available for regular organizations. For more information, see [AUTOTITLE](/admin/identity-and-access-management/understanding-iam-for-enterprises/about-enterprise-managed-users). -{% endif %} -{% endif %} +For more information, see {% ifversion fpt %}[AUTOTITLE](/enterprise-cloud@latest/admin/managing-accounts-and-repositories/managing-users-in-your-enterprise/abilities-of-roles) in the {% data variables.product.prodname_ghe_cloud %} documentation{% else %}[AUTOTITLE](/admin/managing-accounts-and-repositories/managing-users-in-your-enterprise/abilities-of-roles){% endif %}. ## Further reading diff --git a/content/site-policy/acceptable-use-policies/github-child-sexual-exploitation-or-abuse.md b/content/site-policy/acceptable-use-policies/github-child-sexual-exploitation-or-abuse.md new file mode 100644 index 000000000000..bcbe55efd388 --- /dev/null +++ b/content/site-policy/acceptable-use-policies/github-child-sexual-exploitation-or-abuse.md @@ -0,0 +1,24 @@ +--- +title: GitHub Child Sexual Exploitation or Abuse +shortTitle: CSAM Policy +versions: + fpt: '*' +topics: + - Policy + - Legal +redirect_from: + - /csam + - /github/site-policy/github-child-exploitation-or-abuse +--- + +GitHub does not allow any content or conduct on GitHub that exploits, harms, or endangers children, and we have a zero-tolerance policy for child sexual exploitation or abuse material (CSAM). This means you may not engage in activities including but not limited to: + +* Uploading, linking to, or otherwise distributing or promoting CSAM +* Depicting sexualized minors in any form, including textual fantasies, cartoons or drawings, simulations, or AI-generated content +* Soliciting or advertising CSAM or services involving the exploitation of children +* Engaging in or facilitating child grooming, sextortion, or trafficking of minors +* Glorifying, praising, supporting, or instructing in the abuse of children + +GitHub may use automated tools, such as hash matching, as well as human review to detect and remove CSAM. GitHub will swiftly remove any such content once identified and may take additional action, including account suspension and reporting illegal activity to the National Center for Missing & Exploited Children or other law enforcement authorities. + +If you encounter any content that you believe violates this policy, please report it immediately using our [Reporting Abuse](/communities/maintaining-your-safety-on-github/reporting-abuse-or-spam) instructions. diff --git a/content/site-policy/acceptable-use-policies/github-hate-speech-and-discrimination.md b/content/site-policy/acceptable-use-policies/github-hate-speech-and-discrimination.md index 5be67a60d5f0..2136dd64a9ee 100644 --- a/content/site-policy/acceptable-use-policies/github-hate-speech-and-discrimination.md +++ b/content/site-policy/acceptable-use-policies/github-hate-speech-and-discrimination.md @@ -14,7 +14,7 @@ redirect_from: GitHub does not tolerate speech that attacks or promotes hate toward an individual or group of people on the basis of who they are, including age, body size, ability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, sexual identity, or sexual orientation. This includes: * Mocking, attacking, or excluding a person or group based on their beliefs or the characteristics listed above -* Displaying clear affiliation or identification with known terrorist or violent extremist organizations +* Displaying clear affiliation or identification with known [terrorist or violent extremist organizations](/site-policy/acceptable-use-policies/github-terrorism-and-violent-extremism) * Supporting or promoting hate groups or hate-based conspiracy theories * Sharing symbols or images synonymous with hate * Using harmful stereotypes, slurs, or dehumanizing speech diff --git a/content/site-policy/acceptable-use-policies/github-misinformation-and-disinformation.md b/content/site-policy/acceptable-use-policies/github-misinformation-and-disinformation.md index 4e95f298bfee..3b919ead9496 100644 --- a/content/site-policy/acceptable-use-policies/github-misinformation-and-disinformation.md +++ b/content/site-policy/acceptable-use-policies/github-misinformation-and-disinformation.md @@ -1,6 +1,6 @@ --- title: GitHub Misinformation and Disinformation -shortTitle: Misinformation and Disinformation +shortTitle: Disinformation Policy versions: fpt: '*' topics: @@ -18,7 +18,3 @@ You may not post content that presents a distorted view of reality, whether it i * Unsubstantiated claims that could promote hate or targeted harassment of specific groups of people We encourage active participation in the expression of ideas, perspectives, and experiences and may not be in a position to dispute personal accounts or observations. We generally allow parody and satire that is in line with our [Acceptable Use Policies](/site-policy/acceptable-use-policies/github-acceptable-use-policies), and we consider context to be important in how information is received and understood. When reviewing content under this policy, GitHub will consider the impact of various factors that may help to orient the viewer, such as whether the content has been provided with clear disclaimers, citations to credible sources, or includes other details that clarify the accuracy of the information being shared. - -## Synthetic & Manipulated Media Tools - -GitHub does not allow any projects that are designed for, encourage, promote, support, or suggest in any way the use of synthetic or manipulated media for the creation of non-consensual intimate imagery or any content that would constitute misinformation or disinformation under this policy. diff --git a/content/site-policy/acceptable-use-policies/github-non-consensual-intimate-imagery.md b/content/site-policy/acceptable-use-policies/github-non-consensual-intimate-imagery.md new file mode 100644 index 000000000000..5f80813137ac --- /dev/null +++ b/content/site-policy/acceptable-use-policies/github-non-consensual-intimate-imagery.md @@ -0,0 +1,23 @@ +--- +title: GitHub Non-Consensual Intimate Imagery +shortTitle: NCII +versions: + fpt: '*' +topics: + - Policy + - Legal +redirect_from: + - /ncii + - /github/site-policy/github-ncii +--- + +GitHub does not tolerate the distribution of non-consensual intimate imagery (NCII). This includes, but is not limited to: + +* Private or intimate images or videos shared without the consent of all individuals depicted +* Threats to share intimate images or videos +* Sexually suggestive images or videos of a person taken in a private setting without their knowledge or consent such as upskirting, hidden cameras, or other forms of voyeurism +* Digitally altered or synthetic media that depicts a realistic-looking individual in a sexually explicit manner without their consent + +We understand that there may be rare cases in which such content may be shared for legitimate purposes, such as news reporting, education, or in a human rights context. However, we will review such cases on a case-by-case basis and may make exceptions when the content is not gratuitous and serves a clear public interest. + +If you encounter any content that you believe violates this policy, please report it immediately using our [Reporting Abuse](/communities/maintaining-your-safety-on-github/reporting-abuse-or-spam) instructions. diff --git a/content/site-policy/acceptable-use-policies/github-sexually-obscene-content.md b/content/site-policy/acceptable-use-policies/github-sexually-obscene-content.md index 20aa5a1bab57..f18e352b3bfb 100644 --- a/content/site-policy/acceptable-use-policies/github-sexually-obscene-content.md +++ b/content/site-policy/acceptable-use-policies/github-sexually-obscene-content.md @@ -11,7 +11,7 @@ redirect_from: - /github/site-policy/github-community-guidelines#sexually-obscene-content --- -We do not tolerate content associated with sexual exploitation or abuse of another individual, including where minors are concerned. We do not allow sexually themed or suggestive content that serves little or no purpose other than to solicit an erotic or shocking response, particularly where that content is amplified by its placement in profiles or other social contexts. This includes: +We do not tolerate content associated with sexual exploitation or abuse of another individual, including where [minors are concerned](/site-policy/acceptable-use-policies/github-child-sexual-exploitation-or-abuse). We do not allow sexually themed or suggestive content that serves little or no purpose other than to solicit an erotic or shocking response, particularly where that content is amplified by its placement in profiles or other social contexts. This includes: * Pornographic content * Non-consensual intimate imagery diff --git a/content/site-policy/acceptable-use-policies/github-synthetic-media-and-ai-tools.md b/content/site-policy/acceptable-use-policies/github-synthetic-media-and-ai-tools.md new file mode 100644 index 000000000000..4060d9fab494 --- /dev/null +++ b/content/site-policy/acceptable-use-policies/github-synthetic-media-and-ai-tools.md @@ -0,0 +1,20 @@ +--- +title: GitHub Synthetic Media and AI Tools +shortTitle: Synthetic Media and AI Tools +versions: + fpt: '*' +topics: + - Policy + - Legal +redirect_from: + - /ai-tools + - /github/site-policy/github-synthetic-media-and-ai-tools +--- + +GitHub does not allow any projects that are designed for, encourage, promote, support, or suggest in any way the use of large language models or any other synthetic or manipulated media tools for the creation of: + +* Sexual material involving minors or any other content that would constitute [Child Sexual Abuse Material](/site-policy/acceptable-use-policies/github-child-sexual-exploitation-or-abuse) under our policies +* Violent extremist propaganda or any other content that would constitute [Terrorist or Violent Extremist Content](/site-policy/acceptable-use-policies/github-terrorism-and-violent-extremism) under our policies +* Sexually explicit media of people without their consent or any other content that would constitute [Non-Consensual Intimate Imagery](/site-policy/acceptable-use-policies/github-non-consensual-intimate-imagery) under our policies + +GitHub evaluates violations of this policy by examining a project in context. This could include an evaluation of many factors: how a project is configured; how it is marketed; the information provided in its README or other documentation; the external sites or resources it links to; the nature of support provided by the project maintainers; or any other relevant facts that indicate a likelihood of furthering one of the prohibited uses above. diff --git a/content/site-policy/acceptable-use-policies/github-terrorism-and-violent-extremism.md b/content/site-policy/acceptable-use-policies/github-terrorism-and-violent-extremism.md new file mode 100644 index 000000000000..46d34f2ca528 --- /dev/null +++ b/content/site-policy/acceptable-use-policies/github-terrorism-and-violent-extremism.md @@ -0,0 +1,28 @@ +--- +title: GitHub Terrorism and Violent Extremism +shortTitle: Terrorism and Violent Extremism Content +versions: + fpt: '*' +topics: + - Policy + - Legal +redirect_from: + - /tvec + - /github/site-policy/github-terrorism-and-violent-extremism +--- + +GitHub does not allow any content or conduct on GitHub that furthers terrorism or violent extremism (TVE). We will remove content and may ban accounts affiliated with TVE groups or individuals. You may not use GitHub for activities including but not limited to: + +* Organizing, inciting, planning, coordinating, or calling for violence, attacks, or other illegal harm +* Recruiting, promoting, or encouraging others to join or support TVE groups +* Financing, soliciting funds, or providing other support for a TVE group +* Sharing propaganda, manifestos, or any other media intended to praise or spread TVE ideology +* Glorification or praise of TVE groups or acts +* Displaying symbols, logos, slogans, or any other insignia associated with TVE groups in a manner that endorses or supports them +* Posting code, scripts, guides, or instructions that facilitate TVE activities + +GitHub may use automated tools, such as hash matching, as well as human review to detect and remove TVE content. If we become aware of credible evidence that a user is involved in TVE activities outside of GitHub (for example, being part of a legally designated terrorist organization or engaging in extremist violence), we may suspend or terminate that user’s GitHub account to protect the community and may take additional action, such as reporting to law enforcement authorities. + +We recognize there may be exceptional cases where content related to terrorism or extremism appears on GitHub for legitimate reasons — for instance, in academic research, training data, journalism, or counter-terrorism efforts. Such content must be kept to a minimum, clearly placed in an appropriate context (educational, documentary, or critical), and not in any way support extremist causes. Even with context, GitHub may restrict or remove this content if we determine it could be misused or poses a risk. + +If you encounter any content that you believe violates this policy, please report it immediately using our [Reporting Abuse](/communities/maintaining-your-safety-on-github/reporting-abuse-or-spam) instructions. diff --git a/content/site-policy/acceptable-use-policies/index.md b/content/site-policy/acceptable-use-policies/index.md index c7d483b8df9a..d6e62a8a2f90 100644 --- a/content/site-policy/acceptable-use-policies/index.md +++ b/content/site-policy/acceptable-use-policies/index.md @@ -16,6 +16,10 @@ children: - github-misinformation-and-disinformation - github-sexually-obscene-content - github-threats-of-violence-and-gratuitously-violent-content + - github-terrorism-and-violent-extremism + - github-child-sexual-exploitation-or-abuse + - github-non-consensual-intimate-imagery + - github-synthetic-media-and-ai-tools - github-appeal-and-reinstatement --- diff --git a/data/features/ent-security-manager.yml b/data/features/ent-security-manager.yml new file mode 100644 index 000000000000..e3459788b94d --- /dev/null +++ b/data/features/ent-security-manager.yml @@ -0,0 +1,5 @@ +# Ref: 5084 +# Introduction of the security manager role at the Enterprise level (public preview) +versions: + ghec: '*' + ghes: '>=3.20' diff --git a/data/features/enterprise-custom-roles.yml b/data/features/enterprise-custom-roles.yml new file mode 100644 index 000000000000..072d6301da45 --- /dev/null +++ b/data/features/enterprise-custom-roles.yml @@ -0,0 +1,5 @@ +# Reference: #releases/issues/4919 +# Enterprise admins can assign pre-defined and create custom enterprise roles for their enterprise users and teams. [Public Preview] +versions: + ghec: '*' + ghes: '>=3.20' diff --git a/data/reusables/contributing/content-linter-rules.md b/data/reusables/contributing/content-linter-rules.md index aae3ee888fb7..878adcbe4ea1 100644 --- a/data/reusables/contributing/content-linter-rules.md +++ b/data/reusables/contributing/content-linter-rules.md @@ -76,6 +76,8 @@ | GHD058 | journey-tracks-liquid | Journey track properties must use valid Liquid syntax | error | frontmatter, journey-tracks, liquid | | GHD059 | journey-tracks-guide-path-exists | Journey track guide paths must reference existing content files | error | frontmatter, journey-tracks | | GHD060 | journey-tracks-unique-ids | Journey track IDs must be unique within a page | error | frontmatter, journey-tracks, unique-ids | +| GHD061 | frontmatter-hero-image | Hero image paths must be absolute and point to valid images in /assets/images/banner-images/ | error | frontmatter, images | +| GHD062 | frontmatter-intro-links | introLinks keys must be valid keys defined in data/ui.yml under product_landing | error | frontmatter, single-source | | [search-replace](https://github.com/OnkarRuikar/markdownlint-rule-search-replace) | deprecated liquid syntax: octicon- | The octicon liquid syntax used is deprecated. Use this format instead `octicon "" aria-label=""` | error | | | [search-replace](https://github.com/OnkarRuikar/markdownlint-rule-search-replace) | deprecated liquid syntax: site.data | Catch occurrences of deprecated liquid data syntax. | error | | | [search-replace](https://github.com/OnkarRuikar/markdownlint-rule-search-replace) | developer-domain | Catch occurrences of developer.github.com domain. | error | | diff --git a/data/reusables/copilot/available-models-per-plan.md b/data/reusables/copilot/available-models-per-plan.md index 4f5c6050e0b2..fc035f94faa0 100644 --- a/data/reusables/copilot/available-models-per-plan.md +++ b/data/reusables/copilot/available-models-per-plan.md @@ -1,23 +1,9 @@ {% rowheaders %} -| Available models in chat | {% data variables.copilot.copilot_free_short %} | {% data variables.copilot.copilot_pro_short %} | {% data variables.copilot.copilot_pro_plus_short %} | {% data variables.copilot.copilot_business_short %} | {% data variables.copilot.copilot_enterprise_short %} | -|----------------------------------------------------------------|-------------------------------------------------|-------------------------------------------------|-----------------------------------------------------|-----------------------------------------------------|-------------------------------------------------------| -| {% data variables.copilot.copilot_gpt_41 %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | -| {% data variables.copilot.copilot_gpt_5_codex %} | {% octicon "x" aria-label="Not included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | -| {% data variables.copilot.copilot_gpt_5_mini %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | -| {% data variables.copilot.copilot_gpt_5 %} | {% octicon "x" aria-label="Not included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | -| {% data variables.copilot.copilot_o3 %} | {% octicon "x" aria-label="Not included" %} | {% octicon "x" aria-label="Not included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "x" aria-label="Not included" %} | {% octicon "check" aria-label="Included" %} | -| {% data variables.copilot.copilot_o4_mini %} | {% octicon "x" aria-label="Not included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | -| {% data variables.copilot.copilot_claude_haiku_45 %} | {% octicon "x" aria-label="Not included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | -| {% data variables.copilot.copilot_claude_sonnet_45 %} | {% octicon "x" aria-label="Not included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | -| {% data variables.copilot.copilot_claude_opus_41 %} | {% octicon "x" aria-label="Not included" %} | {% octicon "x" aria-label="Not included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "x" aria-label="Not included" %} | {% octicon "check" aria-label="Included" %} | -| {% data variables.copilot.copilot_claude_opus %} | {% octicon "x" aria-label="Not included" %} | {% octicon "x" aria-label="Not included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "x" aria-label="Not included" %} | {% octicon "check" aria-label="Included" %} | -| {% data variables.copilot.copilot_claude_sonnet_35 %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | -| {% data variables.copilot.copilot_claude_sonnet_37 %} | {% octicon "x" aria-label="Not included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | -| {% data variables.copilot.copilot_claude_sonnet_37 %} Thinking | {% octicon "x" aria-label="Not included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | -| {% data variables.copilot.copilot_claude_sonnet_40 %} | {% octicon "x" aria-label="Not included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | -| {% data variables.copilot.copilot_gemini_25_pro %} | {% octicon "x" aria-label="Not included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | -| {% data variables.copilot.copilot_gemini_flash %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | -| {% data variables.copilot.copilot_grok_code %} | {% octicon "x" aria-label="Not included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | +| Available models in chat | {% data variables.copilot.copilot_free_short %} | {% data variables.copilot.copilot_pro_short %} | {% data variables.copilot.copilot_pro_plus_short %} | {% data variables.copilot.copilot_business_short %} | {% data variables.copilot.copilot_enterprise_short %} | +|---------------------------------------------------------|-------------------------------------------------|-------------------------------------------------|-----------------------------------------------------|-----------------------------------------------------|-------------------------------------------------------| +| {% for model in tables.copilot.model-supported-plans %} | +| {{ model.name }} | {% if model.free == true %}{% octicon "check" aria-label="Included" %}{% else %}{% octicon "x" aria-label="Not included" %}{% endif %} | {% if model.pro == true %}{% octicon "check" aria-label="Included" %}{% else %}{% octicon "x" aria-label="Not included" %}{% endif %} | {% if model.pro_plus == true %}{% octicon "check" aria-label="Included" %}{% else %}{% octicon "x" aria-label="Not included" %}{% endif %} | {% if model.business == true %}{% octicon "check" aria-label="Included" %}{% else %}{% octicon "x" aria-label="Not included" %}{% endif %} | {% if model.enterprise == true %}{% octicon "check" aria-label="Included" %}{% else %}{% octicon "x" aria-label="Not included" %}{% endif %} | +| {% endfor %} | {% endrowheaders %} diff --git a/data/reusables/copilot/model-multipliers.md b/data/reusables/copilot/model-multipliers.md index 31ade8310abb..868e4e50ba85 100644 --- a/data/reusables/copilot/model-multipliers.md +++ b/data/reusables/copilot/model-multipliers.md @@ -1,24 +1,9 @@ {% rowheaders %} -| Model | Multiplier for **paid plans** | Multiplier for **{% data variables.copilot.copilot_free_short %}** | -|----------------------------------------------------------------|-------------------------------|--------------------------------------------------------------------| -| {% data variables.copilot.copilot_gpt_41 %} | 0 | 1 | -| {% data variables.copilot.copilot_gpt_5_codex %} | 1 | Not applicable | -| {% data variables.copilot.copilot_gpt_5_mini %} | 0 | 1 | -| {% data variables.copilot.copilot_gpt_5 %} | 1 | Not applicable | -| {% data variables.copilot.copilot_gpt_4o %} | 0 | 1 | -| {% data variables.copilot.copilot_o3 %} | 1 | Not applicable | -| {% data variables.copilot.copilot_o4_mini %} | 0.33 | Not applicable | -| {% data variables.copilot.copilot_claude_haiku_45 %} | 0.33 | Not applicable | -| {% data variables.copilot.copilot_claude_sonnet_45 %} | 1 | Not applicable | -| {% data variables.copilot.copilot_claude_sonnet_35 %} | 1 | 1 | -| {% data variables.copilot.copilot_claude_sonnet_37 %} | 1 | Not applicable | -| {% data variables.copilot.copilot_claude_sonnet_37 %} Thinking | 1.25 | Not applicable | -| {% data variables.copilot.copilot_claude_sonnet_40 %} | 1 | Not applicable | -| {% data variables.copilot.copilot_claude_opus_41 %} | 10 | Not applicable | -| {% data variables.copilot.copilot_claude_opus %} | 10 | Not applicable | -| {% data variables.copilot.copilot_gemini_flash %} | 0.25 | 1 | -| {% data variables.copilot.copilot_gemini_25_pro %} | 1 | Not applicable | -| {% data variables.copilot.copilot_grok_code %} | 0.25 | Not applicable | +| Model | Multiplier for **paid plans** | Multiplier for **{% data variables.copilot.copilot_free_short %}** | +|-----------------------------------------------------|-------------------------------|--------------------------------------------------------------------| +| {% for model in tables.copilot.model-multipliers %} | +| {{ model.name }} | {{ model.multiplier_paid }} | {{ model.multiplier_free }} | +| {% endfor %} | {% endrowheaders %} diff --git a/data/reusables/copilot/model-use-cases/claude-37-sonnet.md b/data/reusables/copilot/model-use-cases/claude-sonnet-45.md similarity index 79% rename from data/reusables/copilot/model-use-cases/claude-37-sonnet.md rename to data/reusables/copilot/model-use-cases/claude-sonnet-45.md index b89e92696c34..6ecce7655aba 100644 --- a/data/reusables/copilot/model-use-cases/claude-37-sonnet.md +++ b/data/reusables/copilot/model-use-cases/claude-sonnet-45.md @@ -1 +1 @@ -{% data variables.copilot.copilot_claude_sonnet_37 %} excels across the software development lifecycle, from initial design to bug fixes, maintenance to optimizations. It is particularly well-suited for multi-file refactoring or architectural planning, where understanding context across components is important. +{% data variables.copilot.copilot_claude_sonnet_45 %} excels across the software development lifecycle, from initial design to bug fixes, maintenance to optimizations. It is particularly well-suited for multi-file refactoring or architectural planning, where understanding context across components is important. diff --git a/data/reusables/copilot/model-use-cases/gemini-20-flash.md b/data/reusables/copilot/model-use-cases/gemini-20-flash.md deleted file mode 100644 index d39c8cf85c2b..000000000000 --- a/data/reusables/copilot/model-use-cases/gemini-20-flash.md +++ /dev/null @@ -1 +0,0 @@ - {% data variables.copilot.copilot_gemini_flash %} supports image input so that developers can bring visual context into tasks like UI inspection, diagram analysis, or layout debugging. This makes {% data variables.copilot.copilot_gemini_flash %} particularly useful for scenarios where image-based input enhances problem-solving, such as asking {% data variables.product.prodname_copilot_short %} to analyze a UI screenshot for accessibility issues or to help understand a visual bug in a layout. diff --git a/data/reusables/copilot/model-use-cases/gpt-5.md b/data/reusables/copilot/model-use-cases/gpt-5.md new file mode 100644 index 000000000000..7a59405da965 --- /dev/null +++ b/data/reusables/copilot/model-use-cases/gpt-5.md @@ -0,0 +1 @@ + {% data variables.copilot.copilot_gpt_5 %} supports image input so that developers can bring visual context into tasks like UI inspection, diagram analysis, or layout debugging. This makes {% data variables.copilot.copilot_gpt_5 %} particularly useful for scenarios where image-based input enhances problem-solving, such as asking {% data variables.product.prodname_copilot_short %} to analyze a UI screenshot for accessibility issues or to help understand a visual bug in a layout. diff --git a/data/reusables/emus/about-guest-collaborators.md b/data/reusables/emus/about-guest-collaborators.md index 255d668a2a97..bf19f4f7f2e0 100644 --- a/data/reusables/emus/about-guest-collaborators.md +++ b/data/reusables/emus/about-guest-collaborators.md @@ -3,3 +3,5 @@ You can use the guest collaborator role to grant limited access to vendors and c * Are provisioned by your IdP, like all {% data variables.enterprise.prodname_managed_users %}. * Can be added as organization members or as collaborators in repositories. * Cannot access internal repositories in the enterprise, except in organizations where they're added as a member. + +The difference between a guest collaborator and a regular user is that when a regular user is added to one organization, they can automatically access **all** internal repositories in the enterprise. diff --git a/data/reusables/enterprise/enterprise-teams-can.md b/data/reusables/enterprise/enterprise-teams-can.md new file mode 100644 index 000000000000..0af46b09c55d --- /dev/null +++ b/data/reusables/enterprise/enterprise-teams-can.md @@ -0,0 +1,6 @@ +Enterprise teams can: + +* Receive **{% data variables.copilot.copilot_business_short %} licenses** directly from the enterprise. +* Be assigned **predefined and custom enterprise roles**, giving members access to enterprise settings. +* Be **added to organizations**, where organization administrators can grant the team additional access and permissions. +* Receive **bypass access** on rulesets. diff --git a/data/reusables/enterprise/enterprise-teams-limits.md b/data/reusables/enterprise/enterprise-teams-limits.md new file mode 100644 index 000000000000..666a1aa0f7f9 --- /dev/null +++ b/data/reusables/enterprise/enterprise-teams-limits.md @@ -0,0 +1 @@ +There are **limits on enterprise teams**. You can create up to 100 teams for a single enterprise and add up to 500 users to each team. Each team can be assigned to a maximum of 1,000 organizations. diff --git a/data/tables/copilot/model-deprecation-history.yml b/data/tables/copilot/model-deprecation-history.yml new file mode 100644 index 000000000000..bcd5c8715d86 --- /dev/null +++ b/data/tables/copilot/model-deprecation-history.yml @@ -0,0 +1,44 @@ +# Please keep this list sorted in the following order: +# 1. By retirement_date, from newest to oldest +# 2. Within the same date, alphabetically by name +# +# This file lists AI models that have been retired +# It is used to generate the "Model retirement history" table at +# /content/copilot/reference/ai-models/supported-models#model-retirement-history. +# +# Column keys +# - name: The model name being deprecated. +# - retirement_date: The official retirement date for the model (YYYY-MM-DD). +# - suggested_alternative: The model recommended for migration. + +- name: 'Claude Opus 4' + retirement_date: '2025-10-23' + suggested_alternative: 'Claude Opus 4.1' + +- name: 'Claude Sonnet 3.7' + retirement_date: '2025-10-23' + suggested_alternative: 'Claude Sonnet 4' + +- name: 'Claude Sonnet 3.7 Thinking' + retirement_date: '2025-10-23' + suggested_alternative: 'Claude Sonnet 4' + +- name: 'Gemini 2.0 Flash' + retirement_date: '2025-10-23' + suggested_alternative: 'Gemini 2.5 Pro' + +- name: 'o1-mini' + retirement_date: '2025-10-23' + suggested_alternative: 'GPT-5 mini' + +- name: 'o3' + retirement_date: '2025-10-23' + suggested_alternative: 'GPT-5' + +- name: 'o3-mini' + retirement_date: '2025-10-23' + suggested_alternative: 'GPT-5 mini' + +- name: 'o4-mini' + retirement_date: '2025-10-23' + suggested_alternative: 'GPT-5 mini' diff --git a/data/tables/copilot/model-multipliers.yml b/data/tables/copilot/model-multipliers.yml new file mode 100644 index 000000000000..178a2fcf0567 --- /dev/null +++ b/data/tables/copilot/model-multipliers.yml @@ -0,0 +1,58 @@ +# Please keep this list sorted in alphabetical order. +# +# This file defines the model multipliers for paid and free plans. +# It is used to generate the "Model multipliers" table at +# /content/copilot/reference/ai-models/supported-models#moodel-multiplier. +# +# Column keys: +# - name: The model name. +# - multiplier_paid: The multiplier for paid plans. +# - multiplier_free: The multiplier for free plans. + +- name: Claude Haiku 4.5 + multiplier_paid: 0.33 + multiplier_free: Not applicable + +- name: Claude Opus 4.1 + multiplier_paid: 10 + multiplier_free: Not applicable + +- name: Claude Sonnet 3.5 + multiplier_paid: 1 + multiplier_free: 1 + +- name: Claude Sonnet 4 + multiplier_paid: 1 + multiplier_free: Not applicable + +- name: Claude Sonnet 4.5 + multiplier_paid: 1 + multiplier_free: Not applicable + +- name: Gemini 2.5 Pro + multiplier_paid: 1 + multiplier_free: Not applicable + +- name: GPT-4.1 + multiplier_paid: 0 + multiplier_free: 1 + +- name: GPT-4o + multiplier_paid: 0 + multiplier_free: 1 + +- name: GPT-5 + multiplier_paid: 1 + multiplier_free: Not applicable + +- name: GPT-5 mini + multiplier_paid: 0 + multiplier_free: 1 + +- name: GPT-5-Codex + multiplier_paid: 1 + multiplier_free: Not applicable + +- name: Grok Code Fast 1 + multiplier_paid: 0.25 + multiplier_free: Not applicable diff --git a/data/tables/copilot/model-release-status.yml b/data/tables/copilot/model-release-status.yml new file mode 100644 index 000000000000..9fb4b664f2ee --- /dev/null +++ b/data/tables/copilot/model-release-status.yml @@ -0,0 +1,100 @@ +# Please keep this list sorted in the following order: +# 1. By provider, in this order: +# - OpenAI +# - Anthropic +# - Google +# - xAI +# 2. Within each provider group, alphabetically by model name. +# +# This file defines the list of AI models available in GitHub Copilot and +# their current release status. +# +# Column keys: +# - name: Model name +# - provider: Provider +# - release_status: Release status +# - agent_mode: true = ✓, false = ✗ +# - ask_mode: true = ✓, false = ✗ +# - edit_mode: true = ✓, false = ✗ + +# OpenAI models +- name: 'GPT-4.1' + provider: 'OpenAI' + release_status: 'GA' + agent_mode: true + ask_mode: true + edit_mode: true + +- name: 'GPT-5' + provider: 'OpenAI' + release_status: 'GA' + agent_mode: true + ask_mode: true + edit_mode: true + +- name: 'GPT-5 mini' + provider: 'OpenAI' + release_status: 'GA' + agent_mode: true + ask_mode: true + edit_mode: true + +- name: 'GPT-5-Codex' + provider: 'OpenAI' + release_status: 'Public preview' + agent_mode: true + ask_mode: true + edit_mode: true + +# Anthropic models +- name: 'Claude Haiku 4.5' + provider: 'Anthropic' + release_status: 'GA' + agent_mode: true + ask_mode: true + edit_mode: true + +- name: 'Claude Opus 4.1' + provider: 'Anthropic' + release_status: 'GA' + agent_mode: false + ask_mode: true + edit_mode: true + +- name: 'Claude Sonnet 3.5' + provider: 'Anthropic' + release_status: 'Closing down: 2025-11-06' + agent_mode: true + ask_mode: true + edit_mode: true + +- name: 'Claude Sonnet 4' + provider: 'Anthropic' + release_status: 'GA' + agent_mode: true + ask_mode: true + edit_mode: true + +- name: 'Claude Sonnet 4.5' + provider: 'Anthropic' + release_status: 'GA' + agent_mode: true + ask_mode: true + edit_mode: true + +# Google models + +- name: 'Gemini 2.5 Pro' + provider: 'Google' + release_status: 'GA' + agent_mode: true + ask_mode: true + edit_mode: true + +# xAI models +- name: 'Grok Code Fast 1' + provider: 'xAI' + release_status: 'Public preview' + agent_mode: true + ask_mode: true + edit_mode: true diff --git a/data/tables/copilot/model-supported-clients.yml b/data/tables/copilot/model-supported-clients.yml new file mode 100644 index 000000000000..f953fcdfd4ed --- /dev/null +++ b/data/tables/copilot/model-supported-clients.yml @@ -0,0 +1,102 @@ +# Please keep this list sorted in alphabetical order. +# +# This file defines which AI models are available in each GitHub Copilot client. +# It is used to generate the "Supported AI models per client" table at +# /content/copilot/reference/ai-models/supported-models#supported-ai-models-per-client. +# +# Column keys: +# - name: The model name. +# - dotcom: Availability on GitHub.com (Copilot Chat in the browser). +# - vscode: Availability in Visual Studio Code. +# - vs: Availability in Visual Studio. +# - eclipse: Availability in Eclipse. +# - xcode: Availability in Xcode. +# - jetbrains: Availability in JetBrains IDEs. + +- name: Claude Haiku 4.5 + dotcom: true + vscode: true + vs: true + eclipse: true + xcode: true + jetbrains: true + +- name: Claude Opus 4.1 + dotcom: true + vscode: true + vs: true + eclipse: true + xcode: true + jetbrains: true + +- name: Claude Sonnet 3.5 + dotcom: true + vscode: true + vs: true + eclipse: true + xcode: true + jetbrains: true + +- name: Claude Sonnet 4 + dotcom: true + vscode: true + vs: true + eclipse: true + xcode: true + jetbrains: true + +- name: Claude Sonnet 4.5 + dotcom: true + vscode: true + vs: true + eclipse: true + xcode: true + jetbrains: true + +- name: Gemini 2.5 Pro + dotcom: true + vscode: true + vs: true + eclipse: true + xcode: true + jetbrains: true + +- name: GPT-4.1 + dotcom: true + vscode: true + vs: true + eclipse: true + xcode: true + jetbrains: true + +- name: GPT-5 + dotcom: true + vscode: true + vs: true + eclipse: true + xcode: true + jetbrains: true + +- name: GPT-5 mini + dotcom: true + vscode: true + vs: true + eclipse: true + xcode: true + jetbrains: true + +- name: GPT-5-Codex + dotcom: false + vscode: true + vs: false + eclipse: false + xcode: false + jetbrains: false + +- name: Grok Code Fast 1 + dotcom: true + vscode: true + vs: true + eclipse: true + xcode: true + jetbrains: true diff --git a/data/tables/copilot/model-supported-plans.yml b/data/tables/copilot/model-supported-plans.yml new file mode 100644 index 000000000000..ecd2efa2e62e --- /dev/null +++ b/data/tables/copilot/model-supported-plans.yml @@ -0,0 +1,90 @@ +# Please keep this list sorted in alphabetical order. +# +# This file defines which AI models are available in each Copilot plan. +# It is used to generate the "Supported AI models per Copilot plan" table at +# /content/copilot/reference/ai-models/supported-models##supported-ai-models-per-copilot-plan. +# +# Column keys: +# - name: The model name. +# - free: Availability on Copilot Free. +# - pro: Availability on Copilot Pro. +# - pro_plus: Availability on Copilot Pro+. +# - business: Availability on Copilot Business. +# - enterprise: Availability on Copilot Enterprise. + +- name: Claude Haiku 4.5 + free: false + pro: true + pro_plus: true + business: true + enterprise: true + +- name: Claude Opus 4.1 + free: false + pro: false + pro_plus: true + business: false + enterprise: true + +- name: Claude Sonnet 3.5 + free: true + pro: true + pro_plus: true + business: true + enterprise: true + +- name: Claude Sonnet 4 + free: false + pro: true + pro_plus: true + business: true + enterprise: true + +- name: Claude Sonnet 4.5 + free: false + pro: true + pro_plus: true + business: true + enterprise: true + +- name: Gemini 2.5 Pro + free: false + pro: true + pro_plus: true + business: true + enterprise: true + +- name: GPT-4.1 + free: true + pro: true + pro_plus: true + business: true + enterprise: true + +- name: GPT-5 + free: false + pro: true + pro_plus: true + business: true + enterprise: true + +- name: GPT-5 mini + free: true + pro: true + pro_plus: true + business: true + enterprise: true + +- name: GPT-5-Codex + free: false + pro: true + pro_plus: true + business: true + enterprise: true + +- name: Grok Code Fast 1 + free: false + pro: true + pro_plus: true + business: true + enterprise: true diff --git a/data/ui.yml b/data/ui.yml index e77f87975515..1e3986a0f4ec 100644 --- a/data/ui.yml +++ b/data/ui.yml @@ -69,11 +69,6 @@ search: general_title: There was an error loading search results. ai_title: There was an error loading Copilot. description: You can still use this field to search our docs. - cta: - heading: Get quick answers! - description: Ask Copilot your question. - dismiss: Dismiss - ask_copilot: Ask Copilot old_search: description: Enter a search term to find it in the GitHub Docs. placeholder: Search GitHub Docs diff --git a/next.config.ts b/next.config.ts index 5db11da8cb33..ce4e8d3a92aa 100644 --- a/next.config.ts +++ b/next.config.ts @@ -4,14 +4,9 @@ import type { NextConfig } from 'next' import frontmatter from '@gr2m/gray-matter' import { getLogLevelNumber } from '@/observability/logger/lib/log-levels' +import { languageKeys } from '@/languages/lib/languages' const ROOT = process.env.ROOT || '.' - -// Language keys are defined here because Next.js config compilation doesn't resolve the @/ path alias -// Importing from src/languages/lib/languages.ts would fail when it tries to import @/frame/lib/constants -// This must match the languages defined in src/languages/lib/languages.ts -const languageKeys = ['en', 'es', 'ja', 'pt', 'zh', 'ru', 'fr', 'ko', 'de'] - const homepage = path.posix.join(ROOT, 'content/index.md') const { data } = frontmatter(fs.readFileSync(homepage, 'utf8')) const productIds = data.children as string[] diff --git a/src/app/client-layout.tsx b/src/app/client-layout.tsx index 60c80ec8c98a..4cb24a664696 100644 --- a/src/app/client-layout.tsx +++ b/src/app/client-layout.tsx @@ -10,7 +10,7 @@ import { initializeEvents } from '@/events/components/events' import { CTAPopoverProvider } from '@/frame/components/context/CTAContext' import { SharedUIContextProvider } from '@/frame/components/context/SharedUIContext' import { LanguagesContext, LanguagesContextT } from '@/languages/components/LanguagesContext' -import { clientLanguages, type ClientLanguageCode } from '@/languages/lib/client-languages' +import { languages, type LanguageCode } from '@/languages/lib/languages' import { MainContextProvider } from '@/app/components/MainContextProvider' import { createMinimalMainContext } from '@/app/lib/main-context-adapter' import type { AppRouterContext } from '@/app/lib/app-router-context' @@ -31,16 +31,11 @@ interface ClientLayoutProps { export function ClientLayout({ children, appContext, pageData }: ClientLayoutProps): JSX.Element { const { theme } = useTheme() - const locale: ClientLanguageCode = useDetectLocale() + const locale: LanguageCode = useDetectLocale() const [isInitialized, setIsInitialized] = useState(false) const [initializationError, setInitializationError] = useState(null) - const languagesContext: LanguagesContextT = useMemo( - () => ({ - languages: clientLanguages, - }), - [], - ) + const languagesContext: LanguagesContextT = useMemo(() => ({ languages }), []) // Create MainContext-compatible data for App Router const mainContext = useMemo( diff --git a/src/app/components/AppRouterLanguagesContext.tsx b/src/app/components/AppRouterLanguagesContext.tsx index c33a612fe956..96aa54097d8d 100644 --- a/src/app/components/AppRouterLanguagesContext.tsx +++ b/src/app/components/AppRouterLanguagesContext.tsx @@ -1,7 +1,7 @@ 'use client' import { createContext, useContext } from 'react' -import { clientLanguages, type ClientLanguageCode } from '@/languages/lib/client-languages' +import { languages, type LanguageCode } from '@/languages/lib/languages' export type AppRouterLanguageItem = { name: string @@ -12,7 +12,7 @@ export type AppRouterLanguageItem = { export type AppRouterLanguagesContextT = { languages: Record - currentLanguage?: ClientLanguageCode + currentLanguage?: LanguageCode } export const AppRouterLanguagesContext = createContext(null) @@ -34,7 +34,7 @@ export const useAppRouterLanguages = (): AppRouterLanguagesContextT => { */ interface AppRouterLanguagesProviderProps { children: React.ReactNode - currentLanguage?: ClientLanguageCode + currentLanguage?: LanguageCode } export function AppRouterLanguagesProvider({ @@ -42,7 +42,7 @@ export function AppRouterLanguagesProvider({ currentLanguage, }: AppRouterLanguagesProviderProps) { const value: AppRouterLanguagesContextT = { - languages: clientLanguages, + languages, currentLanguage, } diff --git a/src/app/components/ServerFooter.tsx b/src/app/components/ServerFooter.tsx index d4b846381a0b..a6d5913a4bf6 100644 --- a/src/app/components/ServerFooter.tsx +++ b/src/app/components/ServerFooter.tsx @@ -1,10 +1,10 @@ import { getUIDataMerged } from '@/data-directory/lib/get-data' import { createTranslationFunctions } from '@/languages/lib/translation-utils' import { LinkExternalIcon } from '@primer/octicons-react' -import type { ClientLanguageCode } from '@/languages/lib/client-languages' +import type { LanguageCode } from '@/languages/lib/languages' interface ServerFooterProps { - currentLanguage: ClientLanguageCode + currentLanguage: LanguageCode } export function ServerFooter({ currentLanguage }: ServerFooterProps) { diff --git a/src/app/lib/app-router-context.ts b/src/app/lib/app-router-context.ts index 1997e009ab64..3e0e8e75ecc2 100644 --- a/src/app/lib/app-router-context.ts +++ b/src/app/lib/app-router-context.ts @@ -1,10 +1,10 @@ import { getUIDataMerged } from '@/data-directory/lib/get-data' -import { type ClientLanguageCode } from '@/languages/lib/client-languages' +import { type LanguageCode } from '@/languages/lib/languages' import { translate } from '@/languages/lib/translation-utils' import { extractLanguageFromPath } from '@/app/lib/language-utils' export interface AppRouterContext { - currentLanguage: ClientLanguageCode + currentLanguage: LanguageCode currentVersion: string sitename: string site: { @@ -19,7 +19,7 @@ export interface AppRouterContext { */ export function createAppRouterContext( pathname: string = '/', - fallbackLanguage?: ClientLanguageCode, + fallbackLanguage?: LanguageCode, ): AppRouterContext { let language = extractLanguageFromPath(pathname) diff --git a/src/app/lib/language-utils.ts b/src/app/lib/language-utils.ts index 00f77fe68571..173f4e9a1f55 100644 --- a/src/app/lib/language-utils.ts +++ b/src/app/lib/language-utils.ts @@ -1,16 +1,16 @@ -import { clientLanguageKeys, type ClientLanguageCode } from '@/languages/lib/client-languages' +import { languageKeys, type LanguageCode } from '@/languages/lib/languages' /** * Extract language from URL path * Handles paths like /en/something, /es/articles, etc. */ -export function extractLanguageFromPath(path: string): ClientLanguageCode { +export function extractLanguageFromPath(path: string): LanguageCode { try { const pathSegments = path.split('/') const firstSegment = pathSegments[1] - if (firstSegment && clientLanguageKeys.includes(firstSegment)) { - return firstSegment as ClientLanguageCode + if (firstSegment && languageKeys.includes(firstSegment)) { + return firstSegment as LanguageCode } } catch (error) { console.warn('Failed to extract language from path:', error) @@ -24,7 +24,7 @@ export function extractLanguageFromPath(path: string): ClientLanguageCode { export function hasLanguagePrefix(path: string): boolean { const pathSegments = path.split('/') const firstSegment = pathSegments[1] - return Boolean(firstSegment && clientLanguageKeys.includes(firstSegment)) + return Boolean(firstSegment && languageKeys.includes(firstSegment)) } /** @@ -43,7 +43,7 @@ export function stripLanguagePrefix(path: string): string { * Add language prefix to path if it doesn't have one * e.g., /articles/example + 'es' -> /es/articles/example */ -export function addLanguagePrefix(path: string, language: ClientLanguageCode): string { +export function addLanguagePrefix(path: string, language: LanguageCode): string { if (hasLanguagePrefix(path)) { return path } diff --git a/src/app/lib/locale-context.tsx b/src/app/lib/locale-context.tsx index 7840c8af714e..bad82ed43256 100644 --- a/src/app/lib/locale-context.tsx +++ b/src/app/lib/locale-context.tsx @@ -1,55 +1,51 @@ 'use client' import { createContext, useContext, ReactNode, useMemo } from 'react' -import { - clientLanguages, - clientLanguageKeys, - type ClientLanguageCode, -} from '@/languages/lib/client-languages' +import { languages, languageKeys, type LanguageCode } from '@/languages/lib/languages' interface LocaleContextType { - readonly locale: ClientLanguageCode - readonly isValidLocale: (locale: string) => locale is ClientLanguageCode - readonly getSupportedLocales: () => readonly ClientLanguageCode[] - readonly getLocaleDisplayName: (locale: ClientLanguageCode) => string - readonly getLocaleNativeName: (locale: ClientLanguageCode) => string + readonly locale: LanguageCode + readonly isValidLocale: (locale: string) => locale is LanguageCode + readonly getSupportedLocales: () => readonly LanguageCode[] + readonly getLocaleDisplayName: (locale: LanguageCode) => string + readonly getLocaleNativeName: (locale: LanguageCode) => string } const LocaleContext = createContext(null) interface LocaleProviderProps { readonly children: ReactNode - readonly locale: ClientLanguageCode + readonly locale: LanguageCode } // Use client languages as the source of truth for supported locales -const SUPPORTED_LOCALES: readonly ClientLanguageCode[] = clientLanguageKeys as ClientLanguageCode[] +const SUPPORTED_LOCALES: readonly LanguageCode[] = languageKeys as LanguageCode[] /** * Validates if a string is a supported locale */ -function isValidLocale(locale: string): locale is ClientLanguageCode { - return clientLanguageKeys.includes(locale) +function isValidLocale(locale: string): locale is LanguageCode { + return languageKeys.includes(locale) } /** - * Gets display name for a locale from client languages data + * Gets display name for a locale from languages module */ -function getLocaleDisplayName(locale: ClientLanguageCode): string { - return clientLanguages[locale]?.name || locale +function getLocaleDisplayName(locale: LanguageCode): string { + return languages[locale]?.name || locale } /** - * Gets native name for a locale from client languages data + * Gets native name for a locale from languages module */ -function getLocaleNativeName(locale: ClientLanguageCode): string { - return clientLanguages[locale]?.nativeName || clientLanguages[locale]?.name || locale +function getLocaleNativeName(locale: LanguageCode): string { + return languages[locale]?.nativeName || languages[locale]?.name || locale } /** * Gets browser language preference as a valid locale */ -function getBrowserLocale(): ClientLanguageCode { +function getBrowserLocale(): LanguageCode { if (typeof window === 'undefined') return 'en' const browserLang = window.navigator.language.split('-')[0] @@ -77,7 +73,7 @@ export function LocaleProvider({ children, locale }: LocaleProviderProps): JSX.E /** * Hook to get current locale with enhanced error handling */ -export function useLocale(): ClientLanguageCode { +export function useLocale(): LanguageCode { const context = useContext(LocaleContext) if (context) { @@ -118,4 +114,4 @@ export function useLocaleContext(): LocaleContextType { } export { isValidLocale, getLocaleDisplayName, getLocaleNativeName } -export type { LocaleContextType, ClientLanguageCode } +export type { LocaleContextType, LanguageCode } diff --git a/src/app/lib/server-context-utils.ts b/src/app/lib/server-context-utils.ts index c389b14b116a..7bb74149c22f 100644 --- a/src/app/lib/server-context-utils.ts +++ b/src/app/lib/server-context-utils.ts @@ -1,11 +1,11 @@ import { extractLanguageFromPath } from '@/app/lib/language-utils' import { extractVersionFromPath } from '@/app/lib/version-utils' import { getUIDataMerged } from '@/data-directory/lib/get-data' -import { type ClientLanguageCode } from '@/languages/lib/client-languages' +import { type LanguageCode } from '@/languages/lib/languages' import { createTranslationFunctions, translate } from '@/languages/lib/translation-utils' export interface ServerAppRouterContext { - currentLanguage: ClientLanguageCode + currentLanguage: LanguageCode currentVersion: string sitename: string site: { data: { ui: any } } @@ -33,7 +33,7 @@ export function createServerAppRouterContext(pathname: string): ServerAppRouterC /** * Create server-side footer with translations */ -export function createServerFooterContent(language: ClientLanguageCode) { +export function createServerFooterContent(language: LanguageCode) { const uiData = getUIDataMerged(language) const { t } = createTranslationFunctions(uiData, 'footer') diff --git a/src/app/lib/use-detect-locale.tsx b/src/app/lib/use-detect-locale.tsx index 8b82d19b58eb..c367b9ef5131 100644 --- a/src/app/lib/use-detect-locale.tsx +++ b/src/app/lib/use-detect-locale.tsx @@ -2,24 +2,24 @@ import { usePathname } from 'next/navigation' import { useMemo, useEffect, useState } from 'react' -import { clientLanguageKeys, type ClientLanguageCode } from '@/languages/lib/client-languages' +import { languageKeys, type LanguageCode } from '@/languages/lib/languages' import Cookies from '@/frame/components/lib/cookies' import { USER_LANGUAGE_COOKIE_NAME } from '@/frame/lib/constants' /** * Validates if a string is a supported locale using client languages */ -function isValidLocale(locale: string): locale is ClientLanguageCode { - return clientLanguageKeys.includes(locale) +function isValidLocale(locale: string): locale is LanguageCode { + return languageKeys.includes(locale) } /** * Hook to detect locale from various sources with fallback logic */ -export function useDetectLocale(): ClientLanguageCode { +export function useDetectLocale(): LanguageCode { const pathname = usePathname() - const [cookieLanguage, setCookieLanguage] = useState(null) - const [browserLanguage, setBrowserLanguage] = useState(null) + const [cookieLanguage, setCookieLanguage] = useState(null) + const [browserLanguage, setBrowserLanguage] = useState(null) // Read cookie and browser language on client-side mount useEffect(() => { @@ -71,7 +71,7 @@ export function useDetectLocale(): ClientLanguageCode { /** * Utility function to detect locale from pathname (for server-side use) */ -export function detectLocaleFromPath(pathname: string): ClientLanguageCode { +export function detectLocaleFromPath(pathname: string): LanguageCode { const pathSegments = pathname.split('/') const firstSegment = pathSegments[1] @@ -82,8 +82,8 @@ export function detectLocaleFromPath(pathname: string): ClientLanguageCode { return 'en' } -export function getSupportedLocales(): readonly ClientLanguageCode[] { - return clientLanguageKeys as ClientLanguageCode[] +export function getSupportedLocales(): readonly LanguageCode[] { + return languageKeys as LanguageCode[] } export { isValidLocale } diff --git a/src/app/types.ts b/src/app/types.ts index c86401e08c43..bfa7ce13feac 100644 --- a/src/app/types.ts +++ b/src/app/types.ts @@ -2,14 +2,14 @@ * Enhanced type definitions for the app router with strict validation */ -import type { ClientLanguageCode } from '@/languages/lib/client-languages' +import type { LanguageCode } from '@/languages/lib/languages' // Core theme types with strict validation export type Theme = 'light' | 'dark' | 'auto' export type ColorMode = 'light' | 'dark' -// Re-export locale type from client-languages for consistency -export type Locale = ClientLanguageCode +// Re-export locale type from languages.ts for consistency +export type Locale = LanguageCode // Version and product identifiers with validation export type VersionId = string diff --git a/src/archives/middleware/archived-enterprise-versions.ts b/src/archives/middleware/archived-enterprise-versions.ts index 35f65e00e68d..fd2be04e9251 100644 --- a/src/archives/middleware/archived-enterprise-versions.ts +++ b/src/archives/middleware/archived-enterprise-versions.ts @@ -14,7 +14,7 @@ import { isArchivedVersion } from '@/archives/lib/is-archived-version' import { setFastlySurrogateKey, SURROGATE_ENUMS } from '@/frame/middleware/set-fastly-surrogate-key' import { readCompressedJsonFileFallbackLazily } from '@/frame/lib/read-json-file' import { archivedCacheControl, languageCacheControl } from '@/frame/middleware/cache-control' -import { pathLanguagePrefixed, languagePrefixPathRegex } from '@/languages/lib/languages' +import { pathLanguagePrefixed, languagePrefixPathRegex } from '@/languages/lib/languages-server' import getRedirect, { splitPathByLanguage } from '@/redirects/lib/get-redirect' import getRemoteJSON from '@/frame/lib/get-remote-json' import { ExtendedRequest } from '@/types' diff --git a/src/article-api/scripts/precompute-pageinfo.ts b/src/article-api/scripts/precompute-pageinfo.ts index 655e96c22ccc..e1a73c49d999 100644 --- a/src/article-api/scripts/precompute-pageinfo.ts +++ b/src/article-api/scripts/precompute-pageinfo.ts @@ -30,7 +30,7 @@ import { brotliCompressSync } from 'zlib' import chalk from 'chalk' import { program, Option } from 'commander' -import { languageKeys } from '@/languages/lib/languages' +import { languageKeys } from '@/languages/lib/languages-server' import { loadPages, loadUnversionedTree } from '@/frame/lib/page-data' import { CACHE_FILE_PATH, getPageInfo } from '../middleware/article-pageinfo' diff --git a/src/assets/scripts/find-orphaned-assets.ts b/src/assets/scripts/find-orphaned-assets.ts index 9180fdbc9e1f..7b9aa2dbff7b 100755 --- a/src/assets/scripts/find-orphaned-assets.ts +++ b/src/assets/scripts/find-orphaned-assets.ts @@ -11,7 +11,7 @@ import { program } from 'commander' import walk from 'walk-sync' import walkFiles from '@/workflows/walk-files' -import languages from '@/languages/lib/languages' +import languages from '@/languages/lib/languages-server' const EXCEPTIONS = new Set([ 'assets/images/site/favicon.ico', diff --git a/src/codeql-cli/scripts/convert-markdown-for-docs.ts b/src/codeql-cli/scripts/convert-markdown-for-docs.ts index 316116daa10b..af583ed05a9b 100644 --- a/src/codeql-cli/scripts/convert-markdown-for-docs.ts +++ b/src/codeql-cli/scripts/convert-markdown-for-docs.ts @@ -7,7 +7,7 @@ import { visitParents } from 'unist-util-visit-parents' import { visit, SKIP } from 'unist-util-visit' import { remove } from 'unist-util-remove' -import { languageKeys } from '@/languages/lib/languages' +import { languageKeys } from '@/languages/lib/languages-server' import { MARKDOWN_OPTIONS } from '../../content-linter/lib/helpers/unified-formatter-options' interface Config { diff --git a/src/content-linter/lib/linting-rules/internal-links-no-lang.ts b/src/content-linter/lib/linting-rules/internal-links-no-lang.ts index d6f318aa43e1..fbaabf9cb55b 100644 --- a/src/content-linter/lib/linting-rules/internal-links-no-lang.ts +++ b/src/content-linter/lib/linting-rules/internal-links-no-lang.ts @@ -2,7 +2,7 @@ import { filterTokens } from 'markdownlint-rule-helpers' import { addFixErrorDetail, getRange } from '../helpers/utils' -import { allLanguageKeys } from '@/languages/lib/languages' +import { languageKeys } from '@/languages/lib/languages' import type { RuleParams, RuleErrorCallback, Rule } from '../../types' export const internalLinksNoLang: Rule = { @@ -28,7 +28,7 @@ export const internalLinksNoLang: Rule = { .filter((attr: [string, string]) => attr[1].startsWith('/') || !attr[1].startsWith('//')) // Filter out link paths that start with language code .filter((attr: [string, string]) => - allLanguageKeys.some((lang) => attr[1].split('/')[1] === lang), + languageKeys.some((lang) => attr[1].split('/')[1] === lang), ) // Get the link path from the attribute .map((attr: [string, string]) => attr[1]) diff --git a/src/content-linter/scripts/lint-content.ts b/src/content-linter/scripts/lint-content.ts index 1243a7c375e7..aeca69d6ce07 100755 --- a/src/content-linter/scripts/lint-content.ts +++ b/src/content-linter/scripts/lint-content.ts @@ -15,7 +15,7 @@ import { defaultConfig } from '../lib/default-markdownlint-options' import { prettyPrintResults } from './pretty-print-results' import { getLintableYml } from '@/content-linter/lib/helpers/get-lintable-yml' import { printAnnotationResults } from '../lib/helpers/print-annotations' -import languages from '@/languages/lib/languages' +import languages from '@/languages/lib/languages-server' import { shouldIncludeResult } from '../lib/helpers/should-include-result' program diff --git a/src/content-linter/tests/lint-files.ts b/src/content-linter/tests/lint-files.ts index e2e62f52521c..f0ad3f5c4e8d 100755 --- a/src/content-linter/tests/lint-files.ts +++ b/src/content-linter/tests/lint-files.ts @@ -9,7 +9,7 @@ import walk from 'walk-sync' import { zip } from 'lodash-es' import { beforeAll, describe, expect, test } from 'vitest' -import languages from '@/languages/lib/languages' +import languages from '@/languages/lib/languages-server' import { getDiffFiles } from '../lib/diff-files' const __dirname = path.dirname(fileURLToPath(import.meta.url)) diff --git a/src/content-render/scripts/all-documents/cli.ts b/src/content-render/scripts/all-documents/cli.ts index db32f513bc77..45c7701cb3bb 100644 --- a/src/content-render/scripts/all-documents/cli.ts +++ b/src/content-render/scripts/all-documents/cli.ts @@ -43,7 +43,7 @@ import { writeFileSync, statSync } from 'fs' import { program, Option } from 'commander' -import { languageKeys } from '@/languages/lib/languages' +import { languageKeys } from '@/languages/lib/languages-server' import { allVersions } from '@/versions/lib/all-versions' import { allDocuments, POSSIBLE_FIELDS, type AllDocument } from './lib' diff --git a/src/content-render/tests/data.ts b/src/content-render/tests/data.ts index b55f997d0269..78218c0947e8 100644 --- a/src/content-render/tests/data.ts +++ b/src/content-render/tests/data.ts @@ -1,7 +1,7 @@ import { afterAll, beforeAll, describe, expect, test } from 'vitest' import Page from '@/frame/lib/page' -import languages from '@/languages/lib/languages' +import languages from '@/languages/lib/languages-server' import nonEnterpriseDefaultVersion from '@/versions/lib/non-enterprise-default-version' import { DataDirectory } from '@/tests/helpers/data-directory' diff --git a/src/content-render/tests/liquid-helpers.ts b/src/content-render/tests/liquid-helpers.ts index 57a9a863b489..9cc9fd671b29 100644 --- a/src/content-render/tests/liquid-helpers.ts +++ b/src/content-render/tests/liquid-helpers.ts @@ -1,7 +1,7 @@ import { afterAll, beforeAll, describe, expect, test, vi } from 'vitest' import { liquid } from '@/content-render/index' -import languages from '@/languages/lib/languages' +import languages from '@/languages/lib/languages-server' import { DataDirectory } from '@/tests/helpers/data-directory' describe('liquid helper tags', () => { diff --git a/src/data-directory/lib/data-schemas/tables/copilot/model-deprecation-history.ts b/src/data-directory/lib/data-schemas/tables/copilot/model-deprecation-history.ts new file mode 100644 index 000000000000..ba31dc99efb6 --- /dev/null +++ b/src/data-directory/lib/data-schemas/tables/copilot/model-deprecation-history.ts @@ -0,0 +1,33 @@ +// This schema enforces the structure in model-deprecation-history.yml + +const modelDeprecationHistorySchema = { + type: 'object', + additionalProperties: false, + required: ['models'], + properties: { + models: { + type: 'object', + items: { + type: 'object', + additionalProperties: false, + required: ['name', 'retirement_date', 'suggested_alternative'], + properties: { + name: { + type: 'string', + lintable: true, + }, + retirement_date: { + type: 'string', + format: 'date', + }, + suggested_alternative: { + type: 'string', + lintable: true, + }, + }, + }, + }, + }, +} + +export default modelDeprecationHistorySchema diff --git a/src/data-directory/lib/data-schemas/tables/copilot/model-multipliers.ts b/src/data-directory/lib/data-schemas/tables/copilot/model-multipliers.ts new file mode 100644 index 000000000000..bd89d584236e --- /dev/null +++ b/src/data-directory/lib/data-schemas/tables/copilot/model-multipliers.ts @@ -0,0 +1,33 @@ +// This schema enforces the structure in model-multipliers.yml + +const modelMultipliersSchema = { + type: 'object', + additionalProperties: false, + required: ['models'], + properties: { + models: { + type: 'object', + items: { + type: 'object', + additionalProperties: false, + required: ['name', 'multiplier_paid', 'multiplier_free'], + properties: { + name: { + type: 'string', + lintable: true, + }, + multiplier_paid: { + type: 'string', + lintable: true, + }, + multiplier_free: { + type: 'string', + lintable: true, + }, + }, + }, + }, + }, +} + +export default modelMultipliersSchema diff --git a/src/data-directory/lib/data-schemas/tables/copilot/model-release-status.ts b/src/data-directory/lib/data-schemas/tables/copilot/model-release-status.ts new file mode 100644 index 000000000000..e5cde3812677 --- /dev/null +++ b/src/data-directory/lib/data-schemas/tables/copilot/model-release-status.ts @@ -0,0 +1,42 @@ +// This schema enforces the structure in model-release-status.yml + +const modelsReleaseStatusSchema = { + type: 'object', + additionalProperties: false, + required: ['models'], + properties: { + models: { + type: 'object', + items: { + type: 'object', + additionalProperties: false, + required: ['name', 'provider', 'release_status', 'agent_mode', 'ask_mode', 'edit_mode'], + properties: { + name: { + type: 'string', + lintable: true, + }, + provider: { + type: 'string', + enum: ['OpenAI', 'Anthropic', 'Google', 'xAI'], + }, + release_status: { + type: 'string', + lintable: true, + }, + agent_mode: { + type: 'boolean', + }, + ask_mode: { + type: 'boolean', + }, + edit_mode: { + type: 'boolean', + }, + }, + }, + }, + }, +} + +export default modelsReleaseStatusSchema diff --git a/src/data-directory/lib/data-schemas/tables/copilot/model-supported-clients.ts b/src/data-directory/lib/data-schemas/tables/copilot/model-supported-clients.ts new file mode 100644 index 000000000000..ffb28af36dc2 --- /dev/null +++ b/src/data-directory/lib/data-schemas/tables/copilot/model-supported-clients.ts @@ -0,0 +1,43 @@ +// This schema enforces the structure in model-supported-clients.yml + +const modelsSupportedClientsSchema = { + type: 'object', + additionalProperties: false, + required: ['models'], + properties: { + models: { + type: 'object', + items: { + type: 'object', + additionalProperties: false, + required: ['name', 'dotcom', 'vscode', 'vs', 'eclipse', 'xcode', 'jetbrains'], + properties: { + name: { + type: 'string', + lintable: true, + }, + dotcom: { + type: 'boolean', + }, + vscode: { + type: 'boolean', + }, + vs: { + type: 'boolean', + }, + eclipse: { + type: 'boolean', + }, + xcode: { + type: 'boolean', + }, + jetbrains: { + type: 'boolean', + }, + }, + }, + }, + }, +} + +export default modelsSupportedClientsSchema diff --git a/src/data-directory/lib/data-schemas/tables/copilot/model-supported-plans.ts b/src/data-directory/lib/data-schemas/tables/copilot/model-supported-plans.ts new file mode 100644 index 000000000000..c9a737a06618 --- /dev/null +++ b/src/data-directory/lib/data-schemas/tables/copilot/model-supported-plans.ts @@ -0,0 +1,40 @@ +// This schema enforces the structure in model-supported-plans.yml + +const modelSupportedPlansSchema = { + type: 'object', + additionalProperties: false, + required: ['models'], + properties: { + models: { + type: 'object', + items: { + type: 'object', + additionalProperties: false, + required: ['name', 'free', 'pro', 'pro_plus', 'business', 'enterprise'], + properties: { + name: { + type: 'string', + lintable: true, + }, + free: { + type: 'boolean', + }, + pro: { + type: 'boolean', + }, + pro_plus: { + type: 'boolean', + }, + business: { + type: 'boolean', + }, + enterprise: { + type: 'boolean', + }, + }, + }, + }, + }, +} + +export default modelSupportedPlansSchema diff --git a/src/data-directory/lib/get-data.ts b/src/data-directory/lib/get-data.ts index 6478c8f3ec3f..55118a8eaa40 100644 --- a/src/data-directory/lib/get-data.ts +++ b/src/data-directory/lib/get-data.ts @@ -5,7 +5,7 @@ import yaml from 'js-yaml' import matter from '@gr2m/gray-matter' import { merge, get } from 'lodash-es' -import languages from '@/languages/lib/languages' +import languages from '@/languages/lib/languages-server' import { correctTranslatedContentStrings } from '@/languages/lib/correct-translation-content' interface YAMLException extends Error { diff --git a/src/data-directory/scripts/find-orphaned-features/delete.ts b/src/data-directory/scripts/find-orphaned-features/delete.ts index fe2d8964f62a..bf39b5773057 100644 --- a/src/data-directory/scripts/find-orphaned-features/delete.ts +++ b/src/data-directory/scripts/find-orphaned-features/delete.ts @@ -2,7 +2,7 @@ import fs from 'fs' import path from 'path' import chalk from 'chalk' -import languages from '@/languages/lib/languages' +import languages from '@/languages/lib/languages-server' type Options = { verbose?: boolean diff --git a/src/data-directory/scripts/find-orphaned-features/find.ts b/src/data-directory/scripts/find-orphaned-features/find.ts index 91164d42fd4a..bce2bb69b642 100644 --- a/src/data-directory/scripts/find-orphaned-features/find.ts +++ b/src/data-directory/scripts/find-orphaned-features/find.ts @@ -38,7 +38,7 @@ import type { Page } from '@/types' import warmServer from '@/frame/lib/warm-server' import { getDeepDataByLanguage } from '@/data-directory/lib/get-data' import { getLiquidTokens } from '@/content-linter/lib/helpers/liquid-utils' -import languages from '@/languages/lib/languages' +import languages from '@/languages/lib/languages-server' import { correctTranslatedContentStrings } from '@/languages/lib/correct-translation-content' const EXCEPTIONS = new Set([ diff --git a/src/data-directory/tests/get-data.ts b/src/data-directory/tests/get-data.ts index df7f5dfcc481..cddbeac7e555 100644 --- a/src/data-directory/tests/get-data.ts +++ b/src/data-directory/tests/get-data.ts @@ -3,7 +3,7 @@ import path from 'path' import { afterAll, beforeAll, describe, expect, test } from 'vitest' -import languages from '@/languages/lib/languages' +import languages from '@/languages/lib/languages-server' import { getDataByLanguage, getDeepDataByLanguage, diff --git a/src/early-access/tests/early-access-unit.ts b/src/early-access/tests/early-access-unit.ts index 3f6fc95257b9..d133f1479a12 100644 --- a/src/early-access/tests/early-access-unit.ts +++ b/src/early-access/tests/early-access-unit.ts @@ -2,7 +2,7 @@ import { expect, test, vi } from 'vitest' import { get, getDOM } from '@/tests/helpers/e2etest' import { describeIfDocsEarlyAccess } from '@/tests/helpers/conditional-runs' -import languages from '@/languages/lib/languages' +import languages from '@/languages/lib/languages-server' const VALID_EARLY_ACCESS_URI = '/early-access/github/save-time-with-slash-commands' diff --git a/src/events/lib/schema.ts b/src/events/lib/schema.ts index 95a52b6c81d2..92e72c75118b 100644 --- a/src/events/lib/schema.ts +++ b/src/events/lib/schema.ts @@ -1,4 +1,4 @@ -import { languageKeys } from '@/languages/lib/languages' +import { languageKeys } from '@/languages/lib/languages-server' import { allVersionKeys } from '@/versions/lib/all-versions' import { productIds } from '@/products/lib/all-products' import { allTools } from '@/tools/lib/all-tools' diff --git a/src/fixtures/fixtures/data/ui.yml b/src/fixtures/fixtures/data/ui.yml index e77f87975515..1e3986a0f4ec 100644 --- a/src/fixtures/fixtures/data/ui.yml +++ b/src/fixtures/fixtures/data/ui.yml @@ -69,11 +69,6 @@ search: general_title: There was an error loading search results. ai_title: There was an error loading Copilot. description: You can still use this field to search our docs. - cta: - heading: Get quick answers! - description: Ask Copilot your question. - dismiss: Dismiss - ask_copilot: Ask Copilot old_search: description: Enter a search term to find it in the GitHub Docs. placeholder: Search GitHub Docs diff --git a/src/frame/components/page-footer/SupportSection.module.scss b/src/frame/components/page-footer/SupportSection.module.scss index 1bb704c2bc40..fb3c73e29441 100644 --- a/src/frame/components/page-footer/SupportSection.module.scss +++ b/src/frame/components/page-footer/SupportSection.module.scss @@ -17,9 +17,9 @@ } } -// Large is 4 columns +// Large is 3 columns @media (min-width: 1280px) { .supportGrid { - grid-template-columns: minmax(18rem, 1fr) repeat(3, 1fr); + grid-template-columns: repeat(3, 1fr); } } diff --git a/src/frame/components/page-footer/SupportSection.tsx b/src/frame/components/page-footer/SupportSection.tsx index b153c5df4658..494745b3fd0a 100644 --- a/src/frame/components/page-footer/SupportSection.tsx +++ b/src/frame/components/page-footer/SupportSection.tsx @@ -7,8 +7,6 @@ import { useMainContext } from '@/frame/components/context/MainContext' import { useVersion } from '@/versions/components/useVersion' import { useRouter } from 'next/router' import { useTranslation } from '@/languages/components/useTranslation' -import { AISearchCTAPopup } from '@/search/components/input/AISearchCTAPopup' -import { useSearchOverlayContext } from '@/search/components/context/SearchOverlayContext' import styles from './SupportSection.module.scss' @@ -17,7 +15,6 @@ export const SupportSection = () => { const { relativePath, enterpriseServerReleases } = useMainContext() const router = useRouter() const { t } = useTranslation('footer') - const { setIsSearchOpen } = useSearchOverlayContext() const isDeprecated = enterpriseServerReleases.isOldestReleaseDeprecated && @@ -29,7 +26,6 @@ export const SupportSection = () => { const showSurvey = !isDeprecated && !isSitePolicyDocs const showContribution = !isDeprecated && !isEarlyAccess && isEnglish const showSupport = true - const showCopilotCTA = !isDeprecated && !isEarlyAccess && isEnglish return (
@@ -42,14 +38,6 @@ export const SupportSection = () => { styles.supportGrid /* ← adds the grid rules */, )} > - {showCopilotCTA && ( - - )} {showSurvey && } {showContribution && } {showSupport && } diff --git a/src/frame/lib/page-data.ts b/src/frame/lib/page-data.ts index 6f0d204705dc..560fb57ac9b9 100644 --- a/src/frame/lib/page-data.ts +++ b/src/frame/lib/page-data.ts @@ -1,9 +1,9 @@ import path from 'path' +import languages from '@/languages/lib/languages-server' import type { Language } from '@/languages/lib/languages' import type { UnversionedTree, UnversionLanguageTree, SiteTree, Tree } from '@/types' -import languages from '@/languages/lib/languages' import { allVersions } from '@/versions/lib/all-versions' import createTree from './create-tree' import nonEnterpriseDefaultVersion from '@/versions/lib/non-enterprise-default-version' diff --git a/src/frame/middleware/context/context.ts b/src/frame/middleware/context/context.ts index 925c89cd2e30..ef4caa955a8d 100644 --- a/src/frame/middleware/context/context.ts +++ b/src/frame/middleware/context/context.ts @@ -2,7 +2,7 @@ import type { NextFunction, Response } from 'express' import type { ExtendedRequest, Context } from '@/types' -import languages from '@/languages/lib/languages' +import languages from '@/languages/lib/languages-server' import enterpriseServerReleases from '@/versions/lib/enterprise-server-releases' import { allVersions } from '@/versions/lib/all-versions' import { productMap } from '@/products/lib/all-products' diff --git a/src/frame/middleware/context/product-groups.ts b/src/frame/middleware/context/product-groups.ts index 0c88acfc3474..e6f8e4d8f2ac 100644 --- a/src/frame/middleware/context/product-groups.ts +++ b/src/frame/middleware/context/product-groups.ts @@ -3,7 +3,7 @@ import type { Response, NextFunction } from 'express' import type { ExtendedRequest } from '@/types' import { getProductGroups } from '@/products/lib/get-product-groups' import warmServer from '@/frame/lib/warm-server' -import { languageKeys } from '@/languages/lib/languages' +import { languageKeys } from '@/languages/lib/languages-server' import { allVersionKeys } from '@/versions/lib/all-versions' const isHomepage = (path: string) => { diff --git a/src/frame/middleware/find-page.ts b/src/frame/middleware/find-page.ts index 99d7e7570463..00b0013da895 100644 --- a/src/frame/middleware/find-page.ts +++ b/src/frame/middleware/find-page.ts @@ -4,7 +4,7 @@ import type { Response, NextFunction } from 'express' import { ROOT } from '@/frame/lib/constants' import Page from '@/frame/lib/page' -import { languagePrefixPathRegex } from '@/languages/lib/languages' +import { languagePrefixPathRegex } from '@/languages/lib/languages-server' import type { ExtendedRequest } from '@/types' interface FindPageOptions { diff --git a/src/frame/middleware/helmet.ts b/src/frame/middleware/helmet.ts index 9cf66d2c6902..978ed674d825 100644 --- a/src/frame/middleware/helmet.ts +++ b/src/frame/middleware/helmet.ts @@ -1,6 +1,6 @@ import { shouldUseAppRouter, isVersionedPath } from '@/app/lib/routing-patterns' import { isArchivedVersion } from '@/archives/lib/is-archived-version' -import { languagePrefixPathRegex } from '@/languages/lib/languages' +import { languagePrefixPathRegex } from '@/languages/lib/languages-server' import versionSatisfiesRange from '@/versions/lib/version-satisfies-range' import type { NextFunction, Request, Response } from 'express' import helmet from 'helmet' diff --git a/src/frame/middleware/llms-txt.ts b/src/frame/middleware/llms-txt.ts index 975162fde4cd..88020d11ecb9 100644 --- a/src/frame/middleware/llms-txt.ts +++ b/src/frame/middleware/llms-txt.ts @@ -5,7 +5,7 @@ import type { ExtendedRequest } from '@/types' import { defaultCacheControl } from '@/frame/middleware/cache-control' import catchMiddlewareError from '@/observability/middleware/catch-middleware-error' import statsd from '@/observability/lib/statsd' -import languages from '@/languages/lib/languages' +import languages from '@/languages/lib/languages-server' import { allVersions } from '@/versions/lib/all-versions' const router = express.Router() diff --git a/src/frame/middleware/reload-tree.ts b/src/frame/middleware/reload-tree.ts index 04513c3ddacc..2f1c4ff9a4df 100644 --- a/src/frame/middleware/reload-tree.ts +++ b/src/frame/middleware/reload-tree.ts @@ -19,7 +19,7 @@ import path from 'path' import type { Response, NextFunction } from 'express' import type { ExtendedRequest, UnversionedTree, SiteTree } from '@/types' -import languages, { languageKeys } from '@/languages/lib/languages' +import languages, { languageKeys } from '@/languages/lib/languages-server' import createTree from '@/frame/lib/create-tree' import warmServer from '@/frame/lib/warm-server' import { loadSiteTree, loadPages, loadPageMap } from '@/frame/lib/page-data' diff --git a/src/frame/tests/pages.ts b/src/frame/tests/pages.ts index 76ed444e4b79..af8c4efc412e 100644 --- a/src/frame/tests/pages.ts +++ b/src/frame/tests/pages.ts @@ -6,7 +6,7 @@ import { decode } from 'html-entities' import { chain, pick } from 'lodash-es' import { loadPages } from '@/frame/lib/page-data' -import libLanguages from '@/languages/lib/languages' +import libLanguages from '@/languages/lib/languages-server' import { liquid } from '@/content-render/index' import patterns from '@/frame/lib/patterns' import removeFPTFromPath from '@/versions/lib/remove-fpt-from-path' diff --git a/src/ghes-releases/scripts/deprecate/archive-version.ts b/src/ghes-releases/scripts/deprecate/archive-version.ts index 91107319c485..e3f0c3435b3b 100755 --- a/src/ghes-releases/scripts/deprecate/archive-version.ts +++ b/src/ghes-releases/scripts/deprecate/archive-version.ts @@ -17,7 +17,7 @@ import createApp from '@/frame/lib/app' import EnterpriseServerReleases from '@/versions/lib/enterprise-server-releases' import loadRedirects from '@/redirects/lib/precompile' import { loadPageMap, loadPages } from '@/frame/lib/page-data' -import { languageKeys } from '@/languages/lib/languages' +import { languageKeys } from '@/languages/lib/languages-server' import { RewriteAssetPathsPlugin } from '@/ghes-releases/scripts/deprecate/rewrite-asset-paths' import Page from '@/frame/lib/page' diff --git a/src/graphql/lib/index.ts b/src/graphql/lib/index.ts index d7c39a43d189..93581761847b 100644 --- a/src/graphql/lib/index.ts +++ b/src/graphql/lib/index.ts @@ -3,7 +3,7 @@ import { readCompressedJsonFileFallback, } from '@/frame/lib/read-json-file' import { getAutomatedPageMiniTocItems } from '@/frame/lib/get-mini-toc-items' -import languages from '@/languages/lib/languages' +import languages from '@/languages/lib/languages-server' import { allVersions } from '@/versions/lib/all-versions' interface GraphqlContext { currentLanguage: string diff --git a/src/landings/components/ArticleList.module.css b/src/landings/components/ArticleList.module.scss similarity index 91% rename from src/landings/components/ArticleList.module.css rename to src/landings/components/ArticleList.module.scss index f05555b0a77c..1139be56603c 100644 --- a/src/landings/components/ArticleList.module.css +++ b/src/landings/components/ArticleList.module.scss @@ -5,7 +5,8 @@ border-radius: 0; } - h3, span { + h3, + span { user-select: text; color: var(--fgColor-accent, var(--color-accent-fg)); } diff --git a/src/landings/components/ArticleList.tsx b/src/landings/components/ArticleList.tsx index ed987d0447be..d36b831f71bf 100644 --- a/src/landings/components/ArticleList.tsx +++ b/src/landings/components/ArticleList.tsx @@ -5,7 +5,7 @@ import { ArrowRightIcon } from '@primer/octicons-react' import { ActionList } from '@primer/react' import { clsx } from 'clsx' import dayjs from 'dayjs' -import styles from './ArticleList.module.css' +import styles from './ArticleList.module.scss' export type ArticleListPropsT = { title?: string diff --git a/src/landings/components/CategoryLanding.tsx b/src/landings/components/CategoryLanding.tsx index 7e62560830f3..e2d12bafeba8 100644 --- a/src/landings/components/CategoryLanding.tsx +++ b/src/landings/components/CategoryLanding.tsx @@ -35,7 +35,10 @@ export const CategoryLanding = () => { if (typeof value === 'string') { return value.toLowerCase().includes(searchQuery.toLowerCase()) } else if (Array.isArray(value)) { - return value.some((item) => item.toLowerCase().includes(searchQuery.toLowerCase())) + return value.some( + (item) => + typeof item === 'string' && item.toLowerCase().includes(searchQuery.toLowerCase()), + ) } return false }) diff --git a/src/landings/components/ProductArticlesList.module.css b/src/landings/components/ProductArticlesList.module.scss similarity index 99% rename from src/landings/components/ProductArticlesList.module.css rename to src/landings/components/ProductArticlesList.module.scss index be7dda8597bd..2a72356c5e1c 100644 --- a/src/landings/components/ProductArticlesList.module.css +++ b/src/landings/components/ProductArticlesList.module.scss @@ -12,4 +12,4 @@ text-decoration: underline; } } -} \ No newline at end of file +} diff --git a/src/landings/components/ProductArticlesList.tsx b/src/landings/components/ProductArticlesList.tsx index 2b9ab311b36b..f025e1376a6a 100644 --- a/src/landings/components/ProductArticlesList.tsx +++ b/src/landings/components/ProductArticlesList.tsx @@ -3,7 +3,7 @@ import { ActionList } from '@primer/react' import { ProductTreeNode, useMainContext } from '@/frame/components/context/MainContext' import { Link } from '@/frame/components/Link' import clsx from 'clsx' -import styles from './ProductArticlesList.module.css' +import styles from './ProductArticlesList.module.scss' export const ProductArticlesList = () => { const { currentProductTree } = useMainContext() diff --git a/src/landings/components/SidebarProduct.module.css b/src/landings/components/SidebarProduct.module.scss similarity index 100% rename from src/landings/components/SidebarProduct.module.css rename to src/landings/components/SidebarProduct.module.scss diff --git a/src/landings/components/SidebarProduct.tsx b/src/landings/components/SidebarProduct.tsx index 31d777e2c4ad..89494c3b1e6f 100644 --- a/src/landings/components/SidebarProduct.tsx +++ b/src/landings/components/SidebarProduct.tsx @@ -7,7 +7,7 @@ import { ProductTreeNode, useMainContext } from '@/frame/components/context/Main import { useAutomatedPageContext } from '@/automated-pipelines/components/AutomatedPageContext' import { nonAutomatedRestPaths } from '@/rest/lib/config' -import styles from './SidebarProduct.module.css' +import styles from './SidebarProduct.module.scss' export const SidebarProduct = () => { const router = useRouter() diff --git a/src/landings/components/TableOfContents.module.css b/src/landings/components/TableOfContents.module.css deleted file mode 100644 index bdc4cb6f612c..000000000000 --- a/src/landings/components/TableOfContents.module.css +++ /dev/null @@ -1,16 +0,0 @@ -/* TableOfContents CSS Module */ - -.linkItem { - font-size: 1rem !important; - color: var(--fgColor-accent) !important; - display: block !important; - width: 100% !important; - text-decoration: underline !important; - - span { - user-select: text; - color: var(--fgColor-accent, var(--color-accent-fg)) !important; - font-size: inherit !important; - text-decoration: inherit !important; - } -} diff --git a/src/landings/components/TableOfContents.tsx b/src/landings/components/TableOfContents.tsx index f2c16c297e34..274c13d9a1ef 100644 --- a/src/landings/components/TableOfContents.tsx +++ b/src/landings/components/TableOfContents.tsx @@ -1,10 +1,7 @@ -import cx from 'classnames' import React from 'react' import { Link } from '@/frame/components/Link' import type { TocItem } from '@/landings/types' -import { ActionList } from '@primer/react' -import styles from './TableOfContents.module.css' type Props = { items: Array @@ -14,10 +11,7 @@ export const TableOfContents = (props: Props) => { const { items, variant = 'expanded' } = props return ( -
+
{variant === 'expanded' && items.map((item) => { const { fullPath: href, title, intro } = item @@ -41,40 +35,29 @@ export const TableOfContents = (props: Props) => { })} {variant === 'compact' && ( - +
    {items.map((item) => { const { fullPath, title, childTocItems } = item return ( - - +
  • + {title} - - {(childTocItems || []).length > 0 && ( -
  • - - {(childTocItems || []).filter(Boolean).map((childItem) => { - return ( - - {childItem.title} - - ) - })} - -
  • + + {(childTocItems || []).filter(Boolean).length > 0 && ( +
      + {(childTocItems || []).filter(Boolean).map((childItem) => ( +
    • + + {childItem.title} + +
    • + ))} +
    )} -
    + ) })} - +
)}
) diff --git a/src/landings/components/bespoke/BespokeLanding.tsx b/src/landings/components/bespoke/BespokeLanding.tsx index cd97570b5b04..5dc38b35890a 100644 --- a/src/landings/components/bespoke/BespokeLanding.tsx +++ b/src/landings/components/bespoke/BespokeLanding.tsx @@ -1,5 +1,3 @@ -import { useMemo } from 'react' - import { DefaultLayout } from '@/frame/components/DefaultLayout' import { useLandingContext } from '@/landings/context/LandingContext' import { LandingHero } from '@/landings/components/shared/LandingHero' @@ -7,16 +5,9 @@ import { ArticleGrid } from '@/landings/components/shared/LandingArticleGridWith import { UtmPreserver } from '@/frame/components/UtmPreserver' import { LandingCarousel } from '@/landings/components/shared/LandingCarousel' -import type { ArticleCardItems } from '@/landings/types' - export const BespokeLanding = () => { const { title, intro, heroImage, introLinks, tocItems, recommended } = useLandingContext() - const flatArticles: ArticleCardItems = useMemo( - () => tocItems.flatMap((item) => item.childTocItems || []), - [tocItems], - ) - return ( @@ -25,7 +16,7 @@ export const BespokeLanding = () => {
- +
diff --git a/src/landings/components/discovery/DiscoveryLanding.tsx b/src/landings/components/discovery/DiscoveryLanding.tsx index 105b671b81bb..c8fe5a2d5974 100644 --- a/src/landings/components/discovery/DiscoveryLanding.tsx +++ b/src/landings/components/discovery/DiscoveryLanding.tsx @@ -1,5 +1,3 @@ -import { useMemo } from 'react' - import { DefaultLayout } from '@/frame/components/DefaultLayout' import { useLandingContext } from '@/landings/context/LandingContext' import { LandingHero } from '@/landings/components/shared/LandingHero' @@ -7,16 +5,9 @@ import { ArticleGrid } from '@/landings/components/shared/LandingArticleGridWith import { LandingCarousel } from '@/landings/components/shared/LandingCarousel' import { UtmPreserver } from '@/frame/components/UtmPreserver' -import type { ArticleCardItems } from '@/landings/types' - export const DiscoveryLanding = () => { const { title, intro, heroImage, introLinks, tocItems, recommended } = useLandingContext() - const flatArticles: ArticleCardItems = useMemo( - () => tocItems.flatMap((item) => item.childTocItems || []), - [tocItems], - ) - return ( @@ -24,7 +15,7 @@ export const DiscoveryLanding = () => {
- +
diff --git a/src/landings/components/journey/JourneyLearningTracks.module.css b/src/landings/components/journey/JourneyLearningTracks.module.scss similarity index 75% rename from src/landings/components/journey/JourneyLearningTracks.module.css rename to src/landings/components/journey/JourneyLearningTracks.module.scss index e21321b55175..58e3fff5fda1 100644 --- a/src/landings/components/journey/JourneyLearningTracks.module.css +++ b/src/landings/components/journey/JourneyLearningTracks.module.scss @@ -1,8 +1,9 @@ .learningTracks { - border: 1px solid var(--borderColor-default, var(--color-border-default, #d1d9e0)); + border: 1px solid + var(--borderColor-default, var(--color-border-default, #d1d9e0)); border-radius: 12px; padding: 1.5rem; - padding-bottom: .75rem; + padding-bottom: 0.75rem; margin-bottom: 1rem; margin-left: 1rem; box-shadow: @@ -10,7 +11,10 @@ 0px 1px 0px 0px rgba(31, 35, 40, 0.06); position: relative; z-index: 1; - background-color: var(--bgColor-default, var(--color-canvas-default, #ffffff)); + background-color: var( + --bgColor-default, + var(--color-canvas-default, #ffffff) + ); } .trackHeader { @@ -60,13 +64,14 @@ width: 1.5rem; height: 1.5rem; background-color: transparent; - border: 1px solid var(--borderColor-default, var(--color-border-default, #d0d7de)); + border: 1px solid + var(--borderColor-default, var(--color-border-default, #d0d7de)); color: var(--fgColor-muted, var(--color-fg-muted, #656d76)); border-radius: 50%; display: flex; align-items: center; justify-content: center; - font-size: 0.70rem; + font-size: 0.7rem; font-weight: 600; line-height: 1; } @@ -78,12 +83,19 @@ /* Hide only the timeline line that extends below the last badge, preserve everything else */ .timelineContainer :global(.Timeline-Item:last-child::before) { - background: linear-gradient(to bottom, var(--borderColor-muted, var(--color-border-muted)) 0%, var(--borderColor-muted, var(--color-border-muted)) 30%, transparent 30%, transparent 100%) !important; + background: linear-gradient( + to bottom, + var(--borderColor-muted, var(--color-border-muted)) 0%, + var(--borderColor-muted, var(--color-border-muted)) 30%, + transparent 30%, + transparent 100% + ) !important; } .timelineBadge { background-color: var(--color-canvas-subtle, #f6f8fa) !important; - border: 1px solid var(--borderColor-default, var(--color-border-default, #d1d9e0)) !important; + border: 1px solid + var(--borderColor-default, var(--color-border-default, #d1d9e0)) !important; } /* Fix entire timeline component overlapping header */ @@ -118,7 +130,8 @@ height: 2rem; background-color: var(--color-canvas-subtle, #f6f8fa); color: var(--fgColor-muted, var(--color-fg-muted)); - border: 1px solid var(--borderColor-default, var(--color-border-default, #d1d9e0)); + border: 1px solid + var(--borderColor-default, var(--color-border-default, #d1d9e0)); border-radius: 50%; display: inline-flex; align-items: center; @@ -131,26 +144,32 @@ /* Add connecting line from badge downward */ .mobileBadge::after { - content: ''; + content: ""; position: absolute; left: 50%; top: 100%; width: 1px; height: 2.5rem; - background-color: var(--borderColor-default, var(--color-border-default, #d1d9e0)); + background-color: var( + --borderColor-default, + var(--color-border-default, #d1d9e0) + ); transform: translateX(-50%); z-index: 1; } /* Add connecting line above badge (except first item) */ .mobileItem:not(:first-child) .mobileBadge::before { - content: ''; + content: ""; position: absolute; left: 50%; bottom: 100%; width: 1px; height: 2.5rem; - background-color: var(--borderColor-default, var(--color-border-default, #d1d9e0)); + background-color: var( + --borderColor-default, + var(--color-border-default, #d1d9e0) + ); transform: translateX(-50%); z-index: 1; } @@ -158,7 +177,10 @@ .mobileConnector { width: 1px; height: 1rem; - background-color: var(--borderColor-default, var(--color-border-default, #d1d9e0)); + background-color: var( + --borderColor-default, + var(--color-border-default, #d1d9e0) + ); margin: 0 auto; } @@ -167,7 +189,8 @@ } .mobileItem .mobileTile { - border: 1px solid var(--borderColor-default, var(--color-border-default, #d1d9e0)); + border: 1px solid + var(--borderColor-default, var(--color-border-default, #d1d9e0)); border-radius: 12px; padding: 1rem; margin-top: 0.5rem; @@ -177,7 +200,10 @@ 0px 1px 0px 0px rgba(31, 35, 40, 0.06); position: relative; z-index: 3; - background-color: var(--bgColor-default, var(--color-canvas-default, #ffffff)); + background-color: var( + --bgColor-default, + var(--color-canvas-default, #ffffff) + ); } /* Desktop: show Timeline component */ @@ -185,7 +211,7 @@ .mobileLayout { display: none; } - + .timelineContainer { display: block; } @@ -208,6 +234,6 @@ } .trackHeader h3 { - margin-bottom: .50rem; + margin-bottom: 0.5rem; } -} \ No newline at end of file +} diff --git a/src/landings/components/journey/JourneyLearningTracks.tsx b/src/landings/components/journey/JourneyLearningTracks.tsx index a742e30fd16f..03997a07873c 100644 --- a/src/landings/components/journey/JourneyLearningTracks.tsx +++ b/src/landings/components/journey/JourneyLearningTracks.tsx @@ -3,7 +3,7 @@ import { ChevronDownIcon, ChevronUpIcon } from '@primer/octicons-react' import { Button, Details, Timeline, Token, useDetails } from '@primer/react' import { Link } from '@/frame/components/Link' import { JourneyTrack } from '@/journeys/lib/journey-path-resolver' -import styles from './JourneyLearningTracks.module.css' +import styles from './JourneyLearningTracks.module.scss' type JourneyLearningTracksProps = { tracks: JourneyTrack[] diff --git a/src/landings/components/shared/LandingArticleGridWithFilter.tsx b/src/landings/components/shared/LandingArticleGridWithFilter.tsx index abc3d20205ff..841f86bd81c7 100644 --- a/src/landings/components/shared/LandingArticleGridWithFilter.tsx +++ b/src/landings/components/shared/LandingArticleGridWithFilter.tsx @@ -1,20 +1,44 @@ -import { useState, useRef, useEffect } from 'react' +import { useState, useRef, useEffect, useMemo } from 'react' import { TextInput, ActionMenu, ActionList, Token, Pagination } from '@primer/react' import { SearchIcon } from '@primer/octicons-react' import cx from 'classnames' import { Link } from '@/frame/components/Link' import { useTranslation } from '@/languages/components/useTranslation' -import { ArticleCardItems, ChildTocItem } from '@/landings/types' +import { ArticleCardItems, ChildTocItem, TocItem } from '@/landings/types' import styles from './LandingArticleGridWithFilter.module.scss' type ArticleGridProps = { - flatArticles: ArticleCardItems + tocItems: TocItem[] } const ALL_CATEGORIES = 'all_categories' +// Helper function to recursively flatten nested articles +// Excludes index pages (pages with childTocItems) +const flattenArticlesRecursive = (articles: ArticleCardItems): ArticleCardItems => { + const flattened: ArticleCardItems = [] + + for (const article of articles) { + // If the article has children, recursively process them but don't include the parent (index page) + if (article.childTocItems && article.childTocItems.length > 0) { + flattened.push(...flattenArticlesRecursive(article.childTocItems)) + } else { + // Only add articles that don't have children (actual article pages, not index pages) + flattened.push(article) + } + } + + return flattened +} + +// Wrapper function that flattens and sorts alphabetically by title (only once) +const flattenArticles = (articles: ArticleCardItems): ArticleCardItems => { + const flattened = flattenArticlesRecursive(articles) + return flattened.sort((a, b) => a.title.localeCompare(b.title)) +} + // Hook to get current articles per page based on screen size const useResponsiveArticlesPerPage = () => { const [articlesPerPage, setArticlesPerPage] = useState(9) // Default to desktop @@ -42,7 +66,7 @@ const useResponsiveArticlesPerPage = () => { return articlesPerPage } -export const ArticleGrid = ({ flatArticles }: ArticleGridProps) => { +export const ArticleGrid = ({ tocItems }: ArticleGridProps) => { const { t } = useTranslation('product_landing') const [searchQuery, setSearchQuery] = useState('') const [selectedCategory, setSelectedCategory] = useState(ALL_CATEGORIES) @@ -53,6 +77,12 @@ export const ArticleGrid = ({ flatArticles }: ArticleGridProps) => { const inputRef = useRef(null) const headingRef = useRef(null) + // Extract child items from tocItems and recursively flatten nested articles to ensure we get all articles with categories + const allArticles = useMemo( + () => flattenArticles(tocItems.flatMap((item) => item.childTocItems || [])), + [tocItems], + ) + // Reset to first page when articlesPerPage changes (screen size changes) useEffect(() => { setCurrentPage(1) @@ -61,13 +91,13 @@ export const ArticleGrid = ({ flatArticles }: ArticleGridProps) => { // Extract unique categories from the articles const categories: string[] = [ ALL_CATEGORIES, - ...Array.from(new Set(flatArticles.flatMap((item) => item.category || []))).sort((a, b) => + ...Array.from(new Set(allArticles.flatMap((item) => item.category || []))).sort((a, b) => a.localeCompare(b), ), ] const applyFilters = () => { - let results = flatArticles + let results = allArticles if (searchQuery) { results = results.filter((token) => { diff --git a/src/landings/types.ts b/src/landings/types.ts index e468cc891134..f6e0ddcfb763 100644 --- a/src/landings/types.ts +++ b/src/landings/types.ts @@ -12,11 +12,13 @@ export type BaseTocItem = { } // Extended type for child TOC items with additional metadata +// This is recursive - children can also have their own children export type ChildTocItem = BaseTocItem & { octicon?: ValidOcticon | null category?: string[] | null complexity?: string[] | null industry?: string[] | null + childTocItems?: ChildTocItem[] } // Main TOC item type that can contain children diff --git a/src/languages/lib/client-languages.ts b/src/languages/lib/client-languages.ts deleted file mode 100644 index c238b09032eb..000000000000 --- a/src/languages/lib/client-languages.ts +++ /dev/null @@ -1,67 +0,0 @@ -import type { LanguageItem } from '@/languages/components/LanguagesContext' - -/** - * Client-safe language data extracted from src/languages/lib/languages.ts. - * Only used by frontend components. - * Does not include server-side logic or Node.js-specific fs or path operations. - */ -export const clientLanguages: Record = { - en: { - name: 'English', - code: 'en', - nativeName: 'English', - hreflang: 'en', - }, - es: { - name: 'Spanish', - code: 'es', - nativeName: 'Español', - hreflang: 'es', - }, - ja: { - name: 'Japanese', - code: 'ja', - nativeName: '日本語', - hreflang: 'ja', - }, - pt: { - name: 'Portuguese', - code: 'pt', - nativeName: 'Português do Brasil', - hreflang: 'pt', - }, - zh: { - name: 'Simplified Chinese', - code: 'zh', - nativeName: '简体中文', - hreflang: 'zh-Hans', - }, - ru: { - name: 'Russian', - code: 'ru', - nativeName: 'Русский', - hreflang: 'ru', - }, - fr: { - name: 'French', - code: 'fr', - nativeName: 'Français', - hreflang: 'fr', - }, - ko: { - name: 'Korean', - code: 'ko', - nativeName: '한국어', - hreflang: 'ko', - }, - de: { - name: 'German', - code: 'de', - nativeName: 'Deutsch', - hreflang: 'de', - }, -} - -export const clientLanguageKeys: string[] = Object.keys(clientLanguages) - -export type ClientLanguageCode = keyof typeof clientLanguages diff --git a/src/languages/lib/get-alert-titles.ts b/src/languages/lib/get-alert-titles.ts index 52406abb62f4..d73e33281c78 100644 --- a/src/languages/lib/get-alert-titles.ts +++ b/src/languages/lib/get-alert-titles.ts @@ -1,7 +1,7 @@ import fs from 'fs/promises' import path from 'path' import yaml from 'js-yaml' -import languages from './languages' +import languages from './languages-server' const cache: Record = {} diff --git a/src/languages/lib/languages-server.ts b/src/languages/lib/languages-server.ts new file mode 100644 index 000000000000..97e41f2ff2b7 --- /dev/null +++ b/src/languages/lib/languages-server.ts @@ -0,0 +1,97 @@ +/* +This file adds the following properties to languages in ./languages.ts: +- dir: string + +This file will also remove languages for local development and tests +that have not be specified by ENABLED_LANGUAGES +*/ + +import path from 'path' +import fs from 'fs' +import dotenv from 'dotenv' + +import { ROOT, TRANSLATIONS_ROOT, TRANSLATIONS_FIXTURE_ROOT } from '@/frame/lib/constants' +import { languages as baseLanguages, type Language as BaseLanguage } from './languages' + +dotenv.config({ quiet: true }) + +// Server-side language extends base language with required dir property +export interface Language extends BaseLanguage { + dir: string +} + +export interface Languages { + [code: string]: Language +} + +function getRoot(languageCode: string): string { + if (languageCode === 'en') return ROOT + + // This one trumps anything else. This makes it possible, and convenient, + // for running tests that depends on testing translations based on + // fixtures exclusively. + if (TRANSLATIONS_FIXTURE_ROOT) { + return path.join(TRANSLATIONS_FIXTURE_ROOT, languageCode) + } + + // example: process.env.TRANSLATIONS_ROOT_ES_ES + const possibleEnvVar = + process.env[`TRANSLATIONS_ROOT_${languageCode.toUpperCase().replace(/-/g, '_')}`] + if (possibleEnvVar) { + return possibleEnvVar + } + + // Default + return path.join(TRANSLATIONS_ROOT, languageCode) +} + +// Build server languages with directory paths +const allLanguagesWithDirs: Languages = {} +for (const [code, lang] of Object.entries(baseLanguages)) { + allLanguagesWithDirs[code] = { + ...lang, + dir: getRoot(lang.locale || code), + } +} + +Object.freeze(allLanguagesWithDirs) + +const languages: Languages = { ...allLanguagesWithDirs } + +if (TRANSLATIONS_FIXTURE_ROOT) { + // Keep all languages that have a directory in the fixture root. + Object.entries(languages).forEach(([code, { dir }]) => { + if (code !== 'en' && !fs.existsSync(dir)) { + delete languages[code] + } + }) +} else if (process.env.ENABLED_LANGUAGES) { + if (process.env.ENABLED_LANGUAGES.toLowerCase() !== 'all') { + Object.keys(languages).forEach((code) => { + if (!process.env.ENABLED_LANGUAGES!.includes(code)) { + delete languages[code] + } + }) + // This makes the translation health report not valid JSON + // console.log(`ENABLED_LANGUAGES: ${process.env.ENABLED_LANGUAGES}`) + } +} else if (process.env.NODE_ENV === 'test') { + // Unless explicitly set, when running tests default to just English + Object.keys(languages).forEach((code) => { + if (code !== 'en') delete languages[code] + }) +} + +export const languageKeys: string[] = Object.keys(languages) + +export const languagePrefixPathRegex: RegExp = new RegExp(`^/(${languageKeys.join('|')})(/|$)`) + +/** Return true if the URL is something like /en/foo or /ja but return false + * if it's something like /foo or /foo/bar or /fr (because French (fr) + * is currently not an active language) + */ +export function pathLanguagePrefixed(path: string): boolean { + return languagePrefixPathRegex.test(path) +} + +export default languages diff --git a/src/languages/lib/languages.ts b/src/languages/lib/languages.ts index ce1fd0538e52..049b03224190 100644 --- a/src/languages/lib/languages.ts +++ b/src/languages/lib/languages.ts @@ -1,26 +1,15 @@ // See also languages-schema.ts // Nota bene: If you are adding a new language, // change accept-language handling in CDN config as well. -import path from 'path' -import fs from 'fs' -import dotenv from 'dotenv' - -import { ROOT, TRANSLATIONS_ROOT, TRANSLATIONS_FIXTURE_ROOT } from '@/frame/lib/constants' - -dotenv.config({ quiet: true }) - -export interface Language { - name: string - nativeName?: string - code: string - hreflang: string - redirectPatterns?: RegExp[] - dir: string -} +/** + * Client-safe language definitions without server-side dependencies. + * For server-side usage with fs/path operations, import from './languages-server.ts' + */ export type LanguageCode = 'en' | 'es' | 'ja' | 'pt' | 'zh' | 'ru' | 'fr' | 'ko' | 'de' export type LocaleCode = + | 'en' | 'es-es' | 'ja-jp' | 'pt-br' @@ -30,57 +19,35 @@ export type LocaleCode = | 'ko-kr' | 'de-de' -export interface Languages { - [code: string]: Language -} - -const possibleEnvVars: Record = { - 'es-es': process.env.TRANSLATIONS_ROOT_ES_ES, - 'ja-jp': process.env.TRANSLATIONS_ROOT_JA_JP, - 'pt-br': process.env.TRANSLATIONS_ROOT_PT_BR, - 'zh-cn': process.env.TRANSLATIONS_ROOT_ZH_CN, - 'ru-ru': process.env.TRANSLATIONS_ROOT_RU_RU, - 'fr-fr': process.env.TRANSLATIONS_ROOT_FR_FR, - 'ko-kr': process.env.TRANSLATIONS_ROOT_KO_KR, - 'de-de': process.env.TRANSLATIONS_ROOT_DE_DE, +export interface Language { + name: string + nativeName?: string + code: LanguageCode + hreflang: string + locale: LocaleCode + redirectPatterns?: RegExp[] + dir?: string } -function getRoot(languageCode: string): string { - if (languageCode === 'en') return ROOT - - // This one trumps anything else. This makes it possible, and convenient, - // for running tests that depends on testing translations based on - // fixtures exclusively. - if (TRANSLATIONS_FIXTURE_ROOT) { - return path.join(TRANSLATIONS_FIXTURE_ROOT, languageCode) - } - - if (languageCode in possibleEnvVars) { - const possibleEnvVar = possibleEnvVars[languageCode as LocaleCode] - if (possibleEnvVar) { - return possibleEnvVar - } - } else { - console.warn(`Not recognized languageCode '${languageCode}'`) - } - // Default - return path.join(TRANSLATIONS_ROOT, languageCode) +export interface Languages { + [code: string]: Language } // Languages in order of accept-language header frequency -const allLanguages: Languages = { +// Note: 'dir' is omitted here as it requires server-side path resolution +export const languages: Languages = { en: { name: 'English', code: 'en', hreflang: 'en', - dir: getRoot('en'), + locale: 'en', }, es: { name: 'Spanish', nativeName: 'Español', code: 'es', hreflang: 'es', - dir: getRoot('es-es'), + locale: 'es-es', }, ja: { name: 'Japanese', @@ -88,7 +55,7 @@ const allLanguages: Languages = { code: 'ja', hreflang: 'ja', redirectPatterns: [/^\/jp/], - dir: getRoot('ja-jp'), + locale: 'ja-jp', }, pt: { name: 'Portuguese', @@ -96,7 +63,7 @@ const allLanguages: Languages = { code: 'pt', hreflang: 'pt', redirectPatterns: [/^\/br/], - dir: getRoot('pt-br'), + locale: 'pt-br', }, zh: { name: 'Simplified Chinese', @@ -104,21 +71,21 @@ const allLanguages: Languages = { code: 'zh', hreflang: 'zh-Hans', redirectPatterns: [/^\/cn/, /^\/zh-\w{2}/], - dir: getRoot('zh-cn'), + locale: 'zh-cn', }, ru: { name: 'Russian', nativeName: 'Русский', code: 'ru', hreflang: 'ru', - dir: getRoot('ru-ru'), + locale: 'ru-ru', }, fr: { name: 'French', nativeName: 'Français', code: 'fr', hreflang: 'fr', - dir: getRoot('fr-fr'), + locale: 'fr-fr', }, ko: { name: 'Korean', @@ -126,59 +93,16 @@ const allLanguages: Languages = { code: 'ko', hreflang: 'ko', redirectPatterns: [/^\/kr/], - dir: getRoot('ko-kr'), + locale: 'ko-kr', }, de: { name: 'German', nativeName: 'Deutsch', code: 'de', hreflang: 'de', - dir: getRoot('de-de'), + locale: 'de-de', }, } -// Some markdownlint tests depend on having access to all -// language keys. Not modifying the original object makes -// it possible to export all keys, even when those directories -// don't exist on disk. -Object.freeze(allLanguages) -export const allLanguageKeys: string[] = Object.keys(allLanguages) -const languages: Languages = { ...allLanguages } - -if (TRANSLATIONS_FIXTURE_ROOT) { - // Keep all languages that have a directory in the fixture root. - Object.entries(languages).forEach(([code, { dir }]) => { - if (code !== 'en' && !fs.existsSync(dir)) { - delete languages[code] - } - }) -} else if (process.env.ENABLED_LANGUAGES) { - if (process.env.ENABLED_LANGUAGES.toLowerCase() !== 'all') { - Object.keys(languages).forEach((code) => { - if (!process.env.ENABLED_LANGUAGES!.includes(code)) { - delete languages[code] - } - }) - // This makes the translation health report not valid JSON - // console.log(`ENABLED_LANGUAGES: ${process.env.ENABLED_LANGUAGES}`) - } -} else if (process.env.NODE_ENV === 'test') { - // Unless explicitly set, when running tests default to just English - Object.keys(languages).forEach((code) => { - if (code !== 'en') delete languages[code] - }) -} - +Object.freeze(languages) export const languageKeys: string[] = Object.keys(languages) - -export const languagePrefixPathRegex: RegExp = new RegExp(`^/(${languageKeys.join('|')})(/|$)`) - -/** Return true if the URL is something like /en/foo or /ja but return false - * if it's something like /foo or /foo/bar or /fr (because French (fr) - * is currently not an active language) - */ -export function pathLanguagePrefixed(path: string): boolean { - return languagePrefixPathRegex.test(path) -} - -export default languages diff --git a/src/languages/middleware/detect-language.ts b/src/languages/middleware/detect-language.ts index 937096580f2c..c54b3f1469fa 100644 --- a/src/languages/middleware/detect-language.ts +++ b/src/languages/middleware/detect-language.ts @@ -2,7 +2,7 @@ import type { Request, Response, NextFunction } from 'express' import parser from 'accept-language-parser' import type { Language as parserLanguage } from 'accept-language-parser' -import languages, { languageKeys } from '@/languages/lib/languages' +import languages, { languageKeys } from '@/languages/lib/languages-server' import { USER_LANGUAGE_COOKIE_NAME } from '@/frame/lib/constants' import type { ExtendedRequest, Languages } from '@/types' import { updateLoggerContext } from '@/observability/logger/lib/logger-context' diff --git a/src/languages/scripts/count-translation-corruptions.ts b/src/languages/scripts/count-translation-corruptions.ts index 4315db629f81..9c7221c85026 100644 --- a/src/languages/scripts/count-translation-corruptions.ts +++ b/src/languages/scripts/count-translation-corruptions.ts @@ -7,7 +7,7 @@ import { TokenizationError } from 'liquidjs' import walk from 'walk-sync' import { getLiquidTokens } from '@/content-linter/lib/helpers/liquid-utils' -import languages from '@/languages/lib/languages' +import languages from '@/languages/lib/languages-server' import warmServer from '@/frame/lib/warm-server' import type { Site } from '@/types' import { correctTranslatedContentStrings } from '@/languages/lib/correct-translation-content' diff --git a/src/languages/scripts/purge-fastly-edge-cache-per-language.ts b/src/languages/scripts/purge-fastly-edge-cache-per-language.ts index 3bd868240e6b..bb4f2fa1754e 100644 --- a/src/languages/scripts/purge-fastly-edge-cache-per-language.ts +++ b/src/languages/scripts/purge-fastly-edge-cache-per-language.ts @@ -1,4 +1,4 @@ -import { languageKeys } from '@/languages/lib/languages' +import { languageKeys } from '@/languages/lib/languages-server' import { makeLanguageSurrogateKey } from '@/frame/middleware/set-fastly-surrogate-key' import purgeEdgeCache from '@/workflows/purge-edge-cache' diff --git a/src/languages/tests/files.ts b/src/languages/tests/files.ts index b0508c436f4e..0f825cb38674 100644 --- a/src/languages/tests/files.ts +++ b/src/languages/tests/files.ts @@ -1,4 +1,4 @@ -import languages from '@/languages/lib/languages' +import languages from '@/languages/lib/languages-server' import { describe, expect, test, vi } from 'vitest' describe('files', () => { diff --git a/src/languages/tests/frame.ts b/src/languages/tests/frame.ts index 4140d36c1940..55122e69d787 100644 --- a/src/languages/tests/frame.ts +++ b/src/languages/tests/frame.ts @@ -1,6 +1,6 @@ import { describe, expect, test, vi } from 'vitest' -import { languageKeys } from '@/languages/lib/languages' +import { languageKeys } from '@/languages/lib/languages-server' import { blockIndex } from '@/frame/middleware/block-robots' import { get, getDOMCached as getDOM } from '@/tests/helpers/e2etest' import Page from '@/frame/lib/page' diff --git a/src/languages/tests/glossary.ts b/src/languages/tests/glossary.ts index 5b3e58b3ad51..2f7372d267e4 100644 --- a/src/languages/tests/glossary.ts +++ b/src/languages/tests/glossary.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from 'vitest' -import { languageKeys } from '@/languages/lib/languages' +import { languageKeys } from '@/languages/lib/languages-server' import { getDOM } from '@/tests/helpers/e2etest' const langs = languageKeys.filter((lang) => lang !== 'en') diff --git a/src/languages/tests/llms-txt-translations.ts b/src/languages/tests/llms-txt-translations.ts index 1b4011f57694..e386c60ac00a 100644 --- a/src/languages/tests/llms-txt-translations.ts +++ b/src/languages/tests/llms-txt-translations.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from 'vitest' import { get } from '@/tests/helpers/e2etest' -import { languageKeys } from '@/languages/lib/languages' +import { languageKeys } from '@/languages/lib/languages-server' const langs = languageKeys.filter((lang) => lang !== 'en') diff --git a/src/languages/tests/redirects.ts b/src/languages/tests/redirects.ts index f718ee74138d..f3d9aaa24232 100644 --- a/src/languages/tests/redirects.ts +++ b/src/languages/tests/redirects.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from 'vitest' -import { languageKeys } from '@/languages/lib/languages' +import { languageKeys } from '@/languages/lib/languages-server' import { get } from '@/tests/helpers/e2etest' import { USER_LANGUAGE_COOKIE_NAME } from '@/frame/lib/constants' diff --git a/src/languages/tests/search.ts b/src/languages/tests/search.ts index 403cb5df0bc0..ee1477b5f53b 100644 --- a/src/languages/tests/search.ts +++ b/src/languages/tests/search.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from 'vitest' -import { languageKeys } from '@/languages/lib/languages' +import { languageKeys } from '@/languages/lib/languages-server' import { get } from '@/tests/helpers/e2etest' const langs = languageKeys.filter((lang) => lang !== 'en') diff --git a/src/products/lib/get-product-groups.ts b/src/products/lib/get-product-groups.ts index 670d61db0911..6336dfc7cb39 100644 --- a/src/products/lib/get-product-groups.ts +++ b/src/products/lib/get-product-groups.ts @@ -6,7 +6,7 @@ import { productMap, data } from '@/products/lib/all-products' import { renderContentWithFallback } from '@/languages/lib/render-with-fallback' import removeFPTFromPath from '@/versions/lib/remove-fpt-from-path' import frontmatter from '@/frame/lib/read-frontmatter' -import languages from '@/languages/lib/languages' +import languages from '@/languages/lib/languages-server' type PageMap = Record diff --git a/src/redirects/lib/get-redirect.ts b/src/redirects/lib/get-redirect.ts index b73780caab77..8cc5e1d0439d 100644 --- a/src/redirects/lib/get-redirect.ts +++ b/src/redirects/lib/get-redirect.ts @@ -1,4 +1,4 @@ -import { languageKeys } from '@/languages/lib/languages' +import { languageKeys } from '@/languages/lib/languages-server' import nonEnterpriseDefaultVersion from '@/versions/lib/non-enterprise-default-version' import { allVersions } from '@/versions/lib/all-versions' import { diff --git a/src/redirects/middleware/handle-redirects.ts b/src/redirects/middleware/handle-redirects.ts index 907271fe469a..82d88882dece 100644 --- a/src/redirects/middleware/handle-redirects.ts +++ b/src/redirects/middleware/handle-redirects.ts @@ -1,7 +1,7 @@ import type { NextFunction, Response } from 'express' import patterns from '@/frame/lib/patterns' -import { pathLanguagePrefixed } from '@/languages/lib/languages' +import { pathLanguagePrefixed } from '@/languages/lib/languages-server' import { deprecatedWithFunctionalRedirects } from '@/versions/lib/enterprise-server-releases' import getRedirect from '../lib/get-redirect' import { defaultCacheControl, languageCacheControl } from '@/frame/middleware/cache-control' diff --git a/src/redirects/middleware/language-code-redirects.ts b/src/redirects/middleware/language-code-redirects.ts index 1ce32056d31d..fc45fca9eac9 100644 --- a/src/redirects/middleware/language-code-redirects.ts +++ b/src/redirects/middleware/language-code-redirects.ts @@ -1,6 +1,6 @@ import type { NextFunction, Response } from 'express' -import languages from '@/languages/lib/languages' +import languages from '@/languages/lib/languages-server' import { defaultCacheControl } from '@/frame/middleware/cache-control' import { ExtendedRequest } from '@/types' diff --git a/src/rest/lib/index.ts b/src/rest/lib/index.ts index 47d96dfb1c2d..9702abf218db 100644 --- a/src/rest/lib/index.ts +++ b/src/rest/lib/index.ts @@ -4,7 +4,7 @@ import path from 'path' import { readCompressedJsonFileFallback } from '@/frame/lib/read-json-file' import { getAutomatedPageMiniTocItems } from '@/frame/lib/get-mini-toc-items' import { allVersions, getOpenApiVersion } from '@/versions/lib/all-versions' -import languages from '@/languages/lib/languages' +import languages from '@/languages/lib/languages-server' import type { Context } from '@/types' import type { Operation } from '@/rest/components/types' diff --git a/src/search/components/input/AISearchCTAPopup.tsx b/src/search/components/input/AISearchCTAPopup.tsx deleted file mode 100644 index 7d0909d205f2..000000000000 --- a/src/search/components/input/AISearchCTAPopup.tsx +++ /dev/null @@ -1,199 +0,0 @@ -import { useEffect, useRef } from 'react' -import { Text, Button, Heading, Popover, useOnEscapePress, Box } from '@primer/react' -import { focusTrap } from '@primer/behaviors' - -import { useTranslation } from '@/languages/components/useTranslation' -import { useMaxWidthBreakpoint, useMinWidthBreakpoint } from '../hooks/useBreakpoint' -import { useCTAPopoverContext } from '@/frame/components/context/CTAContext' -import { sendEvent } from '@/events/components/events' -import { EventType } from '@/events/types' - -let previouslyFocused: HTMLElement | null = null - -export function AISearchCTAPopup({ - isOpen, - dismiss, - setIsSearchOpen, - isDismissible = true, - bannerType = 'popover', - instanceId = '', -}: { - isOpen: boolean - dismiss?: () => void - setIsSearchOpen: (value: boolean) => void - isDismissible?: boolean - bannerType?: 'popover' | 'footer' - instanceId?: string -}) { - const { t } = useTranslation('search') - const { permanentDismiss } = useCTAPopoverContext() - const isLargeOrUp = useMinWidthBreakpoint('large') - const isTooSmallForCTA = useMaxWidthBreakpoint('293px') - let overlayRef = useRef(null) - let dismissButtonRef = useRef(null) - - // Analytics helper functions - const sendCTAAnalytics = (variation: 'dismiss' | 'ask_copilot') => { - const experimentName = - bannerType === 'footer' ? 'copilot_footer_banner' : 'copilot_popover_banner' - sendEvent({ - type: EventType.experiment, - experiment_name: experimentName, - experiment_variation: variation, - experiment_success: true, - }) - } - - const openSearch = () => { - // Send analytics before taking action - sendCTAAnalytics('ask_copilot') - setIsSearchOpen(true) - // They engaged with the CTA, so let's not show this popup for them anymore - permanentDismiss() - } - - // For a11y, focus trap the CTA and allow it to be closed with Escape - useEffect(() => { - if (isTooSmallForCTA) { - return - } - if (isOpen && overlayRef.current && dismissButtonRef.current) { - focusTrap(overlayRef.current, dismissButtonRef.current) - previouslyFocused = document.activeElement as HTMLElement | null - } - }, [isOpen, isTooSmallForCTA]) - - const onDismiss = () => { - if (isTooSmallForCTA) { - return - } - // Send analytics before taking action - sendCTAAnalytics('dismiss') - if (previouslyFocused) { - previouslyFocused.focus() - } - if (dismiss) { - dismiss() - } - } - - useOnEscapePress(onDismiss) - - if (isTooSmallForCTA) { - return null - } - - const innerContent = ( - <> - The Copilot Icon in front of an explosion of color. - - {t('search.cta.heading')} - - - {t('search.cta.description')} - - - {isDismissible ? ( - - ) : null} - - - - ) - - // If not dismissible, it's not being used as a popover - if (!isDismissible) { - return ( - - {innerContent} - - ) - } - - return ( - - - {innerContent} - - - ) -} diff --git a/src/search/lib/elasticsearch-indexes.ts b/src/search/lib/elasticsearch-indexes.ts index a8a5ae43598b..db6643e7f81d 100644 --- a/src/search/lib/elasticsearch-indexes.ts +++ b/src/search/lib/elasticsearch-indexes.ts @@ -1,4 +1,4 @@ -import languages from '@/languages/lib/languages' +import languages from '@/languages/lib/languages-server' import { utcTimestamp } from '@/search/lib/helpers/time' import { allIndexVersionKeys, versionToIndexVersionMap } from '@/search/lib/elasticsearch-versions' diff --git a/src/search/lib/search-request-params/search-params-objects.ts b/src/search/lib/search-request-params/search-params-objects.ts index 1532e4276fd3..2db04bbb0571 100644 --- a/src/search/lib/search-request-params/search-params-objects.ts +++ b/src/search/lib/search-request-params/search-params-objects.ts @@ -3,7 +3,7 @@ we need to validate and parse the parameters. This file contains the configuration for which parameters to expect based on the type of search request "e.g. general search vs autocomplete search" and how to validate them. */ -import languages from '@/languages/lib/languages' +import languages from '@/languages/lib/languages-server' import { allIndexVersionKeys, versionToIndexVersionMap } from '@/search/lib/elasticsearch-versions' import { SearchTypes } from '@/search/types' diff --git a/src/search/scripts/analyze-text.ts b/src/search/scripts/analyze-text.ts index c8025da60aeb..a2be919aca83 100755 --- a/src/search/scripts/analyze-text.ts +++ b/src/search/scripts/analyze-text.ts @@ -10,7 +10,7 @@ import { Command, Option } from 'commander' import chalk from 'chalk' import dotenv from 'dotenv' -import { languageKeys } from '@/languages/lib/languages' +import { languageKeys } from '@/languages/lib/languages-server' import { allVersions } from '@/versions/lib/all-versions' import type { IndicesAnalyzeAnalyzeToken } from '@elastic/elasticsearch/lib/api/types' diff --git a/src/search/scripts/index/index-cli.ts b/src/search/scripts/index/index-cli.ts index 1c494dd83422..acaa3630aff5 100644 --- a/src/search/scripts/index/index-cli.ts +++ b/src/search/scripts/index/index-cli.ts @@ -2,7 +2,7 @@ import { program, Option, Command, InvalidArgumentError } from 'commander' import { errors } from '@elastic/elasticsearch' import dotenv from 'dotenv' -import { languageKeys } from '@/languages/lib/languages' +import { languageKeys } from '@/languages/lib/languages-server' import { indexGeneralSearch } from './lib/index-general-search' import { diff --git a/src/search/scripts/index/lib/index-general-search.ts b/src/search/scripts/index/lib/index-general-search.ts index 595eedc90176..c588489899c8 100644 --- a/src/search/scripts/index/lib/index-general-search.ts +++ b/src/search/scripts/index/lib/index-general-search.ts @@ -1,6 +1,6 @@ import { Client } from '@elastic/elasticsearch' -import { languageKeys } from '@/languages/lib/languages' +import { languageKeys } from '@/languages/lib/languages-server' import { getElasticSearchIndex } from '@/search/lib/elasticsearch-indexes' import { getElasticsearchClient } from '@/search/lib/helpers/get-client' import { diff --git a/src/search/scripts/scrape/lib/build-records.ts b/src/search/scripts/scrape/lib/build-records.ts index fe082ebd4ce7..b50ca772406e 100644 --- a/src/search/scripts/scrape/lib/build-records.ts +++ b/src/search/scripts/scrape/lib/build-records.ts @@ -3,7 +3,7 @@ import chalk from 'chalk' import dotenv from 'dotenv' import boxen from 'boxen' -import languages from '@/languages/lib/languages' +import languages from '@/languages/lib/languages-server' import parsePageSectionsIntoRecords from '@/search/scripts/scrape/lib/parse-page-sections-into-records' import getPopularPages from '@/search/scripts/scrape/lib/popular-pages' import domwaiter from '@/search/scripts/scrape/lib/domwaiter' diff --git a/src/search/scripts/scrape/lib/scrape-into-index-json.ts b/src/search/scripts/scrape/lib/scrape-into-index-json.ts index ccb1880c3fa6..fe609bdcf36a 100644 --- a/src/search/scripts/scrape/lib/scrape-into-index-json.ts +++ b/src/search/scripts/scrape/lib/scrape-into-index-json.ts @@ -1,6 +1,6 @@ import chalk from 'chalk' -import languages from '@/languages/lib/languages' +import languages from '@/languages/lib/languages-server' import buildRecords from '@/search/scripts/scrape/lib/build-records' import findIndexablePages from '@/search/scripts/scrape/lib/find-indexable-pages' import { writeIndexRecords } from '@/search/scripts/scrape/lib/search-index-records' diff --git a/src/search/scripts/scrape/scrape-cli.ts b/src/search/scripts/scrape/scrape-cli.ts index 362a29574631..717e5da1dd7f 100644 --- a/src/search/scripts/scrape/scrape-cli.ts +++ b/src/search/scripts/scrape/scrape-cli.ts @@ -4,7 +4,7 @@ import { existsSync, statSync, readdirSync } from 'fs' import { program, Option } from 'commander' -import { languageKeys } from '@/languages/lib/languages' +import { languageKeys } from '@/languages/lib/languages-server' import scrapeIntoIndexJson from '@/search/scripts/scrape/lib/scrape-into-index-json' import { allIndexVersionOptions, diff --git a/src/types.ts b/src/types.ts index 7aed23911ff9..1a709d0c8efa 100644 --- a/src/types.ts +++ b/src/types.ts @@ -3,6 +3,7 @@ import type { Failbot } from '@github/failbot' import type enterpriseServerReleases from '@/versions/lib/enterprise-server-releases.d' import type { ValidOcticon } from '@/landings/types' +import type { Language, Languages } from '@/languages/lib/languages-server' import type { MiniTocItem } from '@/frame/lib/get-mini-toc-items' // Shared type for resolved article information used across landing pages and carousels @@ -325,16 +326,8 @@ export type SecretScanningData = { isduplicate: boolean } -type Language = { - name: string - code: string - hreflang: string - dir: string -} - -export type Languages = { - [key: string]: Language -} +// Language and Languages types are imported at the top from languages-server +export type { Language, Languages } export type Permalink = { languageCode: string