diff --git a/assets/images/site/favicon.ico b/assets/images/site/favicon.ico index 03d22fcf5237..40e25b4cdd28 100644 Binary files a/assets/images/site/favicon.ico and b/assets/images/site/favicon.ico differ diff --git a/assets/images/site/favicon.png b/assets/images/site/favicon.png index ea8d2712cd0e..5d74638170ed 100644 Binary files a/assets/images/site/favicon.png and b/assets/images/site/favicon.png differ diff --git a/assets/images/site/favicon.svg b/assets/images/site/favicon.svg index 92460e8a4320..5241f67f951e 100644 --- a/assets/images/site/favicon.svg +++ b/assets/images/site/favicon.svg @@ -1 +1 @@ - + diff --git a/content/admin/managing-iam/provisioning-user-accounts-with-scim/configuring-scim-provisioning-for-users.md b/content/admin/managing-iam/provisioning-user-accounts-with-scim/configuring-scim-provisioning-for-users.md index b2395432fc20..0944b1866d00 100644 --- a/content/admin/managing-iam/provisioning-user-accounts-with-scim/configuring-scim-provisioning-for-users.md +++ b/content/admin/managing-iam/provisioning-user-accounts-with-scim/configuring-scim-provisioning-for-users.md @@ -87,6 +87,11 @@ To ensure you can continue to sign in and configure settings when SCIM is enable {% ifversion scim-for-ghes-ga %}You can use any username for your setup user, but we recommend using `scim-admin`. Although the `scim-admin` user consumes a license when first created, the license is freed once SCIM is enabled. With any other username, the user will continue to consume a license after SCIM is enabled.{% endif %} +1. Copy the password reset link after creating the user, and open it in a private browser window. Set a password for this user. + + > [!IMPORTANT] + > As this user will act as a break-glass account, ensure you store the password securely in a password manager. Otherwise you risk losing access to this account. + 1. Promote the user to an enterprise owner. See [AUTOTITLE](/admin/managing-accounts-and-repositories/managing-users-in-your-enterprise/promoting-or-demoting-a-site-administrator#promoting-a-user-from-the-enterprise-settings). ## 2. Create a {% data variables.product.pat_generic %} diff --git a/content/code-security/trialing-github-advanced-security/planning-a-trial-of-ghas.md b/content/code-security/trialing-github-advanced-security/planning-a-trial-of-ghas.md index 143d26c6c86c..52d58f81b5ed 100644 --- a/content/code-security/trialing-github-advanced-security/planning-a-trial-of-ghas.md +++ b/content/code-security/trialing-github-advanced-security/planning-a-trial-of-ghas.md @@ -2,7 +2,7 @@ title: 'Planning a trial of {% data variables.product.prodname_GHAS %}' shortTitle: 'Plan GHAS trial' allowTitleToDifferFromFilename: true -intro: 'Make the most of your trial so you can decide whether {% data variables.product.prodname_AS %} products meet your business needs.' +intro: 'Learn how to prepare for a successful trial of {% data variables.product.prodname_AS %}.' type: overview topics: - Code Security @@ -13,19 +13,19 @@ versions: ghes: '> 3.15' --- -## About trialing {% data variables.product.prodname_GHAS %} +## Is a self-serve trial right for you? -You can trial {% data variables.product.prodname_GHAS %} independently, or working with an expert from {% data variables.product.github %} or a partner organization. The primary audience for these articles is people who will plan and run their trial independently, typically small and medium-sized organizations. +This article is for organizations that want to begin a trial of {% data variables.product.prodname_GHAS %} independently, without the help of an expert or partner. Typically, that means you're a small or medium-sized organization. -* Existing {% data variables.product.prodname_ghe_cloud %} users can set up a trial if you pay for {% data variables.product.prodname_ghe_cloud %} by credit card or PayPal, or if you are already taking part in a free trial of {% data variables.product.prodname_ghe_cloud %}, see [AUTOTITLE](/billing/managing-billing-for-your-products/managing-billing-for-github-advanced-security/setting-up-a-trial-of-github-advanced-security#setting-up-your-trial-of-github-advanced-security). +This article helps you plan for a **self-serve** trial of {% data variables.product.prodname_GHAS %}. A self-serve trial is right for you if both of the following are true: +* You want to conduct your trial independently, without the help of an expert or partner. Typically, this works best for small or medium-sized organizations. +* You're an existing {% data variables.product.prodname_ghe_cloud %} customer who pays by credit card or PayPal. - {% data reusables.advanced-security.ghas-trial-invoiced %} +Otherwise, contact us for help with your trial. +* If you want expert help: [Contact our team](https://github.com/enterprise/contact). +* If you pay by invoice: Contact your sales representative. -* Users on other {% data variables.product.github %} plans can trial {% data variables.product.prodname_GHAS %} as part of a trial of {% data variables.product.prodname_ghe_cloud %}, see [AUTOTITLE](/enterprise-cloud@latest/admin/overview/setting-up-a-trial-of-github-enterprise-cloud). - -> [!NOTE] Although {% data variables.product.prodname_GHAS %} is free of charge during trials, you will be charged for any actions minutes that you use. That is, actions minutes used by the {% data variables.product.prodname_code_scanning %} default setup or by any other workflows you run. - -## Define your company goals +## 1. Define your company goals Before you start a trial, you should define the purpose of the trial and identify the key questions you need to answer. Maintaining a strong focus on these goals will enable you to plan a trial that maximizes discovery and ensures that you have the information needed to decide whether or not to upgrade. @@ -35,45 +35,50 @@ If your company already uses {% data variables.product.github %}, consider what | Example need | Features to explore during the trial | |--|--| -| Enforce use of security features | Enterprise-level security configurations and policies, see [AUTOTITLE](/admin/managing-code-security/securing-your-enterprise/about-security-configurations) and [AUTOTITLE](/admin/enforcing-policies/enforcing-policies-for-your-enterprise/about-enterprise-policies) | -| Protect custom access tokens | Custom patterns for {% data variables.product.prodname_secret_scanning %}, delegated bypass for push protection, and validity checks, see [AUTOTITLE](/code-security/trialing-github-advanced-security/explore-trial-secret-scanning) | -| Define and enforce a development process | Dependency review, auto-triage rules, rulesets, and policies, see [AUTOTITLE](/code-security/supply-chain-security/understanding-your-software-supply-chain/about-dependency-review), [AUTOTITLE](/code-security/dependabot/dependabot-auto-triage-rules/about-dependabot-auto-triage-rules), [AUTOTITLE](/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/about-rulesets), and [AUTOTITLE](/admin/enforcing-policies/enforcing-policies-for-your-enterprise/about-enterprise-policies) | -| Reduce technical debt at scale | {% data variables.product.prodname_code_scanning_caps %} and security campaigns, see [AUTOTITLE](/code-security/trialing-github-advanced-security/explore-trial-code-scanning) | -| Monitor and track trends in security risks | Security overview, see [AUTOTITLE](/code-security/security-overview/viewing-security-insights) | +| Enforce use of security features | Enterprise-level security configurations and policies. See [AUTOTITLE](/admin/managing-code-security/securing-your-enterprise/about-security-configurations) and [AUTOTITLE](/admin/enforcing-policies/enforcing-policies-for-your-enterprise/about-enterprise-policies) | +| Protect custom access tokens | Custom patterns for {% data variables.product.prodname_secret_scanning %}, delegated bypass for push protection, and validity checks. See [AUTOTITLE](/code-security/trialing-github-advanced-security/explore-trial-secret-scanning) | +| Define and enforce a development process | Dependency review, auto-triage rules, rulesets, and policies. See [AUTOTITLE](/code-security/supply-chain-security/understanding-your-software-supply-chain/about-dependency-review), [AUTOTITLE](/code-security/dependabot/dependabot-auto-triage-rules/about-dependabot-auto-triage-rules), [AUTOTITLE](/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/about-rulesets), and [AUTOTITLE](/admin/enforcing-policies/enforcing-policies-for-your-enterprise/about-enterprise-policies) | +| Reduce technical debt at scale | {% data variables.product.prodname_code_scanning_caps %} and security campaigns. See [AUTOTITLE](/code-security/trialing-github-advanced-security/explore-trial-code-scanning) | +| Monitor and track trends in security risks | Security overview. See [AUTOTITLE](/code-security/security-overview/viewing-security-insights) | {% endrowheaders %} If your company doesn't use {% data variables.product.github %} yet, you are likely to have additional questions including how the platform handles data residency, secure account management, and repository migration. For more information, see [AUTOTITLE](/enterprise-cloud@latest/get-started/onboarding/getting-started-with-github-enterprise-cloud). -## Identify the members of your trial team +## 2. Identify the members of your trial team -{% data variables.product.prodname_GHAS %} enables you to integrate security measures throughout the software development life cycle, so it's important to ensure that you include representatives from all areas of your development cycle. Otherwise you risk making a decision without having all the data you need. A trial includes 50 licenses which provides scope for representation from a wide range of people. +{% data variables.product.prodname_GHAS %} enables you to integrate security measures throughout the software development life cycle, so it's important to ensure that you include representatives from all areas of your development cycle. Otherwise, you risk making a decision without having all the data you need. A trial includes 50 licenses which provides scope for representation from a wide range of people. You may also find it helpful to identify a champion for each company need that you want to investigate. -## Determine whether preliminary research is needed +## 3. Determine whether preliminary research is needed -If members of your trial team have not yet used the core features of {% data variables.product.prodname_GHAS %}, it may be helpful to add an experimentation phase in public repositories before you start a trial. Many of the primary features of {% data variables.product.prodname_code_scanning %} and {% data variables.product.prodname_secret_scanning %} can be used on public repositories. Having a good understanding of the core features will allow you to focus your trial period on private repositories, and exploring the additional features and control available with {% data variables.product.prodname_cs_and_sp %}. +Decide whether your team would benefit from hands-on experience with our free security features **before** you begin your trial. Testing code scanning and secret scanning on public repositories can help new users get familiar with the core features of {% data variables.product.prodname_GHAS %}. This will allow you to focus your trial period on private repositories and the advanced features and controls available in {% data variables.product.prodname_cs_and_sp %}. -For more information, see [AUTOTITLE](/code-security/secret-scanning/introduction/about-secret-scanning), [AUTOTITLE](/code-security/code-scanning/introduction-to-code-scanning/about-code-scanning), and [AUTOTITLE](/code-security/supply-chain-security/understanding-your-software-supply-chain/about-supply-chain-security). +For more information, see: +* [AUTOTITLE](/code-security/secret-scanning/enabling-secret-scanning-features/enabling-secret-scanning-for-your-repository) +* [AUTOTITLE](/code-security/code-scanning/enabling-code-scanning/configuring-default-setup-for-code-scanning) +* [AUTOTITLE](/code-security/supply-chain-security/understanding-your-software-supply-chain/configuring-the-dependency-graph) {% ifversion secret-risk-assessment %} -Organizations on {% data variables.product.prodname_team %} and {% data variables.product.prodname_enterprise %} can run a free report to scan the code in their organization for leaked secrets. This can help you understand the current exposure of the repositories in your organization to leaked secrets, as well as see how many existing secret leaks could have been prevented by {% data variables.product.prodname_secret_protection %}. See [AUTOTITLE](/code-security/securing-your-organization/understanding-your-organizations-exposure-to-leaked-secrets/about-secret-risk-assessment).{% endif %} +Organizations on {% data variables.product.prodname_team %} and {% data variables.product.prodname_enterprise %} can run a free report to scan their code for leaked secrets. This helps you assess your repositories' current exposure to leaked secrets and shows how many existing secret leaks could have been prevented by {% data variables.product.prodname_secret_protection %}. See [AUTOTITLE](/code-security/securing-your-organization/understanding-your-organizations-exposure-to-leaked-secrets/about-secret-risk-assessment).{% endif %} + +## 4. Decide which organizations and repositories to test + +It is generally best to start your trial with an **existing** organization. This ensures that you can experience the features in repositories you know well and within a familiar coding environment. -## Agree the organizations and repositories to test +If you want, you can add test organizations or code later. However, be aware that deliberately insecure applications, such as WebGoat, are not the best test. They may contain coding patterns that appear to be insecure but which {% data variables.product.prodname_code_scanning %} determines cannot be exploited. As a result, {% data variables.product.prodname_code_scanning %} may report fewer issues in these artificial codebases than other security scanners. -Generally it is best to use an existing organization for a trial. This ensures that you can trial the features in repositories you know well and that accurately represent your coding environment. Once you start the trial, you may want to create additional organizations with test code to expand your explorations. +## 5. Define the assessment criteria for the trial -Be aware that deliberately insecure applications, such as WebGoat, may contain coding patterns that appear to be insecure, but which {% data variables.product.prodname_code_scanning %} determines cannot be exploited. {% data variables.product.prodname_code_scanning_caps %} typically generates fewer results for artificially insecure codebases than other static application security scanners. +For each company need or goal you set for the trial, decide how you will measure success. For example, if you want to enforce the use of security features, create test cases for security configurations and policies to confirm they work as expected. -## Define the assessment criteria for the trial +## 6. Start your trial -For each company need or goal that you identify, determine what criteria you will measure to determine whether it is successfully met or not. For example, if one need is to enforce the use of security features, you might define a range of test cases for security configurations and policies to give you confidence that they enforce processes as you expect. +If you already use {% data variables.product.prodname_ghe_cloud %} (as a paying customer or as part of a free trial), see [AUTOTITLE](/code-security/trialing-github-advanced-security/trial-advanced-security). -## Next steps +Otherwise, you can trial {% data variables.product.prodname_GHAS %} as part of a trial of {% data variables.product.prodname_ghe_cloud %}. See [AUTOTITLE](/enterprise-cloud@latest/admin/overview/setting-up-a-trial-of-github-enterprise-cloud){% ifversion fpt %} in the {% data variables.product.prodname_ghe_cloud %} documentation{% endif %}. -1. [AUTOTITLE](/code-security/trialing-github-advanced-security/trial-advanced-security) or [AUTOTITLE](/admin/overview/setting-up-a-trial-of-github-enterprise-cloud) with {% data variables.product.prodname_AS %} -1. [AUTOTITLE](/code-security/trialing-github-advanced-security/enable-security-features-trial) -1. [AUTOTITLE](/code-security/trialing-github-advanced-security/explore-trial-secret-scanning) -1. [AUTOTITLE](/code-security/trialing-github-advanced-security/explore-trial-code-scanning) +> [!NOTE] +> {% data variables.product.prodname_GHAS %} is free of charge during trials, but you will be charged for any Actions minutes used by code scanning or any other workflows. diff --git a/content/codespaces/managing-codespaces-for-your-organization/enabling-or-disabling-github-codespaces-for-your-organization.md b/content/codespaces/managing-codespaces-for-your-organization/enabling-or-disabling-github-codespaces-for-your-organization.md index 60791b59aea4..6b148310e870 100644 --- a/content/codespaces/managing-codespaces-for-your-organization/enabling-or-disabling-github-codespaces-for-your-organization.md +++ b/content/codespaces/managing-codespaces-for-your-organization/enabling-or-disabling-github-codespaces-for-your-organization.md @@ -2,7 +2,7 @@ title: Enabling or disabling GitHub Codespaces for your organization shortTitle: 'Enable or disable Codespaces' intro: 'You can control which users can use {% data variables.product.prodname_github_codespaces %} in your organization''s private {% ifversion ghec %}and internal {% endif %}repositories.' -permissions: 'Organization owners can control which users can use {% data variables.product.prodname_github_codespaces %}.' +permissions: 'Organization owners' redirect_from: - /codespaces/managing-codespaces-for-your-organization/managing-user-permissions-for-your-organization - /codespaces/managing-codespaces-for-your-organization/enabling-codespaces-for-your-organization @@ -15,31 +15,42 @@ topics: - Codespaces - Billing - Administrator -product: 'Organizations on {% data variables.product.prodname_team %} and {% data variables.product.prodname_enterprise %} plans can choose to disable {% data variables.product.prodname_github_codespaces %} in private {% ifversion ghec %}and internal {% endif %}repositories. For more information, see [AUTOTITLE](/get-started/learning-about-github/githubs-products).' +product: '{% data variables.product.prodname_team %} and {% data variables.product.prodname_enterprise %}' --- ## About enabling and disabling {% data variables.product.prodname_github_codespaces %} -{% data variables.product.prodname_github_codespaces %} is always available in an organization's public repositories, and any user can create a codespace from these repositories. If your organization is on a {% data variables.product.prodname_free_team %} plan, {% data variables.product.prodname_github_codespaces %} is always available in your organization's private repositories too, and any users with access to these repositories can create a codespace at their own expense. +{% data variables.product.prodname_github_codespaces %} is always available in an organization's public repositories. Any user can create a codespace from these repositories. -If you're the owner of an organization on a {% data variables.product.prodname_team %} or {% data variables.product.prodname_ghe_cloud %} plan, you can choose whether to enable or disable {% data variables.product.prodname_github_codespaces %} in your organization's private {% ifversion ghec %}and internal {% endif %}repositories. If you enable {% data variables.product.prodname_github_codespaces %} in these repositories, you can choose whether to enable for all users or for a selection of members and collaborators. +If your organization is on a {% data variables.product.prodname_free_team %} plan, {% data variables.product.prodname_github_codespaces %} is always available in your organization's private repositories. Any user with access to these repositories can create a codespace at their own expense. -By enabling {% data variables.product.prodname_github_codespaces %}, you can help your members and collaborators get started with projects quickly, without needing to install lots of tools and dependencies locally to start contributing. However, you might want to roll out {% data variables.product.prodname_github_codespaces %} gradually across your organization by enabling it for groups of users at a time. Alternatively, if you need to comply with security regulations that require increased control over the private code in your organization, you might want to disable {% data variables.product.prodname_github_codespaces %} for all your members. +If you're an organization owner on a paid {% data variables.product.github %} plan, you can enable or disable {% data variables.product.prodname_github_codespaces %} for your organization's private {% ifversion ghec %}and internal {% endif %}repositories. You can enable {% data variables.product.prodname_github_codespaces %} for all users or only for selected members and collaborators. -If you have enabled {% data variables.product.prodname_github_codespaces %} in private {% ifversion ghec %}and internal {% endif %}repositories for at least some users, you can choose to pay for these users' usage of {% data variables.product.prodname_github_codespaces %} across all repositories in your organization. For more information, see [AUTOTITLE](/codespaces/managing-codespaces-for-your-organization/choosing-who-owns-and-pays-for-codespaces-in-your-organization). +## Enabling or disabling {% data variables.product.prodname_github_codespaces %} + +> [!NOTE] +> Removing a user's access to {% data variables.product.prodname_github_codespaces %} will prevent them from opening any of their existing codespaces in your organization's private {% ifversion ghec %}and internal {% endif %}repositories. For more details, see [What happens when I remove a user's access to {% data variables.product.prodname_github_codespaces %}?](#what-happens-when-i-remove-a-users-access-to-github-codespaces) + +{% data reusables.profile.access_org %} +{% data reusables.profile.org_settings %} +{% data reusables.organizations.click-codespaces %} +{% data reusables.organizations.click-general %} +1. On the {% data variables.product.prodname_codespaces %} settings page, under "Codespaces access," select your preferred setting for {% data variables.product.prodname_github_codespaces %} in your organization's private {% ifversion ghec %}and internal {% endif %}repositories. -If you cannot access the settings to enable {% data variables.product.prodname_github_codespaces %} in your organization, this may be because an enterprise owner has disabled {% data variables.product.prodname_github_codespaces %} for your organization. For more information, see {% ifversion ghec %}[AUTOTITLE](/admin/policies/enforcing-policies-for-your-enterprise/enforcing-policies-for-github-codespaces-in-your-enterprise).{% elsif fpt %}[AUTOTITLE](/enterprise-cloud@latest/admin/policies/enforcing-policies-for-your-enterprise/enforcing-policies-for-github-codespaces-in-your-enterprise) in the {% data variables.product.prodname_ghe_cloud %} documentation.{% endif %} +## Ensuring your users can create codespaces -## Prerequisites for enabling {% data variables.product.prodname_github_codespaces %} +To allow a user to create codespaces for a repository owned by your organization, you must do one of the following: -Only people who can either push changes to a repository, or fork the repository, can create a codespace for that repository. To allow a user to create codespaces for a repository owned by your organization, you must do one of the following things. +* Give the user **read access** to the repository and permit forking. This allows the user to create a codespace, push changes to a fork, and open a pull request. For more information, see [AUTOTITLE](/organizations/managing-organization-settings/managing-the-forking-policy-for-your-organization). +* Give the user **write access** to the repository so that they can push changes directly to the repository without forking. -* Ensure that the user has read access to the repository, and the repository permits forking, so that the user can create a codespace from the repository, push their changes to a fork, and create a pull request for any changes they want to make. For more information, see [AUTOTITLE](/organizations/managing-organization-settings/managing-the-forking-policy-for-your-organization). -* Ensure that the user has write access to the repository, so that they can push changes directly to the repository without forking. +{% ifversion ghec %} -In addition, to allow users to create codespaces, you must ensure that{% ifversion ghec %}: +Additionally, ensure that: * Your enterprise does not use OIDC with CAP. For more information, see [AUTOTITLE](/admin/identity-and-access-management/using-enterprise-managed-users-for-iam/about-support-for-your-idps-conditional-access-policy). -* Your{% else %} your{% endif %} organization does not have an IP address allow list enabled. For more information, see [Managing allowed IP addresses for your organization](/{% ifversion fpt %}enterprise-cloud@latest/{% endif %}organizations/keeping-your-organization-secure/managing-allowed-ip-addresses-for-your-organization){% ifversion fpt %} in the {% data variables.product.prodname_ghe_cloud %} documentation.{% else %}.{% endif %} +* Your organization does not have an IP address allow list enabled. For more information, see [Managing allowed IP addresses for your organization](/organizations/keeping-your-organization-secure/managing-allowed-ip-addresses-for-your-organization). + +{% endif %} {% ifversion fpt %} @@ -48,23 +59,17 @@ In addition, to allow users to create codespaces, you must ensure that{% ifversi {% endif %} -## About changing your settings +## What happens when I remove a user's access to {% data variables.product.prodname_github_codespaces %}? -If you remove a user's access to {% data variables.product.prodname_github_codespaces %}, the user will immediately be unable to open existing codespaces they have created from your organization's private {% ifversion ghec %}and internal {% endif %}repositories. If you were previously paying for codespaces the user had created from your organization's public repositories, ownership of these codespaces will transfer the user. +Before removing users' access to {% data variables.product.prodname_github_codespaces %}, you should **alert the affected users.** -Before removing users' access, you should alert the affected users. If they have unpublished work in a codespace, they can make sure the work is pushed to a branch in the repository before they lose access. +When you remove a user's access, the user will immediately be unable to open existing codespaces they have created from your organization's private {% ifversion ghec %}and internal {% endif %}repositories. -Once a user loses access to a codespace, the codespace is retained for a period of 7 days, then it is permanently deleted. During this 7-day period, to recover unpublished work from the codespace, the user must contact {% data variables.contact.contact_support %}. +* If you alert them first, they can push any unpublished work to a branch in the repository before they lose access. +* Once a user loses access to a codespace, the codespace is retained for a period of 7 days, then it is permanently deleted. During this 7-day period, to recover unpublished work from the codespace, the user must contact {% data variables.contact.contact_support %}. -## Enabling or disabling {% data variables.product.prodname_github_codespaces %} +If you were previously paying for codespaces the user had created from your organization's public repositories, ownership of these codespaces will transfer to the user. -> [!NOTE] -> If you remove a user's access to {% data variables.product.prodname_github_codespaces %}, the user will immediately be unable to open existing codespaces they have created from your organization's private {% ifversion ghec %}and internal {% endif %}repositories. For more information, see [About changing your settings](#about-changing-your-settings). - -{% data reusables.profile.access_org %} -{% data reusables.profile.org_settings %} -{% data reusables.organizations.click-codespaces %} -{% data reusables.organizations.click-general %} -1. On the {% data variables.product.prodname_codespaces %} settings page, under "Codespaces access," select your preferred setting for {% data variables.product.prodname_github_codespaces %} in your organization's private {% ifversion ghec %}and internal {% endif %}repositories. +## Further reading - You can disable {% data variables.product.prodname_codespaces %}, enable for specific members or teams, enable for all members, or enable for all members and collaborators. +* [AUTOTITLE](/codespaces/managing-codespaces-for-your-organization/choosing-who-owns-and-pays-for-codespaces-in-your-organization) diff --git a/data/reusables/contributing/content-linter-rules.md b/data/reusables/contributing/content-linter-rules.md index f2631f0f144a..b8a6b9f41a6f 100644 --- a/data/reusables/contributing/content-linter-rules.md +++ b/data/reusables/contributing/content-linter-rules.md @@ -71,6 +71,7 @@ | GHD053 | header-content-requirement | Headers must have content between them, such as an introduction | warning | headers, structure, content | | GHD054 | third-party-actions-reusable | Code examples with third-party actions must include disclaimer reusable | warning | actions, reusable, third-party | | GHD055 | frontmatter-validation | Frontmatter properties must meet character limits and required property requirements | warning | frontmatter, character-limits, required-properties | +| GHD056 | frontmatter-landing-recommended | Only landing pages can have recommended articles, there should be no duplicate recommended articles, and all recommended articles must exist | error | frontmatter, landing, recommended | | [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/gated-features/attestations.md b/data/reusables/gated-features/attestations.md index 693860b344da..c7391cd133de 100644 --- a/data/reusables/gated-features/attestations.md +++ b/data/reusables/gated-features/attestations.md @@ -1 +1 @@ -Artifact attestations are available in public repositories for all current {% data variables.product.prodname_dotcom %} plans. They are not available on legacy plans, such as Bronze, Silver, or Gold. {% ifversion fpt %}If you are on a {% data variables.product.prodname_free_user %}, {% data variables.product.prodname_pro %}, or {% data variables.product.prodname_team %} plan, artifact attestations are only available for public repositories. To use artifact attestations in private or internal repositories, you must be on a {% data variables.product.prodname_ghe_cloud %} plan. {% endif %} +Artifact attestations are available for repositories in all current {% data variables.product.prodname_dotcom %} plans. They are not available on legacy plans, such as Bronze, Silver, or Gold. If you are on a {% data variables.product.prodname_free_user %}, {% data variables.product.prodname_pro %}, or {% data variables.product.prodname_team %} plan, artifact attestations are only available for public repositories. To use artifact attestations in private or internal repositories, you must be on a {% data variables.product.prodname_ghe_cloud %} plan. diff --git a/src/content-linter/lib/linting-rules/frontmatter-landing-recommended.js b/src/content-linter/lib/linting-rules/frontmatter-landing-recommended.js new file mode 100644 index 000000000000..cdbc1667b19a --- /dev/null +++ b/src/content-linter/lib/linting-rules/frontmatter-landing-recommended.js @@ -0,0 +1,88 @@ +import fs from 'fs' +import path from 'path' +import { addError } from 'markdownlint-rule-helpers' + +import { getFrontmatter } from '../helpers/utils' + +function isValidArticlePath(articlePath, currentFilePath) { + // Article paths in recommended are relative to the current page's directory + const relativePath = articlePath.startsWith('/') ? articlePath.substring(1) : articlePath + const currentDir = path.dirname(currentFilePath) + const fullPath = path.join(currentDir, `${relativePath}.md`) + + try { + return fs.existsSync(fullPath) && fs.statSync(fullPath).isFile() + } catch { + return false + } +} + +export const frontmatterLandingRecommended = { + names: ['GHD056', 'frontmatter-landing-recommended'], + description: + 'Only landing pages can have recommended articles, there should be no duplicate recommended articles, and all recommended articles must exist', + tags: ['frontmatter', 'landing', 'recommended'], + function: (params, onError) => { + const fm = getFrontmatter(params.lines) + if (!fm || !fm.recommended) return + + const recommendedLine = params.lines.find((line) => line.startsWith('recommended:')) + + if (!recommendedLine) return + + const lineNumber = params.lines.indexOf(recommendedLine) + 1 + + if (!fm.layout || !fm.layout.includes('landing')) { + addError( + onError, + lineNumber, + 'recommended frontmatter key is only valid for landing pages', + recommendedLine, + [1, recommendedLine.length], + null, + ) + } + + // Check for duplicate recommended items and invalid paths + if (Array.isArray(fm.recommended)) { + const seen = new Set() + const duplicates = [] + const invalidPaths = [] + + fm.recommended.forEach((item) => { + if (seen.has(item)) { + duplicates.push(item) + } else { + seen.add(item) + } + + // Validate that the article path exists + if (!isValidArticlePath(item, params.name)) { + invalidPaths.push(item) + } + }) + + if (duplicates.length > 0) { + addError( + onError, + lineNumber, + `Found duplicate recommended articles: ${duplicates.join(', ')}`, + recommendedLine, + [1, recommendedLine.length], + null, + ) + } + + if (invalidPaths.length > 0) { + addError( + onError, + lineNumber, + `Found invalid recommended article paths: ${invalidPaths.join(', ')}`, + recommendedLine, + [1, recommendedLine.length], + null, + ) + } + } + }, +} diff --git a/src/content-linter/lib/linting-rules/index.js b/src/content-linter/lib/linting-rules/index.js index f7e5a74b3477..f267fbc14c2a 100644 --- a/src/content-linter/lib/linting-rules/index.js +++ b/src/content-linter/lib/linting-rules/index.js @@ -54,6 +54,7 @@ import { frontmatterVersionsWhitespace } from '@/content-linter/lib/linting-rule import { frontmatterValidation } from '@/content-linter/lib/linting-rules/frontmatter-validation' import { headerContentRequirement } from '@/content-linter/lib/linting-rules/header-content-requirement' import { thirdPartyActionsReusable } from '@/content-linter/lib/linting-rules/third-party-actions-reusable' +import { frontmatterLandingRecommended } from '@/content-linter/lib/linting-rules/frontmatter-landing-recommended' const noDefaultAltText = markdownlintGitHub.find((elem) => elem.names.includes('no-default-alt-text'), @@ -115,6 +116,7 @@ export const gitHubDocsMarkdownlint = { headerContentRequirement, // GHD053 thirdPartyActionsReusable, // GHD054 frontmatterValidation, // GHD055 + frontmatterLandingRecommended, // GHD056 // Search-replace rules searchReplace, // Open-source plugin diff --git a/src/content-linter/style/github-docs.js b/src/content-linter/style/github-docs.js index 2600cffd27c5..4532b6c7a596 100644 --- a/src/content-linter/style/github-docs.js +++ b/src/content-linter/style/github-docs.js @@ -310,6 +310,12 @@ export const githubDocsFrontmatterConfig = { 'partial-markdown-files': false, 'yml-files': false, }, + 'frontmatter-landing-recommended': { + // GHD056 + severity: 'error', + 'partial-markdown-files': false, + 'yml-files': false, + }, } // Configures rules from the `github/markdownlint-github` repo diff --git a/src/content-linter/tests/fixtures/landing-recommended/article-one.md b/src/content-linter/tests/fixtures/landing-recommended/article-one.md new file mode 100644 index 000000000000..724fe7a6fb7a --- /dev/null +++ b/src/content-linter/tests/fixtures/landing-recommended/article-one.md @@ -0,0 +1,14 @@ +--- +title: Article One +layout: inline +versions: + fpt: '*' + ghec: '*' + ghes: '*' +topics: + - Testing +--- + +# Article One + +This is a supporting article for testing. diff --git a/src/content-linter/tests/fixtures/landing-recommended/article-two.md b/src/content-linter/tests/fixtures/landing-recommended/article-two.md new file mode 100644 index 000000000000..a65122889643 --- /dev/null +++ b/src/content-linter/tests/fixtures/landing-recommended/article-two.md @@ -0,0 +1,14 @@ +--- +title: Article Two +layout: inline +versions: + fpt: '*' + ghec: '*' + ghes: '*' +topics: + - Testing +--- + +# Article Two + +This is another supporting article for testing. diff --git a/src/content-linter/tests/fixtures/landing-recommended/duplicate-recommended.md b/src/content-linter/tests/fixtures/landing-recommended/duplicate-recommended.md new file mode 100644 index 000000000000..8a50baa93d43 --- /dev/null +++ b/src/content-linter/tests/fixtures/landing-recommended/duplicate-recommended.md @@ -0,0 +1,19 @@ +--- +title: Landing with Duplicates +layout: product-landing +versions: + fpt: '*' + ghec: '*' + ghes: '*' +topics: + - Testing +recommended: + - /article-one + - /article-two + - /article-one + - /subdir/article-three +--- + +# Landing with Duplicates + +This landing page has duplicate recommended articles. diff --git a/src/content-linter/tests/fixtures/landing-recommended/invalid-non-landing.md b/src/content-linter/tests/fixtures/landing-recommended/invalid-non-landing.md new file mode 100644 index 000000000000..81cdf1352056 --- /dev/null +++ b/src/content-linter/tests/fixtures/landing-recommended/invalid-non-landing.md @@ -0,0 +1,18 @@ +--- +title: Not a Landing Page +layout: inline +versions: + fpt: '*' + ghec: '*' + ghes: '*' +topics: + - Testing +recommended: + - /article-one + - /article-two + - /subdir/article-three +--- + +# Not a Landing Page + +This page has a recommended property but is not a landing page. diff --git a/src/content-linter/tests/fixtures/landing-recommended/invalid-paths.md b/src/content-linter/tests/fixtures/landing-recommended/invalid-paths.md new file mode 100644 index 000000000000..fb76b85bcea2 --- /dev/null +++ b/src/content-linter/tests/fixtures/landing-recommended/invalid-paths.md @@ -0,0 +1,18 @@ +--- +title: Landing with Invalid Paths +layout: product-landing +versions: + fpt: '*' + ghec: '*' + ghes: '*' +topics: + - Testing +recommended: + - /article-one + - /nonexistent/path + - /another/invalid/path +--- + +# Landing with Invalid Paths + +This landing page has some invalid recommended article paths. diff --git a/src/content-linter/tests/fixtures/landing-recommended/no-recommended.md b/src/content-linter/tests/fixtures/landing-recommended/no-recommended.md new file mode 100644 index 000000000000..dcc452f24f61 --- /dev/null +++ b/src/content-linter/tests/fixtures/landing-recommended/no-recommended.md @@ -0,0 +1,14 @@ +--- +title: Landing without Recommended +layout: product-landing +versions: + fpt: '*' + ghec: '*' + ghes: '*' +topics: + - Testing +--- + +# Landing without Recommended + +This is a landing page without any recommended articles. diff --git a/src/content-linter/tests/fixtures/landing-recommended/subdir/article-three.md b/src/content-linter/tests/fixtures/landing-recommended/subdir/article-three.md new file mode 100644 index 000000000000..2d45cb24de9d --- /dev/null +++ b/src/content-linter/tests/fixtures/landing-recommended/subdir/article-three.md @@ -0,0 +1,14 @@ +--- +title: Article Three +layout: inline +versions: + fpt: '*' + ghec: '*' + ghes: '*' +topics: + - Testing +--- + +# Article Three + +This is a supporting article in a subdirectory for testing. diff --git a/src/content-linter/tests/fixtures/landing-recommended/valid-landing.md b/src/content-linter/tests/fixtures/landing-recommended/valid-landing.md new file mode 100644 index 000000000000..ce2a471c571e --- /dev/null +++ b/src/content-linter/tests/fixtures/landing-recommended/valid-landing.md @@ -0,0 +1,18 @@ +--- +title: Valid Landing Page +layout: product-landing +versions: + fpt: '*' + ghec: '*' + ghes: '*' +topics: + - Testing +recommended: + - /article-one + - /article-two + - /subdir/article-three +--- + +# Valid Landing Page + +This is a valid landing page with recommended articles. diff --git a/src/content-linter/tests/unit/frontmatter-landing-recommended.js b/src/content-linter/tests/unit/frontmatter-landing-recommended.js new file mode 100644 index 000000000000..12969e8c0fea --- /dev/null +++ b/src/content-linter/tests/unit/frontmatter-landing-recommended.js @@ -0,0 +1,78 @@ +import { describe, expect, test } from 'vitest' + +import { runRule } from '@/content-linter/lib/init-test' +import { frontmatterLandingRecommended } from '@/content-linter/lib/linting-rules/frontmatter-landing-recommended' + +const VALID_LANDING = 'src/content-linter/tests/fixtures/landing-recommended/valid-landing.md' +const INVALID_NON_LANDING = + 'src/content-linter/tests/fixtures/landing-recommended/invalid-non-landing.md' +const DUPLICATE_RECOMMENDED = + 'src/content-linter/tests/fixtures/landing-recommended/duplicate-recommended.md' +const INVALID_PATHS = 'src/content-linter/tests/fixtures/landing-recommended/invalid-paths.md' +const NO_RECOMMENDED = 'src/content-linter/tests/fixtures/landing-recommended/no-recommended.md' + +const ruleName = frontmatterLandingRecommended.names[1] + +// Configure the test fixture to not split frontmatter and content +const fmOptions = { markdownlintOptions: { frontMatter: null } } + +describe(ruleName, () => { + test('landing page with recommended articles passes', async () => { + const result = await runRule(frontmatterLandingRecommended, { + files: [VALID_LANDING], + ...fmOptions, + }) + expect(result[VALID_LANDING]).toEqual([]) + }) + + test('non-landing page with recommended property fails', async () => { + const result = await runRule(frontmatterLandingRecommended, { + files: [INVALID_NON_LANDING], + ...fmOptions, + }) + expect(result[INVALID_NON_LANDING]).toHaveLength(1) + expect(result[INVALID_NON_LANDING][0].errorDetail).toContain( + 'recommended frontmatter key is only valid for landing pages', + ) + }) + + test('pages without recommended property pass', async () => { + const result = await runRule(frontmatterLandingRecommended, { + files: [NO_RECOMMENDED], + ...fmOptions, + }) + expect(result[NO_RECOMMENDED]).toEqual([]) + }) + + test('page with duplicate recommended articles fails', async () => { + const result = await runRule(frontmatterLandingRecommended, { + files: [DUPLICATE_RECOMMENDED], + ...fmOptions, + }) + expect(result[DUPLICATE_RECOMMENDED]).toHaveLength(1) // Only duplicate error since all paths are valid + expect(result[DUPLICATE_RECOMMENDED][0].errorDetail).toContain( + 'Found duplicate recommended articles: /article-one', + ) + }) + + test('page with invalid recommended article paths fails', async () => { + const result = await runRule(frontmatterLandingRecommended, { + files: [INVALID_PATHS], + ...fmOptions, + }) + expect(result[INVALID_PATHS]).toHaveLength(1) + expect(result[INVALID_PATHS][0].errorDetail).toContain( + 'Found invalid recommended article paths:', + ) + expect(result[INVALID_PATHS][0].errorDetail).toContain('/nonexistent/path') + expect(result[INVALID_PATHS][0].errorDetail).toContain('/another/invalid/path') + }) + + test('page with valid recommended articles passes', async () => { + const result = await runRule(frontmatterLandingRecommended, { + files: [VALID_LANDING], + ...fmOptions, + }) + expect(result[VALID_LANDING]).toEqual([]) + }) +}) diff --git a/src/frame/lib/frontmatter.js b/src/frame/lib/frontmatter.js index 63dc729ffb60..ca99e44ffce9 100644 --- a/src/frame/lib/frontmatter.js +++ b/src/frame/lib/frontmatter.js @@ -331,6 +331,8 @@ export const schema = { // Recommended configuration for category landing pages recommended: { type: 'array', + minItems: 3, + maxItems: 9, description: 'Array of articles to feature in the carousel section', }, },