diff --git a/content/actions/concepts/runners/communicating-with-self-hosted-runners.md b/content/actions/concepts/runners/communicating-with-self-hosted-runners.md deleted file mode 100644 index 1ba3c32ea71a..000000000000 --- a/content/actions/concepts/runners/communicating-with-self-hosted-runners.md +++ /dev/null @@ -1,99 +0,0 @@ ---- -title: Communicating with self-hosted runners -shortTitle: Self-hosted runner communication -intro: 'Your self-hosted runners can communicate with {% ifversion fpt or ghec %}{% data variables.product.github %}{% else %}{% data variables.location.product_location_enterprise %} and {% data variables.product.prodname_dotcom_the_website %}{% endif %}' -versions: - fpt: '*' - ghes: '*' - ghec: '*' -type: overview -redirect_from: - - /actions/hosting-your-own-runners/managing-self-hosted-runners/communicating-with-self-hosted-runners ---- - -A self-hosted runner connects to {% ifversion fpt or ghec %}{% data variables.product.github %}{% else %}{% data variables.location.product_location_enterprise %}{% endif %} to receive job assignments and to download new versions of the runner application. The self-hosted runner uses an {% ifversion ghes %}HTTP(S){% else %}HTTPS{% endif %} long poll that opens a connection to {% data variables.product.github %} for 50 seconds, and if no response is received, it then times out and creates a new long poll. The application must be running on the machine to accept and run {% data variables.product.prodname_actions %} jobs. - -{% data reusables.actions.runner-app-open-source %} {% ifversion fpt or ghec %} When a new version is released, the runner application automatically updates itself when a job is assigned to the runner, or within a week of release if the runner hasn't been assigned any jobs. {% else ifversion ghes %} When a new version is released, the runner application will automatically update within 24 hours. {% endif %} -{% ifversion ghes %} - -> [!NOTE] -> {% data reusables.actions.upgrade-runners-before-upgrade-ghes %} - -{% endif %} - -{% data reusables.actions.self-hosted-runner-auto-removal %} - -{% data reusables.actions.self-hosted-runner-ports-protocols %} - -{% ifversion fpt or ghec %} -Since the self-hosted runner opens a connection to {% data variables.product.github %}, you do not need to allow {% data variables.product.prodname_dotcom %} to make inbound connections to your self-hosted runner. -{% elsif ghes %} -Only an outbound connection from the runner to {% data variables.product.prodname_ghe_server %} is required. There is no need for an inbound connection from {% data variables.product.prodname_ghe_server %} to the runner. -For caching to work, the runner must be able to communicate with the blob storage and directly download content from it. -{%- endif %} - -{% ifversion ghes %} - -{% data variables.product.prodname_ghe_server %} must accept inbound connections from your runners over HTTP(S) at {% data variables.location.product_location %}'s hostname and API subdomain, and your runners must allow outbound connections over HTTP(S) to {% data variables.location.product_location %}'s hostname and API subdomain. - -{% endif %} - -{% ifversion fpt or ghec %} - -You must ensure that the machine has the appropriate network access with at least 70 kilobits per second upload and download speed to communicate with the {% data variables.product.prodname_dotcom %} hosts listed below. Some hosts are required for essential runner operations, while other hosts are only required for certain functionality. - -You can use the REST API to get meta information about {% data variables.product.company_short %}, including the IP addresses and domain details for {% data variables.product.company_short %} services. The `actions_inbound` section of the API supports both fully qualified and wildcard domains. Fully qualified domains specify a complete domain name (e.g., `example.github.com`), while wildcard domains use a `*` to represent multiple possible subdomains (e.g., `*.github.com`). An example of the self-hosted runner requirements using wildcard domains has been listed below. For more information, see [AUTOTITLE](/rest/meta/meta). - -```shell copy -github.com -*.github.com -*.githubusercontent.com -ghcr.io -``` - -{% data reusables.actions.domain-name-cname-recursive-firewall-rules %} - -{% data reusables.actions.runner-essential-communications %} - -In addition, your workflow may require access to other network resources. - -If you use an IP address allow list for your {% data variables.product.prodname_dotcom %} organization or enterprise account, you must add your self-hosted runner's IP address to the allow list. 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#using-github-actions-with-an-ip-allow-list) or [Enforcing policies for security settings in your enterprise](/{% ifversion fpt %}enterprise-cloud@latest/{% endif %}admin/policies/enforcing-policies-for-your-enterprise/enforcing-policies-for-security-settings-in-your-enterprise){% ifversion fpt %} in the {% data variables.product.prodname_ghe_cloud %} documentation.{% else %}.{% endif %} - -{% else %} - -Self-hosted runners do not require any external internet access in order to function. As a result, you can use network routing to direct communication between the self-hosted runner and {% data variables.product.prodname_ghe_server %}. For example, you can assign a private IP address to your self-hosted runner and configure routing to send traffic to {% data variables.product.prodname_ghe_server %}, with no need for traffic to traverse a public network. - -{% endif %} - -{% ifversion ghes %} - -## Communication between self-hosted runners and {% data variables.product.prodname_dotcom_the_website %} - -Self-hosted runners do not need to connect to {% data variables.product.prodname_dotcom_the_website %} unless you have enabled automatic access to {% data variables.product.prodname_dotcom_the_website %} actions for {% data variables.product.prodname_ghe_server %}. For more information, see [AUTOTITLE](/admin/github-actions/managing-access-to-actions-from-githubcom/about-using-actions-in-your-enterprise). - -If you have enabled automatic access to {% data variables.product.prodname_dotcom_the_website %} actions, then the self-hosted runner will connect directly to {% data variables.product.prodname_dotcom_the_website %} to download actions. You must ensure that the machine has the appropriate network access to communicate with the {% data variables.product.prodname_dotcom %} URLs listed below. - -```shell copy -github.com -api.github.com -codeload.github.com -pkg.actions.githubusercontent.com -``` - -You can use the REST API to get meta information about {% data variables.product.company_short %}, including the IP addresses and domain details for {% data variables.product.company_short %} services. The `actions_inbound` section of the API supports both fully qualified and wildcard domains. Fully qualified domains specify a complete domain name (e.g., `example.github.com`), while wildcard domains use a `*` to represent multiple possible subdomains (e.g., `*.github.com`). An example of the self-hosted runner requirements using wildcard domains has been listed below. For more information, see [AUTOTITLE](/rest/meta/meta). - -```shell copy -github.com -*.github.com -*.githubusercontent.com -ghcr.io -``` - -{% data reusables.actions.domain-name-cname-recursive-firewall-rules %} - -{% endif %} - -## Further reading - -* [AUTOTITLE](/actions/hosting-your-own-runners/managing-self-hosted-runners/using-a-proxy-server-with-self-hosted-runners) -* [AUTOTITLE](/actions/hosting-your-own-runners/managing-self-hosted-runners/monitoring-and-troubleshooting-self-hosted-runners#troubleshooting-network-connectivity) diff --git a/content/actions/concepts/runners/index.md b/content/actions/concepts/runners/index.md index 31e9c4dc8b2e..cf8c6b699f42 100644 --- a/content/actions/concepts/runners/index.md +++ b/content/actions/concepts/runners/index.md @@ -11,7 +11,6 @@ children: - /about-larger-runners - /about-private-networking-with-github-hosted-runners - /about-self-hosted-runners - - /communicating-with-self-hosted-runners - /about-runner-scale-sets - /about-actions-runner-controller - /about-support-for-actions-runner-controller diff --git a/content/actions/how-tos/hosting-your-own-runners/managing-self-hosted-runners/adding-self-hosted-runners.md b/content/actions/how-tos/hosting-your-own-runners/managing-self-hosted-runners/adding-self-hosted-runners.md index 01999592c141..0d4db5adeb4c 100644 --- a/content/actions/how-tos/hosting-your-own-runners/managing-self-hosted-runners/adding-self-hosted-runners.md +++ b/content/actions/how-tos/hosting-your-own-runners/managing-self-hosted-runners/adding-self-hosted-runners.md @@ -10,27 +10,19 @@ versions: fpt: '*' ghes: '*' ghec: '*' -type: tutorial shortTitle: Add self-hosted runners --- -{% data reusables.actions.enterprise-github-hosted-runners %} - -You can add a self-hosted runner to a repository, an organization, or an enterprise. - -If you are an organization or enterprise administrator, you might want to add your self-hosted runners at the organization or enterprise level. This approach makes the runner available to multiple repositories in your organization or enterprise, and also lets you to manage your runners in one place. - > [!WARNING] > {% data reusables.actions.self-hosted-runner-security %} > > For more information, see [AUTOTITLE](/actions/security-for-github-actions/security-guides/security-hardening-for-github-actions). -You can set up automation to scale the number of self-hosted runners. For more information, see [AUTOTITLE](/actions/hosting-your-own-runners/managing-self-hosted-runners/autoscaling-with-self-hosted-runners). - -You can register ephemeral runners that perform a single job before the registration is cleaned up by using just-in-time runner registration. For more information, see [AUTOTITLE](/actions/security-guides/security-hardening-for-github-actions#using-just-in-time-runners). - ## Prerequisites +Before you add a self-hosted runner, you should understand what they are and how they work. See [AUTOTITLE](/actions/concepts/runners/about-self-hosted-runners). + +Additionally, you must meet the following requirements: {% data reusables.actions.self-hosted-runners-prerequisites %} ## Adding a self-hosted runner to a repository @@ -96,11 +88,6 @@ To make an enterprise-level self-hosted runner group available to an organizatio For more information on changing runner group access settings, see [AUTOTITLE](/actions/hosting-your-own-runners/managing-self-hosted-runners/managing-access-to-self-hosted-runners-using-groups#changing-the-access-policy-of-a-self-hosted-runner-group). {% endif %} -{% ifversion ghec or ghes %} - -## Further reading +## Next steps -* [AUTOTITLE](/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners) -* [AUTOTITLE](/admin/github-actions/getting-started-with-github-actions-for-your-enterprise/getting-started-with-self-hosted-runners-for-your-enterprise) - -{% endif %} +You can set up automation to scale the number of self-hosted runners. For more information, see [AUTOTITLE](/actions/reference/self-hosted-runners-reference#autoscaling). diff --git a/content/actions/how-tos/hosting-your-own-runners/managing-self-hosted-runners/autoscaling-with-self-hosted-runners.md b/content/actions/how-tos/hosting-your-own-runners/managing-self-hosted-runners/autoscaling-with-self-hosted-runners.md deleted file mode 100644 index 7f4bb254185a..000000000000 --- a/content/actions/how-tos/hosting-your-own-runners/managing-self-hosted-runners/autoscaling-with-self-hosted-runners.md +++ /dev/null @@ -1,96 +0,0 @@ ---- -title: Autoscaling with self-hosted runners -shortTitle: Autoscale self-hosted runners -intro: You can automatically scale your self-hosted runners in response to webhook events. -redirect_from: - - /actions/hosting-your-own-runners/autoscaling-with-self-hosted-runners - - /actions/hosting-your-own-runners/managing-self-hosted-runners/autoscaling-with-self-hosted-runners -versions: - fpt: '*' - ghec: '*' - ghes: '*' -type: overview ---- - -{% data reusables.actions.enterprise-github-hosted-runners %} - -## About autoscaling - -You can automatically increase or decrease the number of self-hosted runners in your environment in response to the webhook events you receive with a particular label. For example, you can create automation that adds a new self-hosted runner each time you receive a [`workflow_job`](/webhooks-and-events/webhooks/webhook-events-and-payloads#workflow_job) webhook event with the [`queued`](/webhooks-and-events/webhooks/webhook-events-and-payloads#workflow_job) activity, which notifies you that a new job is ready for processing. The webhook payload includes label data, so you can identify the type of runner the job is requesting. Once the job has finished, you can then create automation that removes the runner in response to the `workflow_job` [`completed`](/webhooks-and-events/webhooks/webhook-events-and-payloads#workflow_job) activity. - -## Supported autoscaling solutions - -{% ifversion fpt or ghec %} - -{% data variables.product.prodname_dotcom %}-hosted runners inherently autoscale based on your needs. {% data variables.product.prodname_dotcom %}-hosted runners can be a low-maintenance and cost-effective alternative to developing or implementing autoscaling solutions. For more information, see [AUTOTITLE](/actions/using-github-hosted-runners/about-github-hosted-runners/about-github-hosted-runners). - -{% endif %} - -The [actions/actions-runner-controller](https://github.com/actions/actions-runner-controller) (ARC) project is a Kubernetes-based runner autoscaler. {% data variables.product.prodname_dotcom %} recommends ARC if the team deploying it has expert Kubernetes knowledge and experience. - -For more information, see [AUTOTITLE](/actions/hosting-your-own-runners/managing-self-hosted-runners-with-actions-runner-controller/about-actions-runner-controller) and [AUTOTITLE](/actions/hosting-your-own-runners/managing-self-hosted-runners-with-actions-runner-controller/about-support-for-actions-runner-controller). - -## Using ephemeral runners for autoscaling - -{% data variables.product.prodname_dotcom %} recommends implementing autoscaling with ephemeral self-hosted runners; autoscaling with persistent self-hosted runners is not recommended. In certain cases, {% data variables.product.prodname_dotcom %} cannot guarantee that jobs are not assigned to persistent runners while they are shut down. With ephemeral runners, this can be guaranteed because {% data variables.product.prodname_dotcom %} only assigns one job to a runner. - -This approach allows you to manage your runners as ephemeral systems, since you can use automation to provide a clean environment for each job. This helps limit the exposure of any sensitive resources from previous jobs, and also helps mitigate the risk of a compromised runner receiving new jobs. - ->[!WARNING]The runner application log files for ephemeral runners must be forwarded to an external log storage solution for troubleshooting and diagnostic purposes. While it is not required for ephemeral runners to be deployed, {% data variables.product.prodname_dotcom %} recommends ensuring runner logs are forwarded and preserved externally before deploying an ephemeral runner autoscaling solution in a production environment. For more information, see [AUTOTITLE](/actions/hosting-your-own-runners/managing-self-hosted-runners/monitoring-and-troubleshooting-self-hosted-runners#reviewing-the-self-hosted-runner-application-log-files). - -To add an ephemeral runner to your environment, include the `--ephemeral` parameter when registering your runner using `config.sh`. For example: - -```shell -./config.sh --url https://github.com/octo-org --token example-token --ephemeral -``` - -The {% data variables.product.prodname_actions %} service will then automatically de-register the runner after it has processed one job. You can then create your own automation that wipes the runner after it has been de-registered. - -> [!NOTE] -> If a job is labeled for a certain type of runner, but none matching that type are available, the job does not immediately fail at the time of queueing. Instead, the job will remain queued until the 24 hour timeout period expires. - -Alternatively, you can create ephemeral, just-in-time runners using the REST API. For more information, see [AUTOTITLE](/rest/actions/self-hosted-runners). - -## Controlling runner software updates on self-hosted runners - -By default, self-hosted runners will automatically perform a software update whenever a new version of the runner software is available. If you use ephemeral runners in containers then this can lead to repeated software updates when a new runner version is released. Turning off automatic updates allows you to update the runner version on the container image directly on your own schedule. - -To turn off automatic software updates and install software updates yourself, specify the `--disableupdate` flag when registering your runner using `config.sh`. For example: - -```shell -./config.sh --url https://github.com/YOUR-ORGANIZATION --token EXAMPLE-TOKEN --disableupdate -``` - -If you disable automatic updates, you must still update your runner version regularly. New functionality in {% data variables.product.prodname_actions %} requires changes in both the {% data variables.product.prodname_actions %} service _and_ the runner software. The runner may not be able to correctly process jobs that take advantage of new features in {% data variables.product.prodname_actions %} without a software update. - -If you disable automatic updates, you will be required to update your runner version within 30 days of a new version being made available. You may want to subscribe to notifications for releases in the [`actions/runner` repository](https://github.com/actions/runner/releases). For more information, see [AUTOTITLE](/account-and-profile/managing-subscriptions-and-notifications-on-github/setting-up-notifications/configuring-notifications#about-custom-notifications). - -For instructions on how to install the latest runner version, see the installation instructions for [the latest release](https://github.com/actions/runner/releases). - ->[!WARNING] Any updates released for the software, including major, minor or patch releases, are considered as an available update. If you do not perform a software update within 30 days, the {% data variables.product.prodname_actions %} service will not queue jobs to your runner. In addition, if a critical security update is required, the {% data variables.product.prodname_actions %} service will not queue jobs to your runner until it has been updated. - -## Using webhooks for autoscaling - -You can create your own autoscaling environment by using payloads received from the [`workflow_job`](/webhooks-and-events/webhooks/webhook-events-and-payloads#workflow_job) webhook. This webhook is available at the repository, organization, and enterprise levels, and the payload for this event contains an `action` key that corresponds to the stages of a workflow job's life-cycle; for example when jobs are `queued`, `in_progress`, and `completed`. You must then create your own scaling automation in response to these webhook payloads. - -* For more information about the `workflow_job` webhook, see [AUTOTITLE](/webhooks-and-events/webhooks/webhook-events-and-payloads#workflow_job). -* To learn how to work with webhooks, see [AUTOTITLE](/webhooks). - -## Authentication requirements - -You can register and delete repository and organization self-hosted runners using [the API](/rest/actions/self-hosted-runners). To authenticate to the API, your autoscaling implementation can use an access token or a {% data variables.product.prodname_dotcom %} app. - -Your access token will require the following scope: - -* For private repositories, use an access token with the [`repo` scope](/apps/oauth-apps/building-oauth-apps/scopes-for-oauth-apps#available-scopes). -* For public repositories, use an access token with the [`public_repo` scope](/apps/oauth-apps/building-oauth-apps/scopes-for-oauth-apps#available-scopes). -* For organizations, use an access token with the [`admin:org` scope](/apps/oauth-apps/building-oauth-apps/scopes-for-oauth-apps#available-scopes). - -To authenticate using a {% data variables.product.prodname_dotcom %} App, it must be assigned the following permissions: - -* For repositories, assign the `administration` permission. -* For organizations, assign the `organization_self_hosted_runners` permission. - -You can register and delete enterprise self-hosted runners using [the API](/rest/actions/self-hosted-runners). To authenticate to the API, your autoscaling implementation can use an access token. - -Your access token will require the `manage_runners:enterprise` scope. diff --git a/content/actions/how-tos/hosting-your-own-runners/managing-self-hosted-runners/index.md b/content/actions/how-tos/hosting-your-own-runners/managing-self-hosted-runners/index.md index c5658ec1c394..db655779df68 100644 --- a/content/actions/how-tos/hosting-your-own-runners/managing-self-hosted-runners/index.md +++ b/content/actions/how-tos/hosting-your-own-runners/managing-self-hosted-runners/index.md @@ -8,7 +8,6 @@ versions: ghec: '*' children: - /adding-self-hosted-runners - - /autoscaling-with-self-hosted-runners - /running-scripts-before-or-after-a-job - /customizing-the-containers-used-by-jobs - /configuring-the-self-hosted-runner-application-as-a-service @@ -21,5 +20,5 @@ children: redirect_from: - /actions/hosting-your-own-runners/managing-self-hosted-runners --- - + {% data reusables.actions.enterprise-github-hosted-runners %} diff --git a/content/actions/how-tos/writing-workflows/choosing-what-your-workflow-does/passing-information-between-jobs.md b/content/actions/how-tos/writing-workflows/choosing-what-your-workflow-does/passing-information-between-jobs.md index ad15061bcf13..af5ae1439da9 100644 --- a/content/actions/how-tos/writing-workflows/choosing-what-your-workflow-does/passing-information-between-jobs.md +++ b/content/actions/how-tos/writing-workflows/choosing-what-your-workflow-does/passing-information-between-jobs.md @@ -1,6 +1,6 @@ --- title: Passing information between jobs -shortTitle: Pass information +shortTitle: Pass job outputs intro: You can define outputs to pass information from one job to another. versions: fpt: '*' @@ -12,8 +12,52 @@ redirect_from: - /actions/writing-workflows/choosing-what-your-workflow-does/passing-information-between-jobs --- -{% data reusables.actions.enterprise-github-hosted-runners %} +## Defining and using job outputs -## Overview +1. Open the workflow file containing the job you want to get outputs from. +1. Use the `jobs..outputs` syntax to define the outputs for the job. For example, the following job defines the `output1` and `output2` outputs, which are mapped to the results of `step1` and `step2` respectively: -{% data reusables.actions.jobs.section-defining-outputs-for-jobs %} + ```yaml + jobs: + job1: + runs-on: ubuntu-latest + outputs: + output1: ${{ steps.step1.outputs.test }} + output2: ${{ steps.step2.outputs.test }} + steps: + - id: step1 + run: echo "test=hello" >> "$GITHUB_OUTPUT" + - id: step2 + run: echo "test=world" >> "$GITHUB_OUTPUT" + ``` + +1. In a separate job where you want to access those outputs, use the `jobs..needs` syntax to make it dependent on the original job. For example, the following job checks that `job1` is complete before running: + + ```yaml + jobs: + # Assume job1 is defined as above + job2: + runs-on: ubuntu-latest + needs: job1 + ``` + +1. To access the outputs in the dependent job, use the `needs..outputs.` syntax. For example, the following job accesses the `output1` and `output2` outputs defined in `job1`: + + ```yaml + jobs: + # Assume job1 is defined as above + job2: + runs-on: ubuntu-latest + needs: job1 + steps: + - env: + OUTPUT1: ${{needs.job1.outputs.output1}} + OUTPUT2: ${{needs.job1.outputs.output2}} + run: echo "$OUTPUT1 $OUTPUT2" + ``` + +## Next steps + +To learn more about job outputs and the `needs` context, see the following sections of [AUTOTITLE](/actions/reference/workflow-syntax-for-github-actions#jobsjob_idoutputs): +* [`jobs..outputs`](/actions/reference/workflow-syntax-for-github-actions#jobsjob_idoutputs) +* [`jobs..needs`](/actions/reference/workflow-syntax-for-github-actions#jobsjob_idneeds) diff --git a/content/actions/how-tos/writing-workflows/choosing-what-your-workflow-does/using-jobs-in-a-workflow.md b/content/actions/how-tos/writing-workflows/choosing-what-your-workflow-does/using-jobs-in-a-workflow.md index 35573c70256e..4bedeba4c8a1 100644 --- a/content/actions/how-tos/writing-workflows/choosing-what-your-workflow-does/using-jobs-in-a-workflow.md +++ b/content/actions/how-tos/writing-workflows/choosing-what-your-workflow-does/using-jobs-in-a-workflow.md @@ -1,6 +1,6 @@ --- title: Using jobs in a workflow -shortTitle: Use jobs in a workflow +shortTitle: Use jobs intro: Use workflows to run multiple jobs. versions: fpt: '*' @@ -11,11 +11,9 @@ redirect_from: - /actions/writing-workflows/choosing-what-your-workflow-does/using-jobs-in-a-workflow --- -{% data reusables.actions.enterprise-github-hosted-runners %} +## Prerequisites -## Overview - -{% data reusables.actions.jobs.section-using-jobs-in-a-workflow %} +To implement jobs in your workflows, you need to understand what jobs are. See [AUTOTITLE](/actions/get-started/understanding-github-actions#jobs). ## Setting an ID for a job diff --git a/content/actions/reference/self-hosted-runners-reference.md b/content/actions/reference/self-hosted-runners-reference.md index ad0c571ed8ac..bcd18225f120 100644 --- a/content/actions/reference/self-hosted-runners-reference.md +++ b/content/actions/reference/self-hosted-runners-reference.md @@ -1,10 +1,15 @@ --- title: Self-hosted runners reference shortTitle: Self-hosted runners reference -intro: Find information about requirements and supported actions for self-hosted runners. +intro: Find information about setting up and using self-hosted runners. redirect_from: - /actions/hosting-your-own-runners/managing-self-hosted-runners/supported-architectures-and-operating-systems-for-self-hosted-runners - /actions/reference/supported-architectures-and-operating-systems-for-self-hosted-runners + - /actions/hosting-your-own-runners/autoscaling-with-self-hosted-runners + - /actions/hosting-your-own-runners/managing-self-hosted-runners/autoscaling-with-self-hosted-runners + - /actions/how-tos/hosting-your-own-runners/managing-self-hosted-runners/autoscaling-with-self-hosted-runners + - /actions/hosting-your-own-runners/managing-self-hosted-runners/communicating-with-self-hosted-runners + - /actions/concepts/runners/communicating-with-self-hosted-runners versions: fpt: '*' ghes: '*' @@ -52,3 +57,144 @@ You can use a machine as a self-hosted runner as long as it meets these requirem * `x64` - Linux, macOS, Windows. * `ARM64` - Linux, macOS{% ifversion actions-windows-arm %}, Windows (currently in {% data variables.release-phases.public_preview %}){% endif %}. * `ARM32` - Linux. + +## Autoscaling + +You can automatically increase or decrease the number of self-hosted runners in your environment in response to the webhook events you receive with a particular label. + +### Supported autoscaling solutions + +{% ifversion fpt or ghec %} + +{% data variables.product.prodname_dotcom %}-hosted runners inherently autoscale based on your needs. {% data variables.product.prodname_dotcom %}-hosted runners can be a low-maintenance and cost-effective alternative to developing or implementing autoscaling solutions. For more information, see [AUTOTITLE](/actions/using-github-hosted-runners/about-github-hosted-runners/about-github-hosted-runners). + +{% endif %} + +The [actions/actions-runner-controller](https://github.com/actions/actions-runner-controller) (ARC) project is a Kubernetes-based runner autoscaler. {% data variables.product.prodname_dotcom %} recommends ARC if the team deploying it has expert Kubernetes knowledge and experience. + +For more information, see [AUTOTITLE](/actions/hosting-your-own-runners/managing-self-hosted-runners-with-actions-runner-controller/about-actions-runner-controller) and [AUTOTITLE](/actions/hosting-your-own-runners/managing-self-hosted-runners-with-actions-runner-controller/about-support-for-actions-runner-controller). + +### Ephemeral runners for autoscaling + +{% data variables.product.prodname_dotcom %} recommends implementing autoscaling with ephemeral self-hosted runners; autoscaling with persistent self-hosted runners is not recommended. In certain cases, {% data variables.product.prodname_dotcom %} cannot guarantee that jobs are not assigned to persistent runners while they are shut down. With ephemeral runners, this can be guaranteed because {% data variables.product.prodname_dotcom %} only assigns one job to a runner. + +This approach allows you to manage your runners as ephemeral systems, since you can use automation to provide a clean environment for each job. This helps limit the exposure of any sensitive resources from previous jobs, and also helps mitigate the risk of a compromised runner receiving new jobs. + +>[!WARNING]The runner application log files for ephemeral runners must be forwarded to an external log storage solution for troubleshooting and diagnostic purposes. While it is not required for ephemeral runners to be deployed, {% data variables.product.prodname_dotcom %} recommends ensuring runner logs are forwarded and preserved externally before deploying an ephemeral runner autoscaling solution in a production environment. For more information, see [AUTOTITLE](/actions/hosting-your-own-runners/managing-self-hosted-runners/monitoring-and-troubleshooting-self-hosted-runners#reviewing-the-self-hosted-runner-application-log-files). + +To add an ephemeral runner to your environment, include the `--ephemeral` parameter when registering your runner using `config.sh`. For example: + +```shell +./config.sh --url https://github.com/octo-org --token example-token --ephemeral +``` + +The {% data variables.product.prodname_actions %} service will then automatically de-register the runner after it has processed one job. You can then create your own automation that wipes the runner after it has been de-registered. + +> [!NOTE] +> If a job is labeled for a certain type of runner, but none matching that type are available, the job does not immediately fail at the time of queueing. Instead, the job will remain queued until the 24 hour timeout period expires. + +Alternatively, you can create ephemeral, just-in-time runners using the REST API. For more information, see [AUTOTITLE](/rest/actions/self-hosted-runners). + +### Runner software updates on self-hosted runners + +By default, self-hosted runners will automatically perform a software update whenever a new version of the runner software is available. If you use ephemeral runners in containers then this can lead to repeated software updates when a new runner version is released. Turning off automatic updates allows you to update the runner version on the container image directly on your own schedule. + +To turn off automatic software updates and install software updates yourself, specify the `--disableupdate` flag when registering your runner using `config.sh`. For example: + +```shell +./config.sh --url https://github.com/YOUR-ORGANIZATION --token EXAMPLE-TOKEN --disableupdate +``` + +If you disable automatic updates, you must still update your runner version regularly. New functionality in {% data variables.product.prodname_actions %} requires changes in both the {% data variables.product.prodname_actions %} service _and_ the runner software. The runner may not be able to correctly process jobs that take advantage of new features in {% data variables.product.prodname_actions %} without a software update. + +If you disable automatic updates, you will be required to update your runner version within 30 days of a new version being made available. You may want to subscribe to notifications for releases in the [`actions/runner` repository](https://github.com/actions/runner/releases). For more information, see [AUTOTITLE](/account-and-profile/managing-subscriptions-and-notifications-on-github/setting-up-notifications/configuring-notifications#about-custom-notifications). + +For instructions on how to install the latest runner version, see the installation instructions for [the latest release](https://github.com/actions/runner/releases). + +>[!WARNING] Any updates released for the software, including major, minor or patch releases, are considered as an available update. If you do not perform a software update within 30 days, the {% data variables.product.prodname_actions %} service will not queue jobs to your runner. In addition, if a critical security update is required, the {% data variables.product.prodname_actions %} service will not queue jobs to your runner until it has been updated. + +### Webhooks for autoscaling + +You can create your own autoscaling environment by using payloads received from the [`workflow_job`](/webhooks-and-events/webhooks/webhook-events-and-payloads#workflow_job) webhook. This webhook is available at the repository, organization, and enterprise levels, and the payload for this event contains an `action` key that corresponds to the stages of a workflow job's life-cycle; for example when jobs are `queued`, `in_progress`, and `completed`. You must then create your own scaling automation in response to these webhook payloads. + +* For more information about the `workflow_job` webhook, see [AUTOTITLE](/webhooks-and-events/webhooks/webhook-events-and-payloads#workflow_job). +* To learn how to work with webhooks, see [AUTOTITLE](/webhooks). + +### Authentication requirements + +You can register and delete repository and organization self-hosted runners using [the API](/rest/actions/self-hosted-runners). To authenticate to the API, your autoscaling implementation can use an access token or a {% data variables.product.prodname_dotcom %} app. + +Your access token will require the following scope: + +* For private repositories, use an access token with the [`repo` scope](/apps/oauth-apps/building-oauth-apps/scopes-for-oauth-apps#available-scopes). +* For public repositories, use an access token with the [`public_repo` scope](/apps/oauth-apps/building-oauth-apps/scopes-for-oauth-apps#available-scopes). +* For organizations, use an access token with the [`admin:org` scope](/apps/oauth-apps/building-oauth-apps/scopes-for-oauth-apps#available-scopes). + +To authenticate using a {% data variables.product.prodname_dotcom %} App, it must be assigned the following permissions: + +* For repositories, assign the `administration` permission. +* For organizations, assign the `organization_self_hosted_runners` permission. + +You can register and delete enterprise self-hosted runners using [the API](/rest/actions/self-hosted-runners). To authenticate to the API, your autoscaling implementation can use an access token. + +Your access token will require the `manage_runners:enterprise` scope. + +## Communication + +Self-hosted runners connect to {% ifversion fpt or ghec %}{% data variables.product.github %}{% else %}{% data variables.location.product_location_enterprise %}{% endif %} to receive job assignments and download new versions of the runner application. + +{% data reusables.actions.runner-app-open-source %} {% ifversion fpt or ghec %} When a new version is released, the runner application automatically updates itself when a job is assigned to the runner, or within a week of release if the runner hasn't been assigned any jobs. {% else ifversion ghes %} When a new version is released, the runner application will automatically update within 24 hours.{% endif %} + +### Requirements for communication{% ifversion fpt or ghec %} with {% data variables.product.github %}{% else %} with {% data variables.location.product_location_enterprise %}{% endif %} + +* The self-hosted runner application must be running on the host machine to accept and run {% data variables.product.prodname_actions %} jobs. +{%- ifversion fpt or ghec %} +* The host machine must have appropriate network access with at least 70 kilobits per second upload and download speed. +* The host machine must be able to make outbound HTTPS connections over port 443. +* Depending on the function of the workflows assigned to your self-hosted runner, the host machine must be able to communicate with the {% data variables.product.github %} domains listed below. +{% else %} +* {% data variables.product.prodname_ghe_server %} must accept inbound connections from your runners over HTTP(S) at {% data variables.location.product_location_enterprise %}'s hostname and API subdomain, and your runners must allow outbound connections over HTTP(S) to {% data variables.location.product_location_enterprise %}'s hostname and API subdomain. +* For caching to work, the runner must be able to communicate with, and directly download content from, blob storage. +{% endif %} + +{% ifversion fpt or ghec %} + +### Accessible domains by function + +{% data reusables.actions.domain-name-cname-recursive-firewall-rules %} + +{% data reusables.actions.runner-essential-communications %} + +In addition, your workflow may require access to other network resources. + +If you use an IP address allow list for your {% data variables.product.prodname_dotcom %} organization or enterprise account, you must add your self-hosted runner's IP address to the allow list. 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#using-github-actions-with-an-ip-allow-list) or [Enforcing policies for security settings in your enterprise](/{% ifversion fpt %}enterprise-cloud@latest/{% endif %}admin/policies/enforcing-policies-for-your-enterprise/enforcing-policies-for-security-settings-in-your-enterprise){% ifversion fpt %} in the {% data variables.product.prodname_ghe_cloud %} documentation.{% else %}.{% endif %} + +{% else %} + +### Communication with {% data variables.product.prodname_dotcom_the_website %} + +Self-hosted runners do not need to connect to {% data variables.product.prodname_dotcom_the_website %} unless you have enabled automatic access to {% data variables.product.prodname_dotcom_the_website %} actions for {% data variables.product.prodname_ghe_server %}. For more information, see [AUTOTITLE](/admin/github-actions/managing-access-to-actions-from-githubcom/about-using-actions-in-your-enterprise). + +If you want your runner to connect to {% data variables.product.prodname_dotcom_the_website %}, the host machine must be able to make outbound HTTP connections over port 80, or HTTPS connections over port 443. To ensure connectivity over HTTPS, configure TLS for {% data variables.product.prodname_ghe_server %}. See [AUTOTITLE](/enterprise-server@latest/admin/configuration/hardening-security-for-your-enterprise/configuring-tls). + +If you have enabled automatic access to {% data variables.product.prodname_dotcom_the_website %} actions, then the self-hosted runner will connect directly to {% data variables.product.prodname_dotcom_the_website %} to download actions. You must ensure that the machine has the appropriate network access to communicate with the {% data variables.product.prodname_dotcom %} URLs listed below. + +```shell copy +github.com +api.github.com +codeload.github.com +pkg.actions.githubusercontent.com +``` + +You can use the REST API to get meta information about {% data variables.product.company_short %}, including the IP addresses and domain details for {% data variables.product.company_short %} services. The `actions_inbound` section of the API supports both fully qualified and wildcard domains. Fully qualified domains specify a complete domain name (e.g., `example.github.com`), while wildcard domains use a `*` to represent multiple possible subdomains (e.g., `*.github.com`). An example of the self-hosted runner requirements using wildcard domains has been listed below. For more information, see [AUTOTITLE](/rest/meta/meta). + +```shell copy +github.com +*.github.com +*.githubusercontent.com +ghcr.io +``` + +{% data reusables.actions.domain-name-cname-recursive-firewall-rules %} + +{% endif %} diff --git a/content/copilot/concepts/completions/code-suggestions.md b/content/copilot/concepts/completions/code-suggestions.md index 2a73396cacda..de98bd245f44 100644 --- a/content/copilot/concepts/completions/code-suggestions.md +++ b/content/copilot/concepts/completions/code-suggestions.md @@ -87,6 +87,80 @@ topics: {% data variables.product.prodname_copilot %} checks each suggestion for matches with publicly available code. Any matches are discarded or suggested with a code reference, based on the setting of the "Suggestions matching public code" policy for your account or organization. See [AUTOTITLE](/copilot/concepts/completions/code-referencing). +## About the AI model used for {% data variables.product.prodname_copilot_short %} code completion + +{% data variables.product.prodname_copilot_short %} code completion uses the {% data variables.copilot.copilot_gpt_4o %} {% data variables.product.prodname_copilot_short %} model. This is a fine-tuned version of the GPT-4o mini model. The {% data variables.copilot.copilot_gpt_4o %} {% data variables.product.prodname_copilot_short %} model was trained on a wide range of high quality public {% data variables.product.github %} repositories, providing coverage of over 30 programming languages. See [Programming languages included in the default model](#programming-languages-included-in-the-default-model) below. + +{% vscode %} + +## Changing the model used for code completion + +{% data reusables.copilot.code-completion-switch-prereqs-vscode %} + +Changing the model only affects {% data variables.product.prodname_copilot_short %} code completion. It does not affect {% data variables.product.prodname_copilot_short %} next edit suggestions. + +{% data reusables.copilot.code-completion-switch-model-affects %} + +{% endvscode %} + +{% visualstudio %} + +## Changing the model used for code completion + +{% data reusables.copilot.code-completion-switch-prereqs-vs %} + +{% data reusables.copilot.code-completion-switch-model-affects %} + +{% endvisualstudio %} + +{% jetbrains %} + +## Changing the model used for code completion + +{% data reusables.copilot.code-completion-switch-prereqs-jetbrains %} + +{% data reusables.copilot.code-completion-switch-model-affects %} + +{% endjetbrains %} + +## Programming languages included in the default model + +The following programming languages and technologies are included in the training data for the default LLM used for {% data variables.product.prodname_copilot_short %} code completion: + + * C + * C# + * C++ + * Clojure + * CSS + * Dart + * Dockerfile + * Elixir + * Emacs Lisp + * Go + * Haskell + * HTML + * Java + * JavaScript + * Julia + * Jupyter Notebook + * Kotlin + * Lua + * MATLAB + * Objective-C + * Perl + * PHP + * PowerShell + * Python + * R + * Ruby + * Rust + * Scala + * Shell + * Swift + * TeX + * TypeScript + * Vue + ## Next steps * [AUTOTITLE](/copilot/how-tos/completions/getting-code-suggestions-in-your-ide-with-github-copilot) diff --git a/content/copilot/concepts/copilot-billing/understanding-and-managing-requests-in-copilot.md b/content/copilot/concepts/copilot-billing/understanding-and-managing-requests-in-copilot.md index 08073d6e8e1f..3f6a0d663d52 100644 --- a/content/copilot/concepts/copilot-billing/understanding-and-managing-requests-in-copilot.md +++ b/content/copilot/concepts/copilot-billing/understanding-and-managing-requests-in-copilot.md @@ -44,7 +44,7 @@ The following {% data variables.product.prodname_copilot_short %} features can u If you use **{% data variables.copilot.copilot_free_short %}**, your plan comes with up to 2,000 code completion requests and up to 50 premium requests per month. All chat interactions count as premium requests. -If you're on a **paid plan**, you get unlimited code completions and unlimited chat interactions using the included models ({% data variables.copilot.copilot_gpt_41 %} and {% data variables.copilot.copilot_gpt_4o %}). Rate limiting is in place to accommodate for high demand. See [AUTOTITLE](/copilot/troubleshooting-github-copilot/rate-limits-for-github-copilot). +If you're on a **paid plan**, you get unlimited code completions and unlimited chat interactions using the included models ({% data variables.copilot.copilot_gpt_41 %} and {% data variables.copilot.copilot_gpt_4o %}). Rate limiting is in place to accommodate for high demand. See [AUTOTITLE](/copilot/concepts/rate-limits). Paid plans also receive a monthly allowance of premium requests, which can be used for advanced chat interactions, code completions using premium models, and other premium features. For an overview of the amount of premium requests included in each plan, see [AUTOTITLE](/copilot/about-github-copilot/subscription-plans-for-github-copilot#comparing-copilot-plans). @@ -62,7 +62,7 @@ Unused requests for the previous month do not carry over to the following month. > * Users on {% data variables.copilot.copilot_free_short %}. To access more premium requests, upgrade to a paid plan. > * Users who subscribe, or have subscribed, to {% data variables.copilot.copilot_pro_short %} or {% data variables.copilot.copilot_pro_plus_short %} through {% data variables.product.prodname_mobile %} on iOS or Android. -If you're on a **paid plan** and use all of your premium requests, you can still use {% data variables.product.prodname_copilot_short %} with one of the included models for the rest of the month. This is subject to change. Response times for the included models may vary during periods of high usage. Requests to the included models may be subject to rate limiting. See [AUTOTITLE](/copilot/troubleshooting-github-copilot/rate-limits-for-github-copilot). +If you're on a **paid plan** and use all of your premium requests, you can still use {% data variables.product.prodname_copilot_short %} with one of the included models for the rest of the month. This is subject to change. Response times for the included models may vary during periods of high usage. Requests to the included models may be subject to rate limiting. See [AUTOTITLE](/copilot/concepts/rate-limits). If you need more premium requests beyond your monthly allowance, you can: diff --git a/content/copilot/concepts/index.md b/content/copilot/concepts/index.md index e2fcfd9a5c1d..100efef820a6 100644 --- a/content/copilot/concepts/index.md +++ b/content/copilot/concepts/index.md @@ -19,6 +19,7 @@ children: - /policies - /copilot-knowledge-bases - /build-copilot-extensions + - /rate-limits - /network-settings-for-github-copilot --- diff --git a/content/copilot/how-tos/troubleshoot/rate-limits-for-github-copilot.md b/content/copilot/concepts/rate-limits.md similarity index 96% rename from content/copilot/how-tos/troubleshoot/rate-limits-for-github-copilot.md rename to content/copilot/concepts/rate-limits.md index 96dc5088939f..50afea650a03 100644 --- a/content/copilot/how-tos/troubleshoot/rate-limits-for-github-copilot.md +++ b/content/copilot/concepts/rate-limits.md @@ -1,6 +1,7 @@ --- title: Rate limits for GitHub Copilot shortTitle: Rate limits +allowTitleToDifferFromFilename: true intro: 'Learn about {% data variables.product.prodname_copilot %} rate limits and what to do if you are rate limited.' versions: feature: copilot @@ -8,6 +9,7 @@ topics: - Copilot redirect_from: - /copilot/troubleshooting-github-copilot/rate-limits-for-github-copilot + - /copilot/how-tos/troubleshoot/rate-limits-for-github-copilot --- Rate limiting is a mechanism used to control the number of requests a user or application can make in a given time period. {% data variables.product.github %} uses rate limits to ensure everyone has fair access to {% data variables.product.prodname_copilot %} and to protect against abuse. diff --git a/content/copilot/how-tos/ai-models/changing-the-ai-model-for-copilot-code-completion.md b/content/copilot/how-tos/ai-models/changing-the-ai-model-for-copilot-code-completion.md index 6b9a6e02ea69..7d4c043f1866 100644 --- a/content/copilot/how-tos/ai-models/changing-the-ai-model-for-copilot-code-completion.md +++ b/content/copilot/how-tos/ai-models/changing-the-ai-model-for-copilot-code-completion.md @@ -10,90 +10,24 @@ redirect_from: - /copilot/using-github-copilot/ai-models/changing-the-ai-model-for-copilot-code-completion --- -## Overview - -By default, {% data variables.product.prodname_copilot_short %} code completion uses the {% data variables.copilot.copilot_gpt_4o %} {% data variables.product.prodname_copilot_short %} model. This is a fine-tuned version of the GPT-4o mini model. The {% data variables.copilot.copilot_gpt_4o %} {% data variables.product.prodname_copilot_short %} model was trained on a wide range of high quality public {% data variables.product.github %} repositories, providing coverage of over 30 programming languages. - -
- View the list of programming languages and technologies included in the training data. - - * C - * C# - * C++ - * Clojure - * CSS - * Dart - * Dockerfile - * Elixir - * Emacs Lisp - * Go - * Haskell - * HTML - * Java - * JavaScript - * Julia - * Jupyter Notebook - * Kotlin - * Lua - * MATLAB - * Objective-C - * Perl - * PHP - * PowerShell - * Python - * R - * Ruby - * Rust - * Scala - * Shell - * Swift - * TeX - * TypeScript - * Vue - -
- -{% vscode %} You can switch AI models in the latest releases of {% data variables.product.prodname_vscode_shortname %} with the latest version of the {% data variables.product.prodname_copilot %} extension. {% endvscode %} - -{% visualstudio %} You can switch AI models in {% data variables.product.prodname_vs %} 17.14 Preview 2 and later. {% endvisualstudio %} - -{% jetbrains %} You can switch AI models in the latest releases of JetBrains IDEs with the latest version of the {% data variables.product.prodname_copilot %} extension. {% endjetbrains %} - -> [!NOTE] -> The list of available models will change over time. When only one code completion model is available, the model picker will only show that model. Preview models and additional code completion models will be added to the picker as they become available. - -## Effects of switching the AI model - -Changing the model that's used for {% data variables.product.prodname_copilot_short %} code completion does not affect the model that's used by {% data variables.copilot.copilot_chat_short %}. See [AUTOTITLE](/copilot/using-github-copilot/ai-models/changing-the-ai-model-for-copilot-chat). - -There are no changes to the data collection and usage policy if you change the AI model. - -If you are on a {% data variables.copilot.copilot_free_short %} plan, all completions count against your completions quota regardless of the model used. See [AUTOTITLE](/copilot/about-github-copilot/subscription-plans-for-github-copilot#comparing-copilot-subscriptions). - -The setting to enable or disable suggestions that match public code are applied irrespective of which model you choose. See [AUTOTITLE](/enterprise-cloud@latest/copilot/using-github-copilot/finding-public-code-that-matches-github-copilot-suggestions). - -## Enabling the model switcher +{% vscode %} -{% ifversion fpt %} +The following instructions are for {% data variables.product.prodname_vscode_shortname %}. If you are using {% data variables.product.prodname_vs %}, or a JetBrains IDE, click the appropriate tab at the start of this article. -If you have a {% data variables.copilot.copilot_free_short %} or {% data variables.copilot.copilot_pro_short %} plan, the model switcher for {% data variables.product.prodname_copilot_short %} code completion is automatically enabled. +## Prerequisites -{% endif %} +{% data reusables.copilot.code-completion-switch-prereqs-vscode %} -{% data reusables.copilot.editor-preview-settings %} +{% data reusables.copilot.code-completion-available-models %} -{% vscode %} +For more information, see [AUTOTITLE](/copilot/concepts/completions/code-suggestions#changing-the-model-used-for-code-completion). ## Changing the AI model for code completion -The following instructions are for {% data variables.product.prodname_vscode_shortname %}. If you are using {% data variables.product.prodname_vs %}, or a JetBrains IDE, click the appropriate tab at the start of this article. - 1. Open the command palette by pressing Ctrl+Shift+P (Windows/Linux) / Command+Shift+P (Mac). 1. Type `change completions model` and select the "{% data variables.product.prodname_copilot %}: Change Completions Model" command. 1. In the dropdown menu, select the model you want to use. -Alternatively, if Command Center is enabled, you can click {% octicon "chevron-down" aria-label="The downward-pointing arrowhead" %} beside the **{% octicon "copilot" aria-hidden="true" aria-label="copilot" %}** icon at the top of the {% data variables.product.prodname_vscode_shortname %} window, then click **Configure Code Completions** in the dropdown menu. Then choose **Change Completions Model** in the dropdown menu and select the model you want to use. - ## Checking which model is being used 1. Open the Settings editor by pressing Ctrl+, (Linux/Windows) / Command+, (Mac). @@ -105,10 +39,18 @@ Alternatively, if Command Center is enabled, you can click {% octicon "chevron-d {% visualstudio %} -## Changing the AI model for code completion - The following instructions are for {% data variables.product.prodname_vs %}. If you are using {% data variables.product.prodname_vscode_shortname %}, or a JetBrains IDE, click the appropriate tab at the start of this article. +## Prerequisites + +{% data reusables.copilot.code-completion-switch-prereqs-vs %} + +{% data reusables.copilot.code-completion-available-models %} + +For more information, see [AUTOTITLE](/copilot/concepts/completions/code-suggestions#changing-the-model-used-for-code-completion). + +## Changing the AI model for code completion + 1. Click the **{% octicon "copilot" aria-hidden="true" aria-label="copilot" %}** icon in the top right corner. 1. Click **Settings**, then click **Options**. 1. Under **{% data variables.product.prodname_copilot_short %} Completions**, use the dropdown menu to select the model you want to use. @@ -117,10 +59,18 @@ The following instructions are for {% data variables.product.prodname_vs %}. If {% jetbrains %} -## Changing the AI model for code completion - The following instructions are for JetBrains IDEs. If you are using {% data variables.product.prodname_vs %}, or {% data variables.product.prodname_vscode_shortname %}, click the appropriate tab at the start of this article. +## Prerequisites + +{% data reusables.copilot.code-completion-switch-prereqs-jetbrains %} + +{% data reusables.copilot.code-completion-available-models %} + +For more information, see [AUTOTITLE](/copilot/concepts/completions/code-suggestions#changing-the-model-used-for-code-completion). + +## Changing the AI model for code completion + 1. Click the **{% octicon "copilot" aria-hidden="true" aria-label="copilot" %}** icon in the status bar. 1. In the popup menu, click **Edit Model for Completion**. 1. In the settings dialog box for "Languages & Frameworks > {% data variables.product.prodname_copilot %}," click the dropdown menu for **Model for completions** and select the model you want to use. diff --git a/content/copilot/how-tos/troubleshoot/index.md b/content/copilot/how-tos/troubleshoot/index.md index b1885a72d6f8..560babf8e65e 100644 --- a/content/copilot/how-tos/troubleshoot/index.md +++ b/content/copilot/how-tos/troubleshoot/index.md @@ -7,12 +7,9 @@ versions: ghec: '*' children: - /troubleshooting-common-issues-with-github-copilot - - /rate-limits-for-github-copilot - /viewing-logs-for-github-copilot-in-your-environment - /troubleshooting-firewall-settings-for-github-copilot - /troubleshooting-network-errors-for-github-copilot - - /troubleshooting-issues-with-github-copilot-chat redirect_from: - /copilot/troubleshooting-github-copilot --- - diff --git a/content/copilot/how-tos/troubleshoot/troubleshooting-common-issues-with-github-copilot.md b/content/copilot/how-tos/troubleshoot/troubleshooting-common-issues-with-github-copilot.md index f74fe234638c..df9d00a25f7c 100644 --- a/content/copilot/how-tos/troubleshoot/troubleshooting-common-issues-with-github-copilot.md +++ b/content/copilot/how-tos/troubleshoot/troubleshooting-common-issues-with-github-copilot.md @@ -8,6 +8,10 @@ topics: shortTitle: Common issues with GitHub Copilot redirect_from: - /copilot/troubleshooting-github-copilot/troubleshooting-common-issues-with-github-copilot + - /copilot/troubleshooting-github-copilot/troubleshooting-issues-with-github-copilot-chat-in-ides + - /copilot/troubleshooting-github-copilot/troubleshooting-authentication-issues-with-github-copilot-chat + - /copilot/troubleshooting-github-copilot/troubleshooting-issues-with-github-copilot-chat + - /copilot/how-tos/troubleshoot/troubleshooting-issues-with-github-copilot-chat --- @@ -72,6 +76,48 @@ Service-level request rate limits ensure high service quality for all {% data va In case you experience repeated rate-limiting in {% data variables.product.prodname_copilot_short %}, contact {% data variables.contact.contact_support_page %}. +## Can't find {% data variables.copilot.copilot_chat_short %} in my IDE + +If you can't find {% data variables.copilot.copilot_chat_short %} in your editor, make sure you have checked the "Prerequisites" section of [AUTOTITLE](/copilot/github-copilot-chat/copilot-chat-in-ides/using-github-copilot-chat-in-your-ide). + +> [!NOTE] +> The linked article has tabs for various IDEs. + +## Latest {% data variables.copilot.copilot_chat_short %} does not work in {% data variables.product.prodname_vscode %} + +{% data reusables.copilot.vscode-version-compatibility %} + +To use {% data variables.copilot.copilot_chat_short %}, make sure you are using the [latest version of {% data variables.product.prodname_vscode %}](https://code.visualstudio.com/updates). + +## Authentication problems with {% data variables.enterprise.prodname_managed_user %} accounts + +{% data reusables.copilot.sign-in-ghecom %} See [AUTOTITLE](/copilot/managing-copilot/configure-personal-settings/using-github-copilot-with-an-account-on-ghecom). + +## Authentication problems in {% data variables.product.prodname_vscode %} + +If you are signed in to {% data variables.product.github %} but {% data variables.product.prodname_copilot_short %} is unavailable in {% data variables.product.prodname_vscode %}, it may be due to an authentication problem. Try the following steps to resolve the issue: + +1. In the bottom left corner of the {% data variables.product.prodname_vscode %} window, click the **Accounts** icon, hover over your {% data variables.product.prodname_dotcom %} username, and click the **Sign out** button. +1. To reload {% data variables.product.prodname_vscode %}, press F1 to open the command palette, and select **Developer: Reload Window**. +1. After {% data variables.product.prodname_vscode %} reloads, sign back in to your {% data variables.product.prodname_dotcom %} account. + +## Authentication problems in {% data variables.product.prodname_vs %} + +If you experience authentication issues when you try to use {% data variables.copilot.copilot_chat_short %} in {% data variables.product.prodname_vs %}, you can try the following steps to resolve the issue. + +1. Check that the {% data variables.product.prodname_dotcom %} ID you are signed into {% data variables.product.prodname_vs %} with is the same as the one you have been granted access to {% data variables.copilot.copilot_chat_short %} with. +1. Check whether your {% data variables.product.prodname_dotcom %} ID/credentials need refreshing in {% data variables.product.prodname_vs %}. For more information, see [Work with {% data variables.product.prodname_dotcom %} accounts in {% data variables.product.prodname_vs %}](https://learn.microsoft.com/en-us/visualstudio/ide/work-with-github-accounts?view=vs-2022) in the {% data variables.product.prodname_vs %} documentation. +1. Try removing and re-adding your {% data variables.product.prodname_dotcom %} ID to {% data variables.product.prodname_vs %} and restarting {% data variables.product.prodname_vs %}. +1. If the above steps don't work, click the **Share feedback** button and select **Report a problem** to report the issue to the {% data variables.product.prodname_vs %} team. + + ![Screenshot of the share feedback button in {% data variables.product.prodname_vs %}.](/assets/images/help/copilot/vs-share-feedback-button.png) + +## Interrupted chat responses on {% data variables.product.prodname_dotcom_the_website %} + +If a chat response terminates unexpectedly, before the response is complete, try resubmitting the question. + +In {% data variables.copilot.copilot_chat_short %}'s immersive view (the [github.com/copilot](https://github.com/copilot) page), you can resubmit your question by clicking the {% octicon "sync" aria-label="Retry" %} button under the chat response. + ## Further reading * [AUTOTITLE](/free-pro-team@latest/site-policy/other-site-policies/github-and-trade-controls) diff --git a/content/copilot/how-tos/troubleshoot/troubleshooting-issues-with-github-copilot-chat.md b/content/copilot/how-tos/troubleshoot/troubleshooting-issues-with-github-copilot-chat.md deleted file mode 100644 index 496d01d5f7a5..000000000000 --- a/content/copilot/how-tos/troubleshoot/troubleshooting-issues-with-github-copilot-chat.md +++ /dev/null @@ -1,75 +0,0 @@ ---- -title: Troubleshooting issues with GitHub Copilot Chat -intro: 'This guide describes common issues with {% data variables.copilot.copilot_chat_short %} and how to resolve them.' -product: '{% data reusables.gated-features.copilot-chat-callout %}' -defaultTool: vscode -topics: - - Copilot - - Troubleshooting -versions: - feature: copilot -shortTitle: Copilot Chat -redirect_from: - - /copilot/troubleshooting-github-copilot/troubleshooting-issues-with-github-copilot-chat-in-ides - - /copilot/troubleshooting-github-copilot/troubleshooting-authentication-issues-with-github-copilot-chat - - /copilot/troubleshooting-github-copilot/troubleshooting-issues-with-github-copilot-chat ---- - -You can use {% data variables.copilot.copilot_chat %} in your IDE or on the {% data variables.product.github %} website. Click the tabs above for troubleshooting information for {% data variables.product.prodname_copilot_short %} in {% data variables.product.prodname_vs %}, {% data variables.product.prodname_vscode %}, and on {% data variables.product.github %} in the browser. - -If you need help with {% data variables.copilot.copilot_chat_short %} and can't find the answer here, you can report a bug or ask for help. For more information, see [Sharing feedback about {% data variables.copilot.copilot_chat %}](/copilot/github-copilot-chat/copilot-chat-in-ides/using-github-copilot-chat-in-your-ide#sharing-feedback-about-github-copilot-chat). - -{% vscode %} - -If you can't find {% data variables.copilot.copilot_chat_short %} in your editor, make sure you have checked the [Prerequisites](/copilot/github-copilot-chat/copilot-chat-in-ides/using-github-copilot-chat-in-your-ide#prerequisites) section. - -## Troubleshooting issues caused by version incompatibility - -{% data reusables.copilot.vscode-version-compatibility %} - -To use {% data variables.copilot.copilot_chat_short %}, make sure you are using the [latest version of {% data variables.product.prodname_vscode %}](https://code.visualstudio.com/updates). - -## Troubleshooting authentication issues in your editor - -{% data reusables.copilot.sign-in-ghecom %} See [AUTOTITLE](/copilot/managing-copilot/configure-personal-settings/using-github-copilot-with-an-account-on-ghecom). - -### Troubleshooting authentication issues in {% data variables.product.prodname_vscode %} - -If you are signed in to {% data variables.product.github %} but {% data variables.product.prodname_copilot_short %} is unavailable in {% data variables.product.prodname_vscode %}, it may be due to an authentication problem. Try the following steps to resolve the issue: - -1. In the bottom left corner of the {% data variables.product.prodname_vscode %} window, click the **Accounts** icon, hover over your {% data variables.product.prodname_dotcom %} username, and click the **Sign out** button. -1. To reload {% data variables.product.prodname_vscode %}, press F1 to open the command palette, and select **Developer: Reload Window**. -1. After {% data variables.product.prodname_vscode %} reloads, sign back in to your {% data variables.product.prodname_dotcom %} account. - -{% endvscode %} - -{% visualstudio %} - -If you can't find {% data variables.copilot.copilot_chat_short %} in your editor, make sure you have checked the [Prerequisites](/copilot/github-copilot-chat/copilot-chat-in-ides/using-github-copilot-chat-in-your-ide#prerequisites) section. - -## Troubleshooting authentication issues in your editor - -{% data reusables.copilot.sign-in-ghecom %} See [AUTOTITLE](/copilot/managing-copilot/configure-personal-settings/using-github-copilot-with-an-account-on-ghecom). - -### Troubleshooting authentication issues in {% data variables.product.prodname_vs %} - -If you experience authentication issues when you try to use {% data variables.copilot.copilot_chat %} in {% data variables.product.prodname_vs %}, you can try the following steps to resolve the issue. - -1. Check that the {% data variables.product.prodname_dotcom %} ID you are signed into {% data variables.product.prodname_vs %} with is the same as the one you have been granted access to {% data variables.copilot.copilot_chat %} with. -1. Check whether your {% data variables.product.prodname_dotcom %} ID/credentials need refreshing in {% data variables.product.prodname_vs %}. For more information, see [Work with {% data variables.product.prodname_dotcom %} accounts in {% data variables.product.prodname_vs %}](https://learn.microsoft.com/en-us/visualstudio/ide/work-with-github-accounts?view=vs-2022) in the {% data variables.product.prodname_vs %} documentation. -1. Try removing and re-adding your {% data variables.product.prodname_dotcom %} ID to {% data variables.product.prodname_vs %} and restarting {% data variables.product.prodname_vs %}. -1. If the above steps don't work, click the **Share feedback** button and select **Report a problem** to report the issue to the {% data variables.product.prodname_vs %} team. - - ![Screenshot of the share feedback button in {% data variables.product.prodname_vs %}.](/assets/images/help/copilot/vs-share-feedback-button.png) - -{% endvisualstudio %} - -{% webui %} - -## Troubleshooting interrupted chat responses - -If a chat response terminates unexpectedly, before the response is complete, try resubmitting the question. - -In {% data variables.copilot.copilot_chat_short %}'s immersive view (the [github.com/copilot](https://github.com/copilot) page), you can resubmit your question by clicking the {% octicon "sync" aria-label="Retry" %} button under the chat response. - -{% endwebui %} diff --git a/content/repositories/creating-and-managing-repositories/repository-limits.md b/content/repositories/creating-and-managing-repositories/repository-limits.md index 7c827ed6e7e8..ad49d93c750a 100644 --- a/content/repositories/creating-and-managing-repositories/repository-limits.md +++ b/content/repositories/creating-and-managing-repositories/repository-limits.md @@ -9,14 +9,66 @@ topics: - Repositories --- -Certain types of repository resources can be quite large, requiring excessive processing on {% data variables.product.github %}. Because of this, limits are set to ensure requests complete in a reasonable amount of time. +Certain types of repository resources can be quite large, requiring excessive processing on {% data variables.product.github %}. Because of this, limits are set to ensure requests complete in a reasonable amount of time. Exceeding the recommended maximum limit increases the risk of degraded repository health, which includes, but is not limited to, slow response times for basic Git operations and UI latency. + +>[!NOTE] While following these guidelines can improve repository stability, it does not guarantee supportability, as other factors may lead to unexpected behavior. Most of the limits below affect both {% data variables.product.github %} and the API. +## Repository size + +To ensure optimal performance and manageability, we recommend staying within the following maximum limits for repository structure and size. + +* **On-disk size**: 10 GB + + On-disk size refers to the size of the `.git` folder (the compressed form of the repository). Large repositories can slow down fetch operations and increase clone times for developers and CI. To manage repository size: + + * Use {% data variables.large_files.product_name_long %} ({% data variables.large_files.product_name_short %}) for binary files. + * Store programmatically generated files outside of Git, such as in object storage. + +* **Directory width (number of entries in a single directory)**: 3,000 + + Directories containing numerous frequently modified files can significantly increase repository maintenance costs and degrade the performance of basic Git operations. Segmenting files into a shallow directory structure will reduce the size of these trees and result in less new data created. + +* **Directory depth**: 50 + + Deep directory trees can make history-walking operations slower. + +* **Number of branches**: 5,000 + + Large numbers of branches can result in unnecessary data in fetch operations, leading to slow transfer times or in extreme cases throttled repository performance. + +## Activity + +To avoid throttling and performance issues, we recommend staying within the following operational limits. + +* **Push size**: This limit is enforced at 2GB. +* **Single object size**: + + The recommended maximum limit is 1MB. This is enforced at 100MB. To track large files in a Git repository, we recommend using {% data variables.large_files.product_name_short %}. See [AUTOTITLE](/repositories/working-with-files/managing-large-files/about-git-large-file-storage). + +* **Git read operations (e.g. fetches, clones)**: + + The recommended maximum limit is 15 operations per second per repository. Large amounts of read operations can result in throttled performance for a repository. Automated processes such as CI, machine users, or third-party applications, can degrade a repository's performance in some cases. Consider optimizing your CI's clone strategy and/or using a repository cache server. Note that shallow clones will impose less cost and burden on the server than full clones and therefore may perform better. + +* **Push rate**: The recommended maximum limit is 6 pushes per minute per repository. + ## Text limits {% data variables.product.prodname_dotcom %} displays formatted previews of some files, such as Markdown and Mermaid diagrams. {% data variables.product.prodname_dotcom %} always attempts to render these previews if the files are small (generally less than 2 MB), but more complex files may time out and either fall back to plain text or not be displayed at all. These files are always available in their raw formats, which are served through `{% data variables.product.raw_github_com %}`; for example, `https://{% data variables.product.raw_github_com %}/octocat/Spoon-Knife/master/index.html`. Click the **Raw** button to get the raw URL for a file. +## Pull requests limits + +To reduce delays and performance issues in repositories with high pull request activity, we recommend staying within the following limits. + +* **Open pull requests (against the same branch)**: 1,000 + + Having many open pull requests targeting the same branch can slow down mergeability checks or lead to timeouts. If you're using a merge queue, consider disabling the "require this branch to be up to date before merging" setting. This limits mergeability checks to only the pull requests in the queue. + +* **Pull request merge rate**: 1 merged pull request per minute + + Each merge triggers mergeability checks for all open pull requests, which can cause performance bottlenecks—especially in busy repositories. This can also lead to a race-to-merge situation that impacts developer productivity. To reduce load, disable the "require this branch to be up to date before merging" setting when using a merge queue. + ## Diff limits Because diffs can become very large, we impose these limits on diffs for commits, pull requests, and compare views: diff --git a/data/reusables/contributing/content-linter-rules.md b/data/reusables/contributing/content-linter-rules.md index fa3c668e30c9..f2631f0f144a 100644 --- a/data/reusables/contributing/content-linter-rules.md +++ b/data/reusables/contributing/content-linter-rules.md @@ -70,6 +70,7 @@ | GHD051 | frontmatter-versions-whitespace | Versions frontmatter should not contain unnecessary whitespace | warning | frontmatter, versions | | 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 | | [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/code-completion-available-models.md b/data/reusables/copilot/code-completion-available-models.md new file mode 100644 index 000000000000..cb928709a3a6 --- /dev/null +++ b/data/reusables/copilot/code-completion-available-models.md @@ -0,0 +1 @@ +> [!NOTE] The list of available models will change over time. When only one code completion model is available, the model picker will only show that model. Preview models and additional code completion models will be added to the picker as they become available. diff --git a/data/reusables/copilot/code-completion-switch-model-affects.md b/data/reusables/copilot/code-completion-switch-model-affects.md new file mode 100644 index 000000000000..20b1232d4604 --- /dev/null +++ b/data/reusables/copilot/code-completion-switch-model-affects.md @@ -0,0 +1,19 @@ +> [!NOTE] The list of available models will change over time. When only one code completion model is available, the model picker will only show that model. Preview models and additional code completion models will be added to the picker as they become available. + +For details of how to switch the model for {% data variables.product.prodname_copilot_short %} code completion, see [AUTOTITLE](/copilot/how-tos/ai-models/changing-the-ai-model-for-copilot-code-completion). + +## Effects of switching the AI model + +Changing the model that's used for {% data variables.product.prodname_copilot_short %} code completion does not affect the model that's used by {% data variables.product.prodname_copilot_short %} next edit suggestions or {% data variables.copilot.copilot_chat_short %}. See [AUTOTITLE](/copilot/using-github-copilot/ai-models/changing-the-ai-model-for-copilot-chat). + +There are no changes to the data collection and usage policy if you change the AI model. + +If you are on a {% data variables.copilot.copilot_free_short %} plan, all completions count against your completions quota regardless of the model used. See [AUTOTITLE](/copilot/about-github-copilot/subscription-plans-for-github-copilot#comparing-copilot-subscriptions). + +The setting to enable or disable suggestions that match public code are applied irrespective of which model you choose. See [AUTOTITLE](/copilot/using-github-copilot/finding-public-code-that-matches-github-copilot-suggestions). + +## Enabling the model switcher + +If you have a {% data variables.copilot.copilot_free_short %} or {% data variables.copilot.copilot_pro_short %} plan, the model switcher for {% data variables.product.prodname_copilot_short %} code completion is automatically enabled. + +{% data reusables.copilot.editor-preview-settings %} diff --git a/data/reusables/copilot/code-completion-switch-prereqs-jetbrains.md b/data/reusables/copilot/code-completion-switch-prereqs-jetbrains.md new file mode 100644 index 000000000000..2ca68590d196 --- /dev/null +++ b/data/reusables/copilot/code-completion-switch-prereqs-jetbrains.md @@ -0,0 +1,4 @@ +You can switch the AI model that's used by {% data variables.product.prodname_copilot_short %} code completion if: + +* An alternative model is currently available +* You are using the latest release of JetBrains IDEs with the latest version of the {% data variables.product.prodname_copilot %} extension diff --git a/data/reusables/copilot/code-completion-switch-prereqs-vs.md b/data/reusables/copilot/code-completion-switch-prereqs-vs.md new file mode 100644 index 000000000000..fa5689c1b645 --- /dev/null +++ b/data/reusables/copilot/code-completion-switch-prereqs-vs.md @@ -0,0 +1,4 @@ +You can switch the AI model that's used by {% data variables.product.prodname_copilot_short %} code completion if: + +* An alternative model is currently available +* You are using {% data variables.product.prodname_vs %} 17.14 Preview 2 or later diff --git a/data/reusables/copilot/code-completion-switch-prereqs-vscode.md b/data/reusables/copilot/code-completion-switch-prereqs-vscode.md new file mode 100644 index 000000000000..1e7ce28e5bfe --- /dev/null +++ b/data/reusables/copilot/code-completion-switch-prereqs-vscode.md @@ -0,0 +1,4 @@ +You can switch the AI model that's used by {% data variables.product.prodname_copilot_short %} code completion if: + +* An alternative model is currently available +* You are using the latest releases of {% data variables.product.prodname_vscode_shortname %} with the latest version of the {% data variables.product.prodname_copilot %} extension diff --git a/src/content-linter/lib/linting-rules/frontmatter-validation.js b/src/content-linter/lib/linting-rules/frontmatter-validation.js new file mode 100644 index 000000000000..24c30ec493ac --- /dev/null +++ b/src/content-linter/lib/linting-rules/frontmatter-validation.js @@ -0,0 +1,165 @@ +import { addError } from 'markdownlint-rule-helpers' +import { getFrontmatter } from '@/content-linter/lib/helpers/utils' + +export const frontmatterValidation = { + names: ['GHD055', 'frontmatter-validation'], + description: + 'Frontmatter properties must meet character limits and required property requirements', + tags: ['frontmatter', 'character-limits', 'required-properties'], + function: (params, onError) => { + const fm = getFrontmatter(params.lines) + if (!fm) return + + // Detect content type based on frontmatter properties and file path + const contentType = detectContentType(fm, params.name) + + // Define character limits and requirements for different content types + const contentRules = { + category: { + title: { max: 70, recommended: 67 }, + shortTitle: { max: 30, recommended: 27 }, + intro: { required: true, recommended: 280, max: 362 }, + requiredProperties: ['intro'], + }, + mapTopic: { + title: { max: 70, recommended: 63 }, + shortTitle: { max: 35, recommended: 30 }, + intro: { required: true, recommended: 280, max: 362 }, + requiredProperties: ['intro'], + }, + article: { + title: { max: 80, recommended: 60 }, + shortTitle: { max: 30, recommended: 25 }, + intro: { required: false, recommended: 251, max: 354 }, + requiredProperties: ['topics'], + }, + } + + const rules = contentRules[contentType] + if (!rules) return + + // Check required properties + for (const property of rules.requiredProperties) { + if (!fm[property]) { + addError( + onError, + 1, + `Missing required property '${property}' for ${contentType} content type`, + null, + null, + null, + ) + } + } + + // Check title length + if (fm.title) { + validatePropertyLength(onError, params.lines, 'title', fm.title, rules.title, 'Title') + } + + // Check shortTitle length + if (fm.shortTitle) { + validatePropertyLength( + onError, + params.lines, + 'shortTitle', + fm.shortTitle, + rules.shortTitle, + 'ShortTitle', + ) + } + + // Check intro length if it exists + if (fm.intro && rules.intro) { + validatePropertyLength(onError, params.lines, 'intro', fm.intro, rules.intro, 'Intro') + } + + // Cross-property validation: if title is longer than shortTitle limit, shortTitle must exist + if (fm.title && fm.title.length > rules.shortTitle.max && !fm.shortTitle) { + const titleLine = findPropertyLine(params.lines, 'title') + addError( + onError, + titleLine, + `Title is ${fm.title.length} characters, which exceeds the shortTitle limit of ${rules.shortTitle.max} characters. A shortTitle must be provided.`, + fm.title, + null, + null, + ) + } + + // Special validation for articles: should have at least one topic + if (contentType === 'article' && fm.topics) { + if (!Array.isArray(fm.topics)) { + const topicsLine = findPropertyLine(params.lines, 'topics') + addError(onError, topicsLine, 'Topics must be an array', String(fm.topics), null, null) + } else if (fm.topics.length === 0) { + const topicsLine = findPropertyLine(params.lines, 'topics') + addError( + onError, + topicsLine, + 'Articles should have at least one topic', + 'topics: []', + null, + null, + ) + } + } + }, +} + +function validatePropertyLength(onError, lines, propertyName, propertyValue, limits, displayName) { + const propertyLength = propertyValue.length + const propertyLine = findPropertyLine(lines, propertyName) + + // Only report the most severe error - maximum takes precedence over recommended + if (propertyLength > limits.max) { + addError( + onError, + propertyLine, + `${displayName} exceeds maximum length of ${limits.max} characters (current: ${propertyLength})`, + propertyValue, + null, + null, + ) + } else if (propertyLength > limits.recommended) { + addError( + onError, + propertyLine, + `${displayName} exceeds recommended length of ${limits.recommended} characters (current: ${propertyLength})`, + propertyValue, + null, + null, + ) + } +} + +function detectContentType(frontmatter, filePath) { + // Only apply validation to markdown files + if (!filePath || !filePath.endsWith('.md')) { + return null + } + + // Map topics have mapTopic: true + if (frontmatter.mapTopic === true) { + return 'mapTopic' + } + + // Categories are index.md files that contain children but no mapTopic + // Only check files that look like they're in the content directory structure + if ( + filePath.includes('/index.md') && + frontmatter.children && + Array.isArray(frontmatter.children) && + !frontmatter.mapTopic + ) { + return 'category' + } + + // Everything else is an article + return 'article' +} + +function findPropertyLine(lines, property) { + const line = lines.find((line) => line.trim().startsWith(`${property}:`)) + return line ? lines.indexOf(line) + 1 : 1 +} diff --git a/src/content-linter/lib/linting-rules/index.js b/src/content-linter/lib/linting-rules/index.js index 104b23fe83f3..f7e5a74b3477 100644 --- a/src/content-linter/lib/linting-rules/index.js +++ b/src/content-linter/lib/linting-rules/index.js @@ -51,6 +51,7 @@ import { britishEnglishQuotes } from '@/content-linter/lib/linting-rules/british import { multipleEmphasisPatterns } from '@/content-linter/lib/linting-rules/multiple-emphasis-patterns' import { noteWarningFormatting } from '@/content-linter/lib/linting-rules/note-warning-formatting' import { frontmatterVersionsWhitespace } from '@/content-linter/lib/linting-rules/frontmatter-versions-whitespace' +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' @@ -113,6 +114,7 @@ export const gitHubDocsMarkdownlint = { frontmatterVersionsWhitespace, // GHD051 headerContentRequirement, // GHD053 thirdPartyActionsReusable, // GHD054 + frontmatterValidation, // GHD055 // Search-replace rules searchReplace, // Open-source plugin diff --git a/src/content-linter/lib/linting-rules/liquid-versioning.js b/src/content-linter/lib/linting-rules/liquid-versioning.js index 86563f5d7a67..1f41f5df6865 100644 --- a/src/content-linter/lib/linting-rules/liquid-versioning.js +++ b/src/content-linter/lib/linting-rules/liquid-versioning.js @@ -133,9 +133,14 @@ function validateIfversionConditionals(cond, possibleVersionNames) { if (strParts.length === 2) { const [notKeyword, version] = strParts const isValidVersion = validateVersion(version) - const isValid = notKeyword === 'not' && isValidVersion - if (!isValid) { + const isFeatureBasedVersion = Object.keys(getAllFeatures()).includes(version) + + if (notKeyword !== 'not' || !isValidVersion) { errors.push(`"${cond}" is not a valid conditional`) + } else if (isFeatureBasedVersion) { + errors.push( + `"${cond}" is not valid - the 'not' keyword cannot be used with feature-based version "${version}"`, + ) } } diff --git a/src/content-linter/style/github-docs.js b/src/content-linter/style/github-docs.js index 36a4431aca11..dec20d56789a 100644 --- a/src/content-linter/style/github-docs.js +++ b/src/content-linter/style/github-docs.js @@ -304,6 +304,12 @@ export const githubDocsFrontmatterConfig = { 'partial-markdown-files': false, 'yml-files': false, }, + 'frontmatter-validation': { + // GHD055 + severity: 'warning', + 'partial-markdown-files': false, + 'yml-files': false, + }, } // Configures rules from the `github/markdownlint-github` repo diff --git a/src/content-linter/tests/unit/frontmatter-validation.js b/src/content-linter/tests/unit/frontmatter-validation.js new file mode 100644 index 000000000000..be4a5d6baad0 --- /dev/null +++ b/src/content-linter/tests/unit/frontmatter-validation.js @@ -0,0 +1,504 @@ +import { describe, expect, test } from 'vitest' + +import { runRule } from '@/content-linter/lib/init-test' +import { frontmatterValidation } from '@/content-linter/lib/linting-rules/frontmatter-validation' + +const ruleName = frontmatterValidation.names[1] + +// Configure the test fixture to not split frontmatter and content +const fmOptions = { markdownlintOptions: { frontMatter: null } } + +describe(ruleName, () => { + // Character limit tests + test('category title within limits passes', async () => { + const markdown = `--- +title: 'Short category title' +intro: 'Category introduction' +children: + - /path/to/child +--- +# Content +` + const result = await runRule(frontmatterValidation, { + strings: { 'content/section/index.md': markdown }, + ...fmOptions, + }) + expect(result['content/section/index.md']).toEqual([]) + }) + + test('category title exceeds recommended limit shows warning', async () => { + const markdown = `--- +title: 'This category title is exactly 68 characters long for testing purpos' +shortTitle: 'Short title' +intro: 'Category introduction' +children: + - /path/to/child +--- +# Content +` + const result = await runRule(frontmatterValidation, { + strings: { 'content/section/index.md': markdown }, + ...fmOptions, + }) + expect(result['content/section/index.md']).toHaveLength(1) + expect(result['content/section/index.md'][0].errorDetail).toContain( + 'exceeds recommended length of 67 characters', + ) + }) + + test('category title exceeds maximum limit shows error', async () => { + const markdown = `--- +title: 'This is exactly 71 characters long to exceed the maximum limit for catx' +shortTitle: 'Short title' +intro: 'Category introduction' +children: + - /path/to/child +--- +# Content +` + const result = await runRule(frontmatterValidation, { + strings: { 'content/section/index.md': markdown }, + ...fmOptions, + }) + expect(result['content/section/index.md']).toHaveLength(1) + expect(result['content/section/index.md'][0].errorDetail).toContain( + 'exceeds maximum length of 70 characters', + ) + }) + + test('category shortTitle exceeds limit shows error', async () => { + const markdown = `--- +title: 'Category title' +shortTitle: 'This short title is exactly 31x' +intro: 'Category introduction' +children: + - /path/to/child +--- +# Content +` + const result = await runRule(frontmatterValidation, { + strings: { 'content/section/index.md': markdown }, + ...fmOptions, + }) + expect(result['content/section/index.md']).toHaveLength(1) + expect(result['content/section/index.md'][0].errorDetail).toContain('ShortTitle exceeds') + }) + + test('mapTopic title within limits passes', async () => { + const markdown = `--- +title: 'Using workflows' +intro: 'Map topic introduction' +mapTopic: true +--- +# Content +` + const result = await runRule(frontmatterValidation, { + strings: { 'content/actions/using-workflows/index.md': markdown }, + ...fmOptions, + }) + expect(result['content/actions/using-workflows/index.md']).toEqual([]) + }) + + test('mapTopic title exceeds recommended limit shows warning', async () => { + const markdown = `--- +title: 'This map topic title is exactly 64 characters long for tests now' +shortTitle: 'Short title' +intro: 'Map topic introduction' +mapTopic: true +--- +# Content +` + const result = await runRule(frontmatterValidation, { + strings: { 'content/actions/using-workflows/index.md': markdown }, + ...fmOptions, + }) + expect(result['content/actions/using-workflows/index.md']).toHaveLength(1) + expect(result['content/actions/using-workflows/index.md'][0].errorDetail).toContain( + 'exceeds recommended length of 63 characters', + ) + }) + + test('article title within limits passes', async () => { + const markdown = `--- +title: 'GitHub Actions quickstart' +topics: + - Actions +--- +# Content +` + const result = await runRule(frontmatterValidation, { + strings: { 'content/actions/quickstart.md': markdown }, + ...fmOptions, + }) + expect(result['content/actions/quickstart.md']).toEqual([]) + }) + + test('article title exceeds recommended limit shows warning', async () => { + const markdown = `--- +title: 'This article title is exactly 61 characters long for test now' +shortTitle: 'Short title' +topics: + - Actions +--- +# Content +` + const result = await runRule(frontmatterValidation, { + strings: { 'content/actions/quickstart.md': markdown }, + ...fmOptions, + }) + expect(result['content/actions/quickstart.md']).toHaveLength(1) + expect(result['content/actions/quickstart.md'][0].errorDetail).toContain( + 'exceeds recommended length of 60 characters', + ) + }) + + test('article title exceeds maximum limit shows error', async () => { + const markdown = `--- +title: 'This article title is exactly 81 characters long to exceed the maximum limits now' +shortTitle: 'Short title' +topics: + - Actions +--- +# Content +` + const result = await runRule(frontmatterValidation, { + strings: { 'content/actions/quickstart.md': markdown }, + ...fmOptions, + }) + expect(result['content/actions/quickstart.md']).toHaveLength(1) + expect(result['content/actions/quickstart.md'][0].errorDetail).toContain( + 'exceeds maximum length of 80 characters', + ) + }) + + test('cross-property validation: long title without shortTitle shows error', async () => { + const markdown = `--- +title: 'This article title is exactly 50 characters long' +topics: + - Actions +--- +# Content +` + const result = await runRule(frontmatterValidation, { + strings: { 'content/actions/quickstart.md': markdown }, + ...fmOptions, + }) + expect(result['content/actions/quickstart.md']).toHaveLength(1) + expect(result['content/actions/quickstart.md'][0].errorDetail).toContain( + 'A shortTitle must be provided', + ) + }) + + test('cross-property validation: long title with shortTitle passes', async () => { + const markdown = `--- +title: 'This article title is exactly 50 characters long' +shortTitle: 'Actions quickstart' +topics: + - Actions +--- +# Content +` + const result = await runRule(frontmatterValidation, { + strings: { 'content/actions/quickstart.md': markdown }, + ...fmOptions, + }) + expect(result['content/actions/quickstart.md']).toEqual([]) + }) + + // Required properties tests + test('category with required intro passes', async () => { + const markdown = `--- +title: 'Category title' +intro: 'This is the category introduction.' +children: + - /path/to/child +--- +# Content +` + const result = await runRule(frontmatterValidation, { + strings: { 'content/section/index.md': markdown }, + ...fmOptions, + }) + expect(result['content/section/index.md']).toEqual([]) + }) + + test('category without required intro fails', async () => { + const markdown = `--- +title: 'Category title' +children: + - /path/to/child +--- +# Content +` + const result = await runRule(frontmatterValidation, { + strings: { 'content/section/index.md': markdown }, + ...fmOptions, + }) + expect(result['content/section/index.md']).toHaveLength(1) + expect(result['content/section/index.md'][0].errorDetail).toContain( + "Missing required property 'intro' for category content type", + ) + }) + + test('category with intro too long shows warning', async () => { + const longIntro = 'A'.repeat(400) // Exceeds 362 char limit + const markdown = `--- +title: 'Category title' +intro: '${longIntro}' +children: + - /path/to/child +--- +# Content +` + const result = await runRule(frontmatterValidation, { + strings: { 'content/section/index.md': markdown }, + ...fmOptions, + }) + expect(result['content/section/index.md']).toHaveLength(1) + expect(result['content/section/index.md'][0].errorDetail).toContain( + 'Intro exceeds maximum length of 362 characters', + ) + }) + + test('mapTopic with required intro passes', async () => { + const markdown = `--- +title: 'Map topic title' +intro: 'This is the map topic introduction.' +mapTopic: true +--- +# Content +` + const result = await runRule(frontmatterValidation, { + strings: { 'content/section/topic.md': markdown }, + ...fmOptions, + }) + expect(result['content/section/topic.md']).toEqual([]) + }) + + test('mapTopic without required intro fails', async () => { + const markdown = `--- +title: 'Map topic title' +mapTopic: true +--- +# Content +` + const result = await runRule(frontmatterValidation, { + strings: { 'content/section/topic.md': markdown }, + ...fmOptions, + }) + expect(result['content/section/topic.md']).toHaveLength(1) + expect(result['content/section/topic.md'][0].errorDetail).toContain( + "Missing required property 'intro' for mapTopic content type", + ) + }) + + test('mapTopic with intro too long shows warning', async () => { + const longIntro = 'A'.repeat(400) // Exceeds 362 char limit + const markdown = `--- +title: 'Map topic title' +intro: '${longIntro}' +mapTopic: true +--- +# Content +` + const result = await runRule(frontmatterValidation, { + strings: { 'content/section/topic.md': markdown }, + ...fmOptions, + }) + expect(result['content/section/topic.md']).toHaveLength(1) + expect(result['content/section/topic.md'][0].errorDetail).toContain( + 'Intro exceeds maximum length of 362 characters', + ) + }) + + test('article with required topics passes', async () => { + const markdown = `--- +title: 'Article title' +topics: + - Actions + - CI/CD +--- +# Content +` + const result = await runRule(frontmatterValidation, { + strings: { 'content/section/article.md': markdown }, + ...fmOptions, + }) + expect(result['content/section/article.md']).toEqual([]) + }) + + test('article without required topics fails', async () => { + const markdown = `--- +title: 'Article title' +--- +# Content +` + const result = await runRule(frontmatterValidation, { + strings: { 'content/section/article.md': markdown }, + ...fmOptions, + }) + expect(result['content/section/article.md']).toHaveLength(1) + expect(result['content/section/article.md'][0].errorDetail).toContain( + "Missing required property 'topics' for article content type", + ) + }) + + test('article with empty topics array fails', async () => { + const markdown = `--- +title: 'Article title' +topics: [] +--- +# Content +` + const result = await runRule(frontmatterValidation, { + strings: { 'content/section/article.md': markdown }, + ...fmOptions, + }) + expect(result['content/section/article.md']).toHaveLength(1) + expect(result['content/section/article.md'][0].errorDetail).toContain( + 'Articles should have at least one topic', + ) + }) + + test('article with topics as string fails', async () => { + const markdown = `--- +title: 'Article title' +topics: 'Actions' +--- +# Content +` + const result = await runRule(frontmatterValidation, { + strings: { 'content/section/article.md': markdown }, + ...fmOptions, + }) + expect(result['content/section/article.md']).toHaveLength(1) + expect(result['content/section/article.md'][0].errorDetail).toContain('Topics must be an array') + }) + + test('article with topics as number fails', async () => { + const markdown = `--- +title: 'Article title' +topics: 123 +--- +# Content +` + const result = await runRule(frontmatterValidation, { + strings: { 'content/section/article.md': markdown }, + ...fmOptions, + }) + expect(result['content/section/article.md']).toHaveLength(1) + expect(result['content/section/article.md'][0].errorDetail).toContain('Topics must be an array') + }) + + test('article with intro too long shows warning', async () => { + const longIntro = 'A'.repeat(400) // Exceeds 354 char limit for articles + const markdown = `--- +title: 'Article title' +intro: '${longIntro}' +topics: + - Actions +--- +# Content +` + const result = await runRule(frontmatterValidation, { + strings: { 'content/section/article.md': markdown }, + ...fmOptions, + }) + expect(result['content/section/article.md']).toHaveLength(1) + expect(result['content/section/article.md'][0].errorDetail).toContain( + 'Intro exceeds maximum length of 354 characters', + ) + }) + + test('article intro exceeds recommended but not maximum shows warning', async () => { + const mediumIntro = 'A'.repeat(300) // Exceeds 251 recommended but under 354 max + const markdown = `--- +title: 'Article title' +intro: '${mediumIntro}' +topics: + - Actions +--- +# Content +` + const result = await runRule(frontmatterValidation, { + strings: { 'content/section/article.md': markdown }, + ...fmOptions, + }) + expect(result['content/section/article.md']).toHaveLength(1) + expect(result['content/section/article.md'][0].errorDetail).toContain( + 'Intro exceeds recommended length of 251 characters', + ) + }) + + // Combined validation tests + test('multiple violations show multiple errors', async () => { + const longIntro = 'A'.repeat(400) + const markdown = `--- +title: 'This is exactly 71 characters long to exceed the maximum limit for catx' +intro: '${longIntro}' +shortTitle: 'Short title' +children: + - /path/to/child +--- +# Content +` + const result = await runRule(frontmatterValidation, { + strings: { 'content/section/index.md': markdown }, + ...fmOptions, + }) + expect(result['content/section/index.md']).toHaveLength(2) + expect(result['content/section/index.md'][0].errorDetail).toContain('Title exceeds') + expect(result['content/section/index.md'][1].errorDetail).toContain('Intro exceeds') + }) + + test('no frontmatter passes', async () => { + const markdown = `# Content without frontmatter` + const result = await runRule(frontmatterValidation, { strings: { markdown }, ...fmOptions }) + expect(result.markdown).toEqual([]) + }) + + test('content type detection works correctly', async () => { + // Test category detection + const categoryMarkdown = `--- +title: 'Category' +intro: 'Category intro' +children: + - /child +--- +# Content +` + const categoryResult = await runRule(frontmatterValidation, { + strings: { 'content/section/index.md': categoryMarkdown }, + ...fmOptions, + }) + expect(categoryResult['content/section/index.md']).toEqual([]) + + // Test mapTopic detection + const mapTopicMarkdown = `--- +title: 'Map Topic' +intro: 'Map topic intro' +mapTopic: true +--- +# Content +` + const mapTopicResult = await runRule(frontmatterValidation, { + strings: { 'content/section/topic.md': mapTopicMarkdown }, + ...fmOptions, + }) + expect(mapTopicResult['content/section/topic.md']).toEqual([]) + + // Test article detection + const articleMarkdown = `--- +title: 'Article' +topics: + - Topic +--- +# Content +` + const articleResult = await runRule(frontmatterValidation, { + strings: { 'content/section/article.md': articleMarkdown }, + ...fmOptions, + }) + expect(articleResult['content/section/article.md']).toEqual([]) + }) +}) diff --git a/src/content-linter/tests/unit/liquid-versioning.js b/src/content-linter/tests/unit/liquid-versioning.js index 22f1a7aad588..2711ce83ee9e 100644 --- a/src/content-linter/tests/unit/liquid-versioning.js +++ b/src/content-linter/tests/unit/liquid-versioning.js @@ -94,4 +94,29 @@ describe(liquidIfVersionTags.names.join(' - '), () => { const errors = result.markdown expect(errors.length).toBe(0) }) + + test('ifversion tags with not keyword and feature-based versions fail', async () => { + const markdown = [ + '{% ifversion not volvo %}', + '{% ifversion fpt or not volvo %}', + '{% ifversion not them-and-all %}', + ] + const result = await runRule(liquidIfVersionTags, { + strings: { markdown: markdown.join('\n') }, + }) + const errors = result.markdown + expect(errors.length).toBe(markdown.length) + expect(errors.every((error) => error.errorDetail.includes('feature-based version'))).toBe(true) + }) + + test('ifversion tags with not keyword and short versions pass', async () => { + const markdown = [ + '{% ifversion not ghec %}', + '{% ifversion fpt or not ghes %}', + '{% ifversion not fpt %}', + ].join('\n') + const result = await runRule(liquidIfVersionTags, { strings: { markdown } }) + const errors = result.markdown + expect(errors.length).toBe(0) + }) }) diff --git a/src/content-render/liquid/ifversion.js b/src/content-render/liquid/ifversion.js index 1305fe9b981c..7446601d9e28 100644 --- a/src/content-render/liquid/ifversion.js +++ b/src/content-render/liquid/ifversion.js @@ -48,7 +48,7 @@ export default class extends Tag { } // The following is _mostly_ verbatim from https://github.com/harttle/liquidjs/blob/v9.22.1/src/builtin/tags/if.ts - // The additions here are the handleNots() and handleOperators() calls. + // The additions here are the handleNots(), handleOperators(), and handleVersionNames() calls. *render(ctx, emitter) { const r = this.liquid.renderer @@ -64,6 +64,13 @@ export default class extends Tag { // This will replace syntax like `fpt or ghes < 3.0` with `fpt or true` or `fpt or false`. resolvedBranchCond = this.handleOperators(resolvedBranchCond) + // Resolve version names to boolean values for Markdown API context. + // This will replace syntax like `fpt or ghec` with `true or false` based on current version. + // Only apply this transformation in Markdown API context to avoid breaking existing functionality. + if (ctx.environments.markdownRequested) { + resolvedBranchCond = this.handleVersionNames(resolvedBranchCond, ctx) + } + // Use Liquid's native function for the final evaluation. const cond = yield new Value(resolvedBranchCond, this.liquid).value(ctx, ctx.opts.lenientIf) @@ -174,4 +181,27 @@ export default class extends Tag { return resolvedBranchCond } + + handleVersionNames(resolvedBranchCond, ctx) { + if (!this.currentVersionObj) { + console.warn('currentVersionObj not found in ifversion context.') + return resolvedBranchCond + } + + // Split the condition into tokens for processing + const tokens = resolvedBranchCond.split(/\s+/) + const processedTokens = tokens.map((token) => { + // Check if the token is a version short name (fpt, ghec, ghes, ghae) + const versionShortNames = ['fpt', 'ghec', 'ghes', 'ghae'] + if (versionShortNames.includes(token)) { + // Transform version names to boolean values for Markdown API + // This fixes the original issue where version names were undefined in API context + return token === this.currentVersionObj.shortName ? 'true' : 'false' + } + // Return the token unchanged if it's not a version name + return token + }) + + return processedTokens.join(' ') + } } diff --git a/src/fixtures/fixtures/content/get-started/liquid/index.md b/src/fixtures/fixtures/content/get-started/liquid/index.md index ab84f4b632fd..da3fe83dd65b 100644 --- a/src/fixtures/fixtures/content/get-started/liquid/index.md +++ b/src/fixtures/fixtures/content/get-started/liquid/index.md @@ -18,5 +18,6 @@ children: - /links-with-liquid - /tool-specific - /tool-platform-switcher + - /tool-picker-issue - /data --- diff --git a/src/fixtures/fixtures/content/get-started/liquid/tool-picker-issue.md b/src/fixtures/fixtures/content/get-started/liquid/tool-picker-issue.md new file mode 100644 index 000000000000..d60e7041a471 --- /dev/null +++ b/src/fixtures/fixtures/content/get-started/liquid/tool-picker-issue.md @@ -0,0 +1,42 @@ +--- +title: Tool picker issue test +intro: This page demonstrates the tool picker issue where only the default tool content is shown in markdown API +defaultTool: webui +versions: + fpt: '*' + ghes: '*' + ghec: '*' +type: tutorial +--- + +## Starting a review + +{% webui %} + +1. Under your repository name, click **Pull requests**. +1. In the list of pull requests, click the pull request you'd like to review. +1. On the pull request, click **Files changed**. + +{% endwebui %} + +{% codespaces %} + +1. Open the pull request in your codespace. +1. Navigate to the **Files** tab. +1. Review the changes inline. + +{% endcodespaces %} + +## Submitting your review + +{% webui %} + +After reviewing the files, you can submit your review from the web interface. + +{% endwebui %} + +{% codespaces %} + +After reviewing the files, you can submit your review directly from Codespaces. + +{% endcodespaces %} diff --git a/src/fixtures/fixtures/content/get-started/liquid/tool-specific.md b/src/fixtures/fixtures/content/get-started/liquid/tool-specific.md index d74d14787642..d19398730498 100644 --- a/src/fixtures/fixtures/content/get-started/liquid/tool-specific.md +++ b/src/fixtures/fixtures/content/get-started/liquid/tool-specific.md @@ -1,7 +1,7 @@ --- title: Tool switching Liquid tags intro: Demonstrates the HTML that becomes of a the different tool Liquid tags like `webui`, `cli`, and `codespaces` -defaultTool: desktop +defaultTool: webui versions: fpt: '*' ghes: '*' @@ -15,13 +15,13 @@ This page has a tool switcher {% webui %} -1. this is webui content +1. This is webui content {% endwebui %} {% cli %} -this is cli content +This is cli content ```shell cli content @@ -30,7 +30,7 @@ cli content {% endcli %} {% desktop %} - this is desktop content + This is desktop content {% enddesktop %} {% webui %} diff --git a/src/fixtures/tests/api-article-body.ts b/src/fixtures/tests/api-article-body.ts index 49528febd64b..05006793549c 100644 --- a/src/fixtures/tests/api-article-body.ts +++ b/src/fixtures/tests/api-article-body.ts @@ -74,4 +74,123 @@ describe('article body api', () => { const { error } = JSON.parse(res.body) expect(error).toBe("Multiple 'pathname' keys") }) + + test('tool picker shows all tool variants in markdown', async () => { + const res = await get(makeURL('/en/get-started/liquid/tool-specific')) + expect(res.statusCode).toBe(200) + expect(res.headers['content-type']).toContain('text/markdown') + + // Should contain all tool-specific content variants + expect(res.body).toContain('
') + expect(res.body).toContain('
') + expect(res.body).toContain('
') + + // Should contain the actual content from each tool + expect(res.body).toContain('This is webui content') + expect(res.body).toContain('This is cli content') + expect(res.body).toContain('This is desktop content') + + // Should contain tool-specific sections + expect(res.body).toContain('Webui section specific content') + expect(res.body).toContain('Desktop section specific content') + + // Verify multiple instances of the same tool are preserved + const webuiMatches = res.body.match(/
/g) + const desktopMatches = res.body.match(/
/g) + expect(webuiMatches).toBeDefined() + expect(webuiMatches!.length).toBeGreaterThan(1) + expect(desktopMatches).toBeDefined() + expect(desktopMatches!.length).toBeGreaterThan(1) + }) + + test('codespaces tool content is included in markdown API', async () => { + const res = await get(makeURL('/en/get-started/liquid/tool-picker-issue')) + expect(res.statusCode).toBe(200) + expect(res.headers['content-type']).toContain('text/markdown') + + // Should contain both webui and codespaces tool content + expect(res.body).toContain('
') + expect(res.body).toContain('
') + + // Should contain the actual content from both tools + expect(res.body).toContain('Under your repository name, click **Pull requests**') + expect(res.body).toContain('Open the pull request in your codespace') + expect(res.body).toContain( + 'After reviewing the files, you can submit your review from the web interface', + ) + expect(res.body).toContain( + 'After reviewing the files, you can submit your review directly from Codespaces', + ) + + // Verify both tools appear in multiple sections + const webuiMatches = res.body.match(/
/g) + const codespacesMatches = res.body.match(/
/g) + expect(webuiMatches).toBeDefined() + expect(webuiMatches!.length).toBe(2) + expect(codespacesMatches).toBeDefined() + expect(codespacesMatches!.length).toBe(2) + }) + + test('codespaces content included in production markdown API', async () => { + // Test a real production page that has codespaces content + const res = await get( + makeURL( + '/en/pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/reviewing-proposed-changes-in-a-pull-request', + ), + ) + + // Skip test if page doesn't exist in fixture environment + if (res.statusCode === 404) { + console.log('Production page not available in fixture environment, skipping test') + return + } + + expect(res.statusCode).toBe(200) + + // Verify the fix is working - codespaces content should now be present + const hasCodespacesContent = res.body.includes('
') + expect(hasCodespacesContent).toBe(true) + + // Also verify that webui content is still present + expect(res.body).toContain('
') + }) + + test('verifies original issue #5400 is resolved', async () => { + // This test specifically addresses the original issue where tool picker + // content was missing from the Markdown API response + const res = await get( + makeURL( + '/en/pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/reviewing-proposed-changes-in-a-pull-request', + ), + ) + + // Skip test if page doesn't exist in fixture environment + if (res.statusCode === 404) { + console.log( + 'Production page not available in fixture environment, skipping issue verification test', + ) + return + } + + expect(res.statusCode).toBe(200) + expect(res.headers['content-type']).toContain('text/markdown') + + // The original issue was that only webui content was returned, missing codespaces + expect(res.body).toContain('
') + expect(res.body).toContain('
') + + // Verify specific codespaces content that was missing before the fix + expect(res.body).toContain('GitHub Codespaces') + expect(res.body).toContain('Open the pull request in a codespace') + + // Ensure both tools are rendered with their respective content + const webuiMatches = res.body.match(/
/g) + const codespacesMatches = res.body.match(/
/g) + + expect(webuiMatches).toBeDefined() + expect(codespacesMatches).toBeDefined() + expect(codespacesMatches!.length).toBeGreaterThan(0) + + console.log('âś… Issue #5400 resolved: All tool picker content now included in Markdown API') + }) }) diff --git a/src/fixtures/tests/playwright-rendering.spec.ts b/src/fixtures/tests/playwright-rendering.spec.ts index a04a803e21ed..95177154e087 100644 --- a/src/fixtures/tests/playwright-rendering.spec.ts +++ b/src/fixtures/tests/playwright-rendering.spec.ts @@ -257,23 +257,23 @@ test.describe('tool picker', () => { await page.getByTestId('tool-picker').getByRole('link', { name: 'GitHub CLI' }).click() await expect(page).toHaveURL(/\?tool=cli/) - await expect(page.getByText('this is cli content')).toBeVisible() - await expect(page.getByText('this is webui content')).not.toBeVisible() + await expect(page.getByText('This is cli content')).toBeVisible() + await expect(page.getByText('This is webui content')).not.toBeVisible() await page.getByTestId('tool-picker').getByRole('link', { name: 'Web browser' }).click() await expect(page).toHaveURL(/\?tool=webui/) - await expect(page.getByText('this is cli content')).not.toBeVisible() - await expect(page.getByText('this is desktop content')).not.toBeVisible() - await expect(page.getByText('this is webui content')).toBeVisible() + await expect(page.getByText('This is cli content')).not.toBeVisible() + await expect(page.getByText('This is desktop content')).not.toBeVisible() + await expect(page.getByText('This is webui content')).toBeVisible() }) test('prefer default tool', async ({ page }) => { await page.goto('/get-started/liquid/tool-specific') - // defaultTool is set in the fixture frontmatter - await expect(page.getByText('this is desktop content')).toBeVisible() - await expect(page.getByText('this is webui content')).not.toBeVisible() - await expect(page.getByText('this is cli content')).not.toBeVisible() + // defaultTool is set in the fixture frontmatter to webui + await expect(page.getByText('This is webui content')).toBeVisible() + await expect(page.getByText('This is desktop content')).not.toBeVisible() + await expect(page.getByText('This is cli content')).not.toBeVisible() }) test('remember last clicked tool', async ({ page }) => { @@ -284,28 +284,28 @@ test.describe('tool picker', () => { // Return and now the cookie should start us off with Web UI content again await page.goto('/get-started/liquid/tool-specific') - await expect(page.getByText('this is cli content')).not.toBeVisible() - await expect(page.getByText('this is desktop content')).not.toBeVisible() - await expect(page.getByText('this is webui content')).toBeVisible() + await expect(page.getByText('This is cli content')).not.toBeVisible() + await expect(page.getByText('This is desktop content')).not.toBeVisible() + await expect(page.getByText('This is webui content')).toBeVisible() }) test('minitoc matches picker', async ({ page }) => { - // default tool set to desktop in fixture fronmatter + // default tool set to webui in fixture frontmatter await page.goto('/get-started/liquid/tool-specific') await turnOffExperimentsInPage(page) await dismissCTAPopover(page) - await expect( - page.getByTestId('minitoc').getByRole('link', { name: 'Desktop section' }), - ).toBeVisible() await expect( page.getByTestId('minitoc').getByRole('link', { name: 'Webui section' }), - ).not.toBeVisible() - await page.getByTestId('tool-picker').getByRole('link', { name: 'Web browser' }).click() + ).toBeVisible() await expect( page.getByTestId('minitoc').getByRole('link', { name: 'Desktop section' }), ).not.toBeVisible() + await page.getByTestId('tool-picker').getByRole('link', { name: 'Desktop' }).click() await expect( page.getByTestId('minitoc').getByRole('link', { name: 'Webui section' }), + ).not.toBeVisible() + await expect( + page.getByTestId('minitoc').getByRole('link', { name: 'Desktop section' }), ).toBeVisible() }) }) pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy