diff --git a/content/admin/all-releases.md b/content/admin/all-releases.md index f5dad4c6428d..9af60d5df368 100644 --- a/content/admin/all-releases.md +++ b/content/admin/all-releases.md @@ -20,28 +20,32 @@ We provide documentation for both supported and unsupported versions of {% data For information about the latest release, see the [{% data variables.product.prodname_enterprise %}](https://github.com/enterprise) website. -| Version | Release | {% data variables.release-phases.closing_down_caps %} date | Supported | Release notes | Documentation | -| :- | :- | :- | :-: | :- | :- | +| Version | Candidate | Release | {% data variables.release-phases.closing_down_caps %} date | Supported | Release notes | Docs | +| :- | :- | :- | :- | :-: | :- | :- | {%- for version in enterpriseServerReleases.supported %} -{%- assign currentDate = 'now' | date: '%s' %} -{%- assign deprecationDate = enterpriseServerReleases.dates[version].deprecationDate | date: '%s' %} -| {{version}} | {{enterpriseServerReleases.dates[version].releaseDate}} | Support temporarily extended until further notice | {% octicon "check" aria-label="Supported" %} | [{{version}} release notes](/enterprise-server@{{version}}/admin/release-notes) | [{{version}} documentation](/enterprise-server@{{version}}) | +| {{version}} | {{enterpriseServerReleases.dates[version].displayCandidateDate}} | {{enterpriseServerReleases.dates[version].displayReleaseDate}} | Support temporarily extended until further notice | {% octicon "check" aria-label="Supported" %} | [{{version}} release notes](/enterprise-server@{{version}}/admin/release-notes) | [{{version}} docs](/enterprise-server@{{version}}) | {%- endfor %} {%- for version in enterpriseServerReleases.deprecatedReleasesWithNewFormat %} -| {{version}} | {{enterpriseServerReleases.dates[version].releaseDate}} | {{enterpriseServerReleases.dates[version].deprecationDate}} | {% octicon "x" aria-label="Not supported" %} | [{{version}} release notes](/enterprise-server@{{version}}/admin/release-notes) | [{{version}} documentation](/enterprise-server@{{version}}) | +{%- assign candidateDisplay = enterpriseServerReleases.dates[version].displayCandidateDate | default: enterpriseServerReleases.dates[version].releaseDate %} +{%- assign releaseDisplay = enterpriseServerReleases.dates[version].displayReleaseDate | default: enterpriseServerReleases.dates[version].releaseDate %} +| {{version}} | {{candidateDisplay}} | {{releaseDisplay}} | {{enterpriseServerReleases.dates[version].deprecationDate}} | {% octicon "x" aria-label="Not supported" %} | [{{version}} release notes](/enterprise-server@{{version}}/admin/release-notes) | [{{version}} docs](/enterprise-server@{{version}}) | {%- endfor %} {%- for version in enterpriseServerReleases.deprecatedReleasesWithLegacyFormat %} -| {{version}} | {{enterpriseServerReleases.dates[version].releaseDate}} | {{enterpriseServerReleases.dates[version].deprecationDate}} | {% octicon "x" aria-label="Not supported" %} | [{{version}} release notes](https://enterprise.github.com/releases/series/{{version}}) | [{{version}} documentation](/enterprise/{{version}}) | +{%- assign candidateDisplay = enterpriseServerReleases.dates[version].displayCandidateDate | default: enterpriseServerReleases.dates[version].releaseDate %} +{%- assign releaseDisplay = enterpriseServerReleases.dates[version].displayReleaseDate | default: enterpriseServerReleases.dates[version].releaseDate %} +| {{version}} | {{candidateDisplay}} | {{releaseDisplay}} | {{enterpriseServerReleases.dates[version].deprecationDate}} | {% octicon "x" aria-label="Not supported" %} | [{{version}} release notes](https://enterprise.github.com/releases/series/{{version}}) | [{{version}} docs](/enterprise/{{version}}) | {%- endfor %} ### Developer documentation that is {% data variables.release-phases.closing_down %} We hosted developer documentation for {% data variables.product.prodname_ghe_server %} on a separate site until the 2.17 release. We provide developer documentation for version 2.16 and earlier, but do not maintain or update the documentation. -| Version | Release | {% data variables.release-phases.closing_down_caps %} date | Developer documentation | -| :- | :- | :- | :- | +| Version | Candidate | Release | {% data variables.release-phases.closing_down_caps %} date | Developer docs | +| :- | :- | :- | :- | :- | {%- for version in enterpriseServerReleases.deprecatedReleasesOnDeveloperSite %} -| {{version}} | {{enterpriseServerReleases.dates[version].releaseDate}} | {{enterpriseServerReleases.dates[version].deprecationDate}} | [{{version}} developer documentation](https://developer.github.com/enterprise/{{version}}) | +{%- assign candidateDisplay = enterpriseServerReleases.dates[version].displayCandidateDate | default: enterpriseServerReleases.dates[version].releaseDate %} +{%- assign releaseDisplay = enterpriseServerReleases.dates[version].displayReleaseDate | default: enterpriseServerReleases.dates[version].releaseDate %} +| {{version}} | {{candidateDisplay}} | {{releaseDisplay}} | {{enterpriseServerReleases.dates[version].deprecationDate}} | [{{version}} developer docs](https://developer.github.com/enterprise/{{version}}) | {%- endfor %} ## Recommended {% data variables.product.prodname_codeql_cli %} versions for code scanning diff --git a/content/billing/concepts/enterprise-billing/usage-based-licenses.md b/content/billing/concepts/enterprise-billing/usage-based-licenses.md index b8eda07c9e7c..9bd763e12290 100644 --- a/content/billing/concepts/enterprise-billing/usage-based-licenses.md +++ b/content/billing/concepts/enterprise-billing/usage-based-licenses.md @@ -31,14 +31,28 @@ Usage-based billing for licenses provides flexibility and cost savings compared {% data reusables.billing.do-i-have-usage-based %} -{% data variables.visual_studio.prodname_vss_ghe %} is currently not supported for usage-based billing. - ## Can I use {% data variables.product.prodname_ghe_server %}? Although you can sync licenses with {% data variables.product.prodname_ghe_server %}, usage-based licensing is a cloud-first license model where users must first be added to an organization on {% data variables.product.prodname_ghe_cloud %}. For a detailed comparison between usage-based and volume licensing models, see [AUTOTITLE](/billing/concepts/enterprise-billing/combined-enterprise-use#about-licensing-models). +## Can I use a {% data variables.product.prodname_vs %} bundle? + +If you have a {% data variables.product.prodname_vs %} bundle with {% data variables.product.prodname_ghe_cloud %}, you can switch to usage-based billing by contacting your account manager or {% data variables.contact.contact_enterprise_sales %} ahead of contract renewal. + +Usage-based billing will apply to non-bundled licenses, categorized as "GitHub Enterprise licenses" on your enterprise's Licensing page. These licenses include: + +* Licenses for enterprise members who are not matched to a {% data variables.product.prodname_vs %} account. +* Any extra {% data variables.product.prodname_enterprise %} licenses you consume beyond the number of licenses purchased in your volume agreement. + +Bundled licenses ({% data variables.product.prodname_vs %} plus {% data variables.product.prodname_enterprise %}) **remain on a volume agreement**. + +Before switching to usage-based billing, to ensure you are not charged extra for {% data variables.product.prodname_vs %} users who should consume a bundled license: + +* Ensure all {% data variables.product.prodname_vs %} users are correctly matched to their account on {% data variables.product.github %}. See [AUTOTITLE](/enterprise-cloud@latest/billing/how-tos/set-up-payment/set-up-vs-subscription#reconciling-users-across-visual-studio-and-github). +* Add all {% data variables.product.prodname_vs %} users to your enterprise on **{% data variables.product.prodname_ghe_cloud %}** before adding them to {% data variables.product.prodname_ghe_server %}. Users who are only on {% data variables.product.prodname_ghe_server %} will consume a "{% data variables.product.prodname_enterprise %}" license once you switch to usage-based billing. + ## How are metered licenses measured? {% data reusables.billing.metered-license-measures %} diff --git a/content/billing/concepts/enterprise-billing/visual-studio-subs.md b/content/billing/concepts/enterprise-billing/visual-studio-subs.md index 6967be5824e0..b903ce5515dc 100644 --- a/content/billing/concepts/enterprise-billing/visual-studio-subs.md +++ b/content/billing/concepts/enterprise-billing/visual-studio-subs.md @@ -22,7 +22,7 @@ shortTitle: Visual Studio subs contentType: concepts --- -> [!NOTE] Usage-based billing is not currently supported for {% data variables.visual_studio.prodname_vss_ghe %}. +> [!NOTE] Customers with a {% data variables.product.prodname_vs %} bundle can **switch to usage-based billing** for {% data variables.product.prodname_enterprise %} licenses. This allows you to pay for licenses on a flexible monthly cycle for users who are not part of your {% data variables.product.prodname_vs %} subscription. See [AUTOTITLE](/billing/concepts/enterprise-billing/usage-based-licenses). {% data reusables.enterprise-accounts.vss-ghe-description %} {% data variables.visual_studio.prodname_vss_ghe %} is available from Microsoft under the terms of the Microsoft Enterprise Agreement. For more information, see [{% data variables.visual_studio.prodname_vss_ghe %}](https://visualstudio.microsoft.com/subscriptions/visual-studio-github/) on the {% data variables.product.prodname_vs %} website. @@ -46,7 +46,9 @@ For more information about {% data variables.product.prodname_enterprise %}, see {% data reusables.enterprise.ghe-includes-ghec-and-ghes %} For more information, see [AUTOTITLE](/admin/overview/about-github-for-enterprises#about-deployment-options). -For users only on {% data variables.product.prodname_ghe_server %}, each {% data variables.product.prodname_vs %} subscriber will only consume one license as long as the email address associated with their {% data variables.product.prodname_ghe_server %} account matches their {% data variables.product.prodname_vs %} UPN. For users on both {% data variables.product.prodname_ghe_server %} and {% data variables.product.prodname_ghe_cloud %}, only one license will be consumed as long as you follow the instructions in the 'About licenses for {% data variables.visual_studio.prodname_vss_ghec %}' section, and the user's accounts are linked as described in [AUTOTITLE](/billing/managing-your-license-for-github-enterprise/syncing-license-usage-between-github-enterprise-server-and-github-enterprise-cloud). +For users only on {% data variables.product.prodname_ghe_server %}, each {% data variables.product.prodname_vs %} subscriber will only consume one license as long as the email address associated with their {% data variables.product.prodname_ghe_server %} account matches their {% data variables.product.prodname_vs %} UPN. This does not apply if you have switched to **usage-based billing**, in which case users must also be on {% data variables.product.prodname_ghe_cloud %} to consume a bundled license. + +For users on both {% data variables.product.prodname_ghe_server %} and {% data variables.product.prodname_ghe_cloud %}, only one license will be consumed as long as you follow the instructions in the 'About licenses for {% data variables.visual_studio.prodname_vss_ghec %}' section, and the user's accounts are linked as described in [AUTOTITLE](/billing/managing-your-license-for-github-enterprise/syncing-license-usage-between-github-enterprise-server-and-github-enterprise-cloud). ## Further reading diff --git a/content/billing/how-tos/set-up-payment/set-up-vs-subscription.md b/content/billing/how-tos/set-up-payment/set-up-vs-subscription.md index 38d29c1174a6..d86a6c5adae5 100644 --- a/content/billing/how-tos/set-up-payment/set-up-vs-subscription.md +++ b/content/billing/how-tos/set-up-payment/set-up-vs-subscription.md @@ -16,6 +16,8 @@ shortTitle: Set up VS subscription contentType: how-tos --- +> [!NOTE] Customers with a {% data variables.product.prodname_vs %} bundle can **switch to usage-based billing** for {% data variables.product.prodname_enterprise %} licenses. This allows you to pay for licenses on a flexible monthly cycle for users who are not part of your {% data variables.product.prodname_vs %} subscription. See [AUTOTITLE](/billing/concepts/enterprise-billing/usage-based-licenses). + {% data reusables.enterprise-accounts.vss-ghe-description %} See [AUTOTITLE](/billing/managing-billing-for-your-products/managing-licenses-for-visual-studio-subscriptions-with-github-enterprise/about-visual-studio-subscriptions-with-github-enterprise). ## Prerequisites diff --git a/content/migrations/using-github-enterprise-importer/migrating-between-github-products/migrating-repositories-from-github-enterprise-server-to-github-enterprise-cloud.md b/content/migrations/using-github-enterprise-importer/migrating-between-github-products/migrating-repositories-from-github-enterprise-server-to-github-enterprise-cloud.md index 60529b6af56a..8a94d4385e2d 100644 --- a/content/migrations/using-github-enterprise-importer/migrating-between-github-products/migrating-repositories-from-github-enterprise-server-to-github-enterprise-cloud.md +++ b/content/migrations/using-github-enterprise-importer/migrating-between-github-products/migrating-repositories-from-github-enterprise-server-to-github-enterprise-cloud.md @@ -69,30 +69,41 @@ To migrate your repositories from {% data variables.product.prodname_ghe_server ## Step 3: Set up blob storage -{% data reusables.enterprise-migration-tool.blob-storage-intro %} +When performing a repository migration, you must store your repository data in a place that {% data variables.product.prodname_importer_proper_name %} can access. This can be accomplished by: + +* Using local storage on the GHES instance (GHES **3.16** and later) +* Using a blob storage provider + +### Using local storage (GHES 3.16+) + +{% data reusables.enterprise-migration-tool.local-storage-steps %} + +### Using a blob storage provider + +If your {% data variables.product.prodname_ghe_server %} instance is behind a firewall, you may need to set up blob storage with an external cloud service. + +First, you must set up blob storage with a supported provider. Then, if you're using a cloud provider, you must configure your credentials for the storage provider in the {% data variables.enterprise.management_console %} or {% data variables.product.prodname_cli %}. + +{% data reusables.enterprise-migration-tool.supported-blob-storage-providers %} > [!NOTE] > You only need to configure blob storage if you use {% data variables.product.prodname_ghe_server %} versions 3.8 or higher. If you use {% data variables.product.prodname_ghe_server %} versions 3.7 or lower, skip to [Step 4: Set up a migration source in {% data variables.product.prodname_ghe_cloud %}](#step-4-set-up-a-migration-source-in-github-enterprise-cloud). > > Blob storage is required to migrate repositories with large Git source or metadata. If you use {% data variables.product.prodname_ghe_server %} versions 3.7 or lower, you will not be able to perform migrations where your Git source or metadata exports exceed 2GB. To perform these migrations, update to {% data variables.product.prodname_ghe_server %} versions 3.8 or higher. -### Setting up an AWS S3 storage bucket +#### Setting up an AWS S3 storage bucket {% data reusables.enterprise-migration-tool.set-up-aws-bucket %} -### Setting up an Azure Blob Storage account +#### Setting up an Azure Blob Storage account {% data reusables.enterprise-migration-tool.set-up-azure-storage-account %} -### Using local storage (GHES 3.16+) - -{% data reusables.enterprise-migration-tool.local-storage-steps %} - -### Configuring blob storage in the {% data variables.enterprise.management_console %} of {% data variables.location.product_location_enterprise %} +#### Configuring blob storage in the {% data variables.enterprise.management_console %} of {% data variables.location.product_location_enterprise %} {% data reusables.enterprise-migration-tool.blob-storage-management-console %} -### Allowing network access +#### Allowing network access If you have configured firewall rules on your storage account, ensure you have allowed access to the IP ranges for your migration destination. See [AUTOTITLE](/migrations/using-github-enterprise-importer/migrating-between-github-products/managing-access-for-a-migration-between-github-products#configuring-ip-allow-lists-for-migrations). @@ -239,6 +250,78 @@ If you're using {% data variables.product.prodname_ghe_server %} 3.8 or higher, You may need to allowlist {% data variables.product.company_short %}'s IP ranges. For more information, see [AUTOTITLE](/migrations/using-github-enterprise-importer/migrating-between-github-products/managing-access-for-a-migration-between-github-products#configuring-ip-allow-lists-for-migrations). +### Uploading your migration archives to {% data variables.product.prodname_ghos %} + +> [!NOTE] +> Repository migrations with {% data variables.product.prodname_ghos %} are currently in {% data variables.release-phases.public_preview %} and subject to change. + +If you're using {% data variables.product.prodname_ghos %}, you will upload your archive to {% data variables.product.prodname_ghos %} using the following process: + +1. Create a multipart upload by submitting a POST request +1. Upload archive in multiple parts up to 100MB in size as PATCH requests +1. Submit a PUT request to complete the upload + +#### 1. Create the multipart upload + +You will submit a POST request to: + +```http +https://uploads.github.com/organizations/{organization_id}/gei/archive/blobs/uploads +``` + +Include a JSON body like below with the archive name and size. The content type can remain as `"application/octet-stream"` for all uploads. + +```json +{ +"content_type": "application/octet-stream", +"name": "git-archive.tar.gz", +"size": 262144000 +} +``` + +This will return a JSON object response as follows: + +```json +{ + "guid": "363b2659-b8a3-4878-bfff-eed4bcb54d35", + "node_id": "MA_kgDaACQzNjNiMjY1OS1iOGEzLTQ4NzgtYmZmZi1lZWQ0YmNiNTRkMzU", + "name": "git-archive.tar.gz", + "size": 33287, + "uri": "gei://archive/363b2659-b8a3-4878-bfff-eed4bcb54d35", + "created_at": "2024-11-13T12:35:45.761-08:00" +} +``` + +This URI represents the uploaded archive, and will be used to enqueue migration when you start your repository migration. The response will also include the location in the response header used to upload file parts using a PATCH request in the next step: + +```http +/organizations/{organization_id}/gei/archive/blobs/uploads?part_number=1&guid=&upload_id= +``` + +#### 2. Upload the archive in multiple parts + +Upload up to 100 MB of your file in each part with a PATCH request using the location from the previous response header, ensuring that the raw data is uploaded in the request body without using a multipart form. If the final part of your file is less than 100 MB, upload only the remaining bytes in that last request: + +```http +https://uploads.github.com/{location} +``` + +This will return an empty response body with the following location in the response header: + +```http +/organizations/{organization_id}/gei/archive/blobs/uploads?part_number=2&guid=&upload_id= +``` + +Repeat this until the upload is complete. Ensure that you are reading up to 100 MB of the file at a time, and submitting requests to the new location values with the incremented `part_number` values. + +#### 3. Complete the upload + +Submit a PUT request to the "last path" value from the previous step with an empty body and your upload to GitHub-owned storage is complete. The GEI URI can be constructed with the GUID from this initial POST request in the following format: + +```http +gei://archive/{guid} +``` + ## Step 7: Start your repository migration {% data reusables.enterprise-migration-tool.start-repository-migration-ec %} @@ -328,6 +411,19 @@ For {% data variables.product.pat_generic %} requirements, see [AUTOTITLE](/migr ## Step 4: Set up blob storage +### Migrating repositories with {% data variables.product.prodname_ghos %} + +> [!NOTE] +> Repository migrations with {% data variables.product.prodname_ghos %} are currently in {% data variables.release-phases.public_preview %} and subject to change. + +If you do not want to set up and provide {% data variables.product.prodname_importer_proper_name %} with access to a customer-owned blob storage account for storing your repository archives, you can migrate repositories using {% data variables.product.prodname_ghos %}. To do so, you must be running v1.9.0 (or higher) of {% data variables.product.prodname_gei_cli %}. {% data variables.product.prodname_ghos %} does not require additional setup and is available as an option when you run {% data variables.product.prodname_gei_cli %} commands. + +For security purposes, {% data variables.product.prodname_ghos %} is explicitly write-only, and downloads from {% data variables.product.prodname_ghos %} are not possible. After a migration is complete, the repository archives are immediately deleted. If an archive is uploaded and not used in a migration, the archive is deleted after 7 days. + +When you use the CLI flag for {% data variables.product.prodname_ghos %}, the repository archive is automatically exported to the destination configured in the Management Console, uploaded to GitHub-owned storage, and imported to your migration destination. When using {% data variables.product.prodname_ghos %} we recommend configuring local storage. See [Using local storage (GHES 3.16+)](#using-local-storage-ghes-316-1). + +### Migrating repositories with customer-owned blob storage + {% data reusables.enterprise-migration-tool.blob-storage-intro %} ### Setting up an AWS S3 storage bucket @@ -419,6 +515,7 @@ gh gei generate-script --github-source-org SOURCE \ | `--target-api-url TARGET-API-URL` | {% data reusables.enterprise-migration-tool.add-target-api-url %} | | `--no-ssl-verify` | {% data reusables.enterprise-migration-tool.ssl-flag %} | | `--download-migration-logs` | Download the migration log for each migrated repository. For more information about migration logs, see [AUTOTITLE](/migrations/using-github-enterprise-importer/completing-your-migration-with-github-enterprise-importer/accessing-your-migration-logs-for-github-enterprise-importer#downloading-all-migration-logs-for-an-organization). | +| `--use-github-storage`| Perform a repository migration using {% data variables.product.prodname_ghos %} as the intermediate blob storage solution. | ### Reviewing the migration script @@ -495,6 +592,7 @@ gh gei migrate-repo --github-source-org SOURCE --source-repo CURRENT-NAME --gith | `--no-ssl-verify` | {% data reusables.enterprise-migration-tool.ssl-flag %} | | `--skip-releases` | {% data reusables.enterprise-migration-tool.skip-releases %} | | `--target-repo-visibility TARGET-VISIBILITY` | {% data reusables.enterprise-migration-tool.set-repository-visibility %} | +| `--use-github-storage`| Perform a repository migration using {% data variables.product.prodname_ghos %} as the intermediate blob storage solution. | #### Aborting the migration diff --git a/content/migrations/using-github-enterprise-importer/migrating-from-bitbucket-server-to-github-enterprise-cloud/migrating-repositories-from-bitbucket-server-to-github-enterprise-cloud.md b/content/migrations/using-github-enterprise-importer/migrating-from-bitbucket-server-to-github-enterprise-cloud/migrating-repositories-from-bitbucket-server-to-github-enterprise-cloud.md index c1919dd92691..4593eeec10fc 100644 --- a/content/migrations/using-github-enterprise-importer/migrating-from-bitbucket-server-to-github-enterprise-cloud/migrating-repositories-from-bitbucket-server-to-github-enterprise-cloud.md +++ b/content/migrations/using-github-enterprise-importer/migrating-from-bitbucket-server-to-github-enterprise-cloud/migrating-repositories-from-bitbucket-server-to-github-enterprise-cloud.md @@ -94,6 +94,15 @@ You will first generate an archive of the data you want to migrate and push the Before you can run a migration, you need to set up a storage container with your chosen cloud provider to store your data. +### Using {% data variables.product.prodname_ghos %} + +> [!NOTE] +> Repository migrations with {% data variables.product.prodname_ghos %} are currently in {% data variables.release-phases.public_preview %} and subject to change. + +If you do not want to set up and provide {% data variables.product.prodname_importer_proper_name %} with access to a blob storage account behind your firewall, you can migrate repositories with {% data variables.product.prodname_ghos %} using the `--use-github-storage` flag. To do so, you must be running v1.9.0 (or higher) of {% data variables.product.prodname_bbs2gh_cli %}. + +For security purposes, {% data variables.product.prodname_ghos %} is explicitly write-only, and downloads from {% data variables.product.prodname_ghos %} are not possible. After a migration is complete, the repository archives are immediately deleted. If an archive is uploaded and not used in a migration, the archive is deleted after 7 days. + ### Setting up an AWS S3 storage bucket {% data reusables.enterprise-migration-tool.set-up-aws-bucket %} @@ -143,10 +152,12 @@ gh bbs2gh migrate-repo --bbs-server-url BBS-SERVER-URL \ --ssh-user SSH-USER --ssh-private-key PATH-TO-KEY # If your Bitbucket Server instance runs on Windows: --smb-user SMB-USER - # If you're using AWS S3 as your blob storage provider: + # If you are using AWS S3 as your blob storage provider: --aws-bucket-name AWS-BUCKET-NAME # If you are running a Bitbucket Data Center cluster or your Bitbucket Server is behind a load balancer: --archive-download-host ARCHIVE-DOWNLOAD-HOST + # If you are using GitHub owned blob storage: + --use-github-storage ``` {% data reusables.enterprise-migration-tool.placeholder-table %} @@ -208,7 +219,7 @@ gh bbs2gh migrate-repo --archive-path ARCHIVE-PATH \ --bbs-server-url BBS-SERVER-URL \ --bbs-project PROJECT \ --bbs-repo CURRENT-NAME \ - # If you're using AWS S3 as your blob storage provider: + # If you are using AWS S3 as your blob storage provider: --aws-bucket-name AWS-BUCKET-NAME # If you are migrating to {% data variables.enterprise.data_residency_site %}: --target-api-url TARGET-API-URL @@ -258,6 +269,8 @@ gh bbs2gh generate-script --bbs-server-url BBS-SERVER-URL \ --smb-user SMB-USER # If you are running a Bitbucket Data Center cluster or your Bitbucket Server is behind a load balancer: --archive-download-host ARCHIVE-DOWNLOAD-HOST + # If you are using GitHub owned blob storage: + --use-github-storage ``` {% data reusables.enterprise-migration-tool.download-migration-logs-flag %} diff --git a/data/reusables/enterprise-migration-tool/blob-storage-intro.md b/data/reusables/enterprise-migration-tool/blob-storage-intro.md index c59e6eb65b82..ccca8f1c44fc 100644 --- a/data/reusables/enterprise-migration-tool/blob-storage-intro.md +++ b/data/reusables/enterprise-migration-tool/blob-storage-intro.md @@ -3,4 +3,5 @@ You must store your repository data in a place that {% data variables.product.pr First, you must set up blob storage with a supported provider. Then, if you're using a cloud provider, you must configure your credentials for the storage provider in the {% data variables.enterprise.management_console %} or {% data variables.product.prodname_cli %}. {% data reusables.enterprise-migration-tool.supported-blob-storage-providers %} -* Local storage on the GHES instance (GHES **3.16** and later) + +* Local storage on the GHES instance (GHES **3.16** and later). We recommend using this option with {% data variables.product.prodname_ghos %}. diff --git a/data/reusables/enterprise-migration-tool/local-storage-steps.md b/data/reusables/enterprise-migration-tool/local-storage-steps.md index 6a1288212e45..ecf71d2bdfe6 100644 --- a/data/reusables/enterprise-migration-tool/local-storage-steps.md +++ b/data/reusables/enterprise-migration-tool/local-storage-steps.md @@ -1,4 +1,7 @@ -When you run a migration with local storage, archive data is written to the disk on {% data variables.location.product_location_enterprise %}, without the need for a cloud storage provider. {% data variables.product.prodname_importer_proper_name %} will automatically retrieve the stored archive from {% data variables.product.prodname_ghe_server %}, unless you have blocked egress traffic from {% data variables.product.prodname_ghe_server %}. +When you run a migration with local storage, archive data is written to the disk on {% data variables.location.product_location_enterprise %}, without the need for a cloud storage provider. + +* If you do not have firewall rules blocking egress traffic from {% data variables.product.prodname_ghe_server %}, {% data variables.product.prodname_importer_proper_name %} can automatically retrieve the stored archive from {% data variables.product.prodname_ghe_server %}. +* If you do have firewall rules in place and don't want to allow access to {% data variables.product.prodname_importer_proper_name %}, you can upload your archive data to {% data variables.product.prodname_ghos %} for {% data variables.product.prodname_importer_proper_name %} to access. To do so manually, see [Uploading your migration archives to GitHub-owned blob storage](/migrations/using-github-enterprise-importer/migrating-between-github-products/migrating-repositories-from-github-enterprise-server-to-github-enterprise-cloud?tool=api#uploading-your-migration-archives-to-github-owned-blob-storage) in the API version of this article. 1. From an administrative account on {% data variables.product.prodname_ghe_server %}, in the upper-right corner of any page, click {% octicon "rocket" aria-label="Site admin" %}. {% data reusables.enterprise_site_admin_settings.management-console %} diff --git a/data/variables/product.yml b/data/variables/product.yml index dcde2a2ab085..4562b039fb46 100644 --- a/data/variables/product.yml +++ b/data/variables/product.yml @@ -51,6 +51,7 @@ prodname_ado2gh_cli_short: ADO2GH extension prodname_bbs2gh: BBS2GH prodname_bbs2gh_cli: BBS2GH extension of the GitHub CLI prodname_bbs2gh_cli_short: BBS2GH extension +prodname_ghos: GitHub-owned blob storage # GitHub Education prodname_education: 'GitHub Education' diff --git a/src/ghes-releases/lib/enterprise-dates.json b/src/ghes-releases/lib/enterprise-dates.json index 7a5922930759..0c014cc79155 100644 --- a/src/ghes-releases/lib/enterprise-dates.json +++ b/src/ghes-releases/lib/enterprise-dates.json @@ -1,194 +1,290 @@ { "11.10": { "releaseDate": "2014-06-09", - "deprecationDate": "2015-07-07" + "deprecationDate": "2015-07-07", + "releaseCandidateDate": "2014-06-09", + "generalAvailabilityDate": "2014-07-07" }, "2.0": { "releaseDate": "2014-10-14", - "deprecationDate": "2016-02-09" + "deprecationDate": "2016-02-09", + "releaseCandidateDate": "2014-10-14", + "generalAvailabilityDate": "2014-11-11" }, "2.1": { "releaseDate": "2014-12-23", - "deprecationDate": "2016-04-26" + "deprecationDate": "2016-04-26", + "releaseCandidateDate": "2014-12-23", + "generalAvailabilityDate": "2015-01-20" }, "2.2": { "releaseDate": "2015-04-01", - "deprecationDate": "2016-08-03" + "deprecationDate": "2016-08-03", + "releaseCandidateDate": "2015-04-01", + "generalAvailabilityDate": "2015-04-29" }, "2.3": { "releaseDate": "2015-07-06", - "deprecationDate": "2016-11-01" + "deprecationDate": "2016-11-01", + "releaseCandidateDate": "2015-07-06", + "generalAvailabilityDate": "2015-08-03" }, "2.4": { "releaseDate": "2015-09-15", - "deprecationDate": "2017-02-09" + "deprecationDate": "2017-02-09", + "releaseCandidateDate": "2015-09-15", + "generalAvailabilityDate": "2015-10-13" }, "2.5": { "releaseDate": "2016-01-12", - "deprecationDate": "2017-03-14" + "deprecationDate": "2017-03-14", + "releaseCandidateDate": "2016-01-12", + "generalAvailabilityDate": "2016-02-09" }, "2.6": { "releaseDate": "2016-03-29", - "deprecationDate": "2017-04-26" + "deprecationDate": "2017-04-26", + "releaseCandidateDate": "2016-03-29", + "generalAvailabilityDate": "2016-04-26" }, "2.7": { "releaseDate": "2016-07-06", - "deprecationDate": "2017-08-03" + "deprecationDate": "2017-08-03", + "releaseCandidateDate": "2016-07-06", + "generalAvailabilityDate": "2016-08-03" }, "2.8": { "releaseDate": "2016-10-12", - "deprecationDate": "2017-11-09" + "deprecationDate": "2017-11-09", + "releaseCandidateDate": "2016-10-12", + "generalAvailabilityDate": "2016-11-09" }, "2.9": { "releaseDate": "2017-02-01", - "deprecationDate": "2018-03-01" + "deprecationDate": "2018-03-01", + "releaseCandidateDate": "2017-02-01", + "generalAvailabilityDate": "2017-03-01" }, "2.10": { "releaseDate": "2017-05-08", - "deprecationDate": "2018-06-05" + "deprecationDate": "2018-06-05", + "releaseCandidateDate": "2017-05-08", + "generalAvailabilityDate": "2017-06-05" }, "2.11": { "releaseDate": "2017-08-16", - "deprecationDate": "2018-09-13" + "deprecationDate": "2018-09-13", + "releaseCandidateDate": "2017-08-16", + "generalAvailabilityDate": "2017-09-13" }, "2.12": { "releaseDate": "2017-11-14", - "deprecationDate": "2018-12-12" + "deprecationDate": "2018-12-12", + "releaseCandidateDate": "2017-11-14", + "generalAvailabilityDate": "2017-12-12" }, "2.13": { "releaseDate": "2018-02-27", - "deprecationDate": "2019-03-27" + "deprecationDate": "2019-03-27", + "releaseCandidateDate": "2018-02-27", + "generalAvailabilityDate": "2018-03-27" }, "2.14": { "releaseDate": "2018-06-14", - "deprecationDate": "2019-07-12" + "deprecationDate": "2019-07-12", + "releaseCandidateDate": "2018-06-14", + "generalAvailabilityDate": "2018-07-12" }, "2.15": { "releaseDate": "2018-09-18", - "deprecationDate": "2019-10-16" + "deprecationDate": "2019-10-16", + "releaseCandidateDate": "2018-09-18", + "generalAvailabilityDate": "2018-10-16" }, "2.16": { "releaseDate": "2018-12-25", - "deprecationDate": "2020-01-22" + "deprecationDate": "2020-01-22", + "releaseCandidateDate": "2018-12-25", + "generalAvailabilityDate": "2019-01-22" }, "2.17": { "releaseDate": "2019-04-25", - "deprecationDate": "2020-05-23" + "deprecationDate": "2020-05-23", + "releaseCandidateDate": "2019-04-25", + "generalAvailabilityDate": "2019-05-23" }, "2.18": { "releaseDate": "2019-07-23", - "deprecationDate": "2020-08-20" + "deprecationDate": "2020-08-20", + "releaseCandidateDate": "2019-07-23", + "generalAvailabilityDate": "2019-08-20" }, "2.19": { "releaseDate": "2019-10-15", - "deprecationDate": "2020-11-12" + "deprecationDate": "2020-11-12", + "releaseCandidateDate": "2019-10-15", + "generalAvailabilityDate": "2019-11-12" }, "2.20": { "releaseDate": "2020-01-14", - "deprecationDate": "2021-03-02" + "deprecationDate": "2021-03-02", + "releaseCandidateDate": "2020-01-14", + "generalAvailabilityDate": "2020-02-11" }, "2.21": { "releaseDate": "2020-05-12", - "deprecationDate": "2021-06-09" + "deprecationDate": "2021-06-09", + "releaseCandidateDate": "2020-05-12", + "generalAvailabilityDate": "2020-06-09" }, "2.22": { "releaseDate": "2020-08-26", - "deprecationDate": "2021-09-23" + "deprecationDate": "2021-09-23", + "releaseCandidateDate": "2020-08-26", + "generalAvailabilityDate": "2020-09-23" }, "3.0": { "releaseDate": "2021-01-12", - "deprecationDate": "2022-02-16" + "deprecationDate": "2022-02-16", + "releaseCandidateDate": "2021-01-12", + "generalAvailabilityDate": "2021-02-16" }, "3.1": { "releaseDate": "2021-05-06", - "deprecationDate": "2022-06-30" + "deprecationDate": "2022-06-30", + "releaseCandidateDate": "2021-05-06", + "generalAvailabilityDate": "2021-06-03" }, "3.2": { "releaseDate": "2021-09-09", - "deprecationDate": "2022-10-12" + "deprecationDate": "2022-10-12", + "releaseCandidateDate": "2021-09-09", + "generalAvailabilityDate": "2021-09-28" }, "3.3": { "releaseDate": "2021-11-09", - "deprecationDate": "2023-01-18" + "deprecationDate": "2023-01-18", + "releaseCandidateDate": "2021-11-09", + "generalAvailabilityDate": "2021-12-07" }, "3.4": { "releaseDate": "2022-02-15", - "deprecationDate": "2023-03-23" + "deprecationDate": "2023-03-23", + "releaseCandidateDate": "2022-02-15", + "generalAvailabilityDate": "2022-03-15" }, "3.5": { "releaseDate": "2022-05-10", - "deprecationDate": "2023-06-29" + "deprecationDate": "2023-06-29", + "releaseCandidateDate": "2022-05-10", + "generalAvailabilityDate": "2022-05-31" }, "3.6": { "releaseDate": "2022-07-26", - "deprecationDate": "2023-09-25" + "deprecationDate": "2023-09-25", + "releaseCandidateDate": "2022-07-26", + "generalAvailabilityDate": "2022-08-16" }, "3.7": { "releaseDate": "2022-10-25", - "deprecationDate": "2024-01-04" + "deprecationDate": "2024-01-04", + "releaseCandidateDate": "2022-10-25", + "generalAvailabilityDate": "2022-11-08" }, "3.8": { "releaseDate": "2023-02-07", - "deprecationDate": "2024-03-26" + "deprecationDate": "2024-03-26", + "releaseCandidateDate": "2023-02-07", + "generalAvailabilityDate": "2023-03-07" }, "3.9": { "releaseDate": "2023-06-08", - "deprecationDate": "2024-07-26" + "deprecationDate": "2024-07-26", + "releaseCandidateDate": "2023-06-08", + "generalAvailabilityDate": "2023-06-29" }, "3.10": { "releaseDate": "2023-08-08", - "deprecationDate": "2024-09-25" + "deprecationDate": "2024-09-25", + "releaseCandidateDate": "2023-08-08", + "generalAvailabilityDate": "2023-08-29" }, "3.11": { "releaseDate": "2023-11-14", - "deprecationDate": "2024-12-19" + "deprecationDate": "2024-12-19", + "releaseCandidateDate": "2023-11-14", + "generalAvailabilityDate": "2023-12-05" }, "3.12": { "releaseDate": "2024-02-13", - "deprecationDate": "2025-04-03" + "deprecationDate": "2025-04-03", + "releaseCandidateDate": "2024-02-13", + "generalAvailabilityDate": "2024-03-05" }, "3.13": { "releaseDate": "2024-05-16", - "deprecationDate": "2025-06-19" + "deprecationDate": "2025-06-19", + "releaseCandidateDate": "2024-05-16", + "generalAvailabilityDate": "2024-06-18" }, "3.14": { "releaseDate": "2024-08-06", - "deprecationDate": "2025-08-27" + "deprecationDate": "2025-08-27", + "releaseCandidateDate": "2024-08-06", + "generalAvailabilityDate": "2024-08-27" }, "3.15": { "releaseDate": "2024-11-12", - "deprecationDate": "2025-12-19" + "deprecationDate": "2025-12-19", + "releaseCandidateDate": "2024-11-12", + "generalAvailabilityDate": "2024-12-03" }, "3.16": { "releaseDate": "2025-02-25", - "deprecationDate": "2026-03-11" + "deprecationDate": "2026-03-11", + "releaseCandidateDate": "2025-02-25", + "generalAvailabilityDate": "2025-03-11" }, "3.17": { "releaseDate": "2025-05-13", - "deprecationDate": "2026-06-03" + "deprecationDate": "2026-06-03", + "releaseCandidateDate": "2025-05-13", + "generalAvailabilityDate": "2025-06-03" }, "3.18": { "releaseDate": "2025-08-05", - "deprecationDate": "2026-08-26" + "deprecationDate": "2026-08-26", + "releaseCandidateDate": "2025-08-05", + "generalAvailabilityDate": "2025-08-26" }, "3.19": { "releaseDate": "2025-11-11", - "deprecationDate": "2026-12-09" + "deprecationDate": "2026-12-09", + "releaseCandidateDate": "2025-11-11", + "generalAvailabilityDate": "2025-12-09" }, "3.20": { "releaseDate": "2026-02-17", - "deprecationDate": "2027-03-17" + "deprecationDate": "2027-03-17", + "releaseCandidateDate": "2026-02-17", + "generalAvailabilityDate": "2026-03-17" }, "3.21": { "releaseDate": "2026-05-12", - "deprecationDate": "2027-06-02" + "deprecationDate": "2027-06-02", + "releaseCandidateDate": "2026-05-12", + "generalAvailabilityDate": "2026-06-02" }, "3.22": { "releaseDate": "2026-08-04", - "deprecationDate": "2027-08-25" + "deprecationDate": "2027-08-25", + "releaseCandidateDate": "2026-08-04", + "generalAvailabilityDate": "2026-08-25" }, "3.23": { "releaseDate": "2026-11-10", - "deprecationDate": "2027-12-08" + "deprecationDate": "2027-12-08", + "releaseCandidateDate": "2026-11-10", + "generalAvailabilityDate": "2026-12-08" } } \ No newline at end of file diff --git a/src/ghes-releases/scripts/update-enterprise-dates.ts b/src/ghes-releases/scripts/update-enterprise-dates.ts index 8d00388d666a..e468f8a79bc4 100644 --- a/src/ghes-releases/scripts/update-enterprise-dates.ts +++ b/src/ghes-releases/scripts/update-enterprise-dates.ts @@ -13,8 +13,10 @@ import { getContents } from '@/workflows/git-utils' interface EnterpriseDates { [releaseNumber: string]: { - releaseDate: string + releaseDate: string // For backward compatibility - RC date initially, then GA date once available deprecationDate: string + releaseCandidateDate?: string // Release Candidate date + generalAvailabilityDate?: string // General Availability date } } @@ -54,8 +56,11 @@ async function main(): Promise { const formattedDates: EnterpriseDates = {} Object.entries(rawDates).forEach(([releaseNumber, releaseObject]) => { formattedDates[releaseNumber] = { + // For backward compatibility, keep releaseDate as RC date initially, then GA date once available releaseDate: releaseObject.release_candidate || releaseObject.start, deprecationDate: releaseObject.end, + releaseCandidateDate: releaseObject.release_candidate, + generalAvailabilityDate: releaseObject.start, } }) diff --git a/src/versions/lib/enterprise-server-releases.d.ts b/src/versions/lib/enterprise-server-releases.d.ts index 7e399535a246..e094961c96cb 100644 --- a/src/versions/lib/enterprise-server-releases.d.ts +++ b/src/versions/lib/enterprise-server-releases.d.ts @@ -1,54 +1,65 @@ +// ============================================================================ +// TYPE DEFINITIONS +// ============================================================================ + type Dates = { [key: string]: { - releaseDate: string + releaseDate: string // For backward compatibility - will be RC date initially, then GA date once available deprecationDate: string + releaseCandidateDate?: string // Release Candidate date + generalAvailabilityDate?: string // General Availability date + displayCandidateDate?: string | null // Computed: RC date if in past, null if future + displayReleaseDate?: string | null // Computed: GA date if in past, null if future } } -export const dates: Dates +// ============================================================================ +// STATICALLY DEFINED VALUES +// ============================================================================ export const next: string - export const nextNext: string - export const supported: string[] - export const releaseCandidate: null | string - export const deprecatedWithFunctionalRedirects: string[] - export const deprecated: string[] - export const legacyAssetVersions: string[] - export const firstReleaseStoredInBlobStorage: string +export const firstVersionDeprecatedOnNewSite: string +export const lastVersionWithoutArchivedRedirectsFile: string +export const lastReleaseWithLegacyFormat: string +export const firstReleaseNote: string +export const firstRestoredAdminGuides: string + +// ============================================================================ +// COMPUTED VALUES +// ============================================================================ export const all: string[] export const latest: string export const latestStable: string export const oldestSupported: string +export const dates: Dates export const nextDeprecationDate: string export const isOldestReleaseDeprecated: boolean export const deprecatedOnNewSite: string[] - -export const firstVersionDeprecatedOnNewSite: string -export const lastVersionWithoutArchivedRedirectsFile: string -export const lastReleaseWithLegacyFormat: string export const deprecatedReleasesWithLegacyFormat: string[] - export const deprecatedReleasesWithNewFormat: string[] - export const deprecatedReleasesOnDeveloperSite: string[] -export const firstReleaseNote: string -export const firstRestoredAdminGuides: string +// ============================================================================ +// HELPER FUNCTIONS +// ============================================================================ export declare function findReleaseNumberIndex(releaseNum: string): number export declare function getNextReleaseNumber(releaseNum: string): string export declare function getPreviousReleaseNumber(releaseNum: string): string +// ============================================================================ +// DEFAULT EXPORT +// ============================================================================ + const allExports = { - dates, next, nextNext, supported, @@ -57,21 +68,22 @@ const allExports = { deprecated, legacyAssetVersions, firstReleaseStoredInBlobStorage, + firstVersionDeprecatedOnNewSite, + lastVersionWithoutArchivedRedirectsFile, + lastReleaseWithLegacyFormat, + firstReleaseNote, + firstRestoredAdminGuides, all, latest, latestStable, oldestSupported, + dates, nextDeprecationDate, isOldestReleaseDeprecated, deprecatedOnNewSite, - firstVersionDeprecatedOnNewSite, - lastVersionWithoutArchivedRedirectsFile, - lastReleaseWithLegacyFormat, deprecatedReleasesWithLegacyFormat, deprecatedReleasesWithNewFormat, deprecatedReleasesOnDeveloperSite, - firstReleaseNote, - firstRestoredAdminGuides, findReleaseNumberIndex, getNextReleaseNumber, getPreviousReleaseNumber, diff --git a/src/versions/lib/enterprise-server-releases.js b/src/versions/lib/enterprise-server-releases.js index 6706736c6db0..eae8d6f37bdc 100644 --- a/src/versions/lib/enterprise-server-releases.js +++ b/src/versions/lib/enterprise-server-releases.js @@ -3,43 +3,24 @@ import semver from 'semver' import versionSatisfiesRange from './version-satisfies-range' -export const dates = JSON.parse(await fs.readFile('src/ghes-releases/lib/enterprise-dates.json')) +const rawDates = JSON.parse(await fs.readFile('src/ghes-releases/lib/enterprise-dates.json')) -// GHES Release Lifecycle Dates: -// enterprise-releases/docs/supported-versions.md#release-lifecycle-dates +// ============================================================================ +// STATICALLY DEFINED VALUES +// ============================================================================ -// Some frontmatter may contain the upcoming GHES release number +// Upcoming GHES release numbers (used in frontmatter and release planning) export const next = '3.18' export const nextNext = '3.19' +// Currently supported GHES versions (in descending order, latest first) export const supported = ['3.17', '3.16', '3.15', '3.14'] -// Edit this to `null` when it's no longer the release candidate +// Set to version number when in RC phase, null when no RC is active export const releaseCandidate = null -// Ensure that: -// "next" is ahead of "latest" by one minor or major release. -// "nextNext" is ahead of "next" by one minor or major release. -isValidNext(supported[0], next) -isValidNext(next, nextNext) - -function isValidNext(v1, v2) { - const semverV1 = semver.coerce(v1).raw - const semverV2 = semver.coerce(v2).raw - const isValid = - semverV2 === semver.inc(semverV1, 'minor') || semverV2 === semver.inc(semverV1, 'major') - if (!isValid) - throw new Error(`The version "${v2}" is not one version ahead of "${v1}" as expected`) -} - -// This indicates the point where we started treating redirect lookups -// to be a *function* rather than a big *lookup object*. -// This is important distinguish because we need to leverage that -// when dealing with redirects specifically in these archived -// enterprise versions. -// When you're archiving a version, add the new archived number to this -// array and you should never need to touch the `deprecated` array -// on the line just below. +// Deprecated versions with functional redirect handling (3.0+) +// When archiving a new version, add it here and update the archival process export const deprecatedWithFunctionalRedirects = [ '3.13', '3.12', @@ -56,6 +37,8 @@ export const deprecatedWithFunctionalRedirects = [ '3.1', '3.0', ] + +// All deprecated versions (combines functional + legacy redirect handling) export const deprecated = [ ...deprecatedWithFunctionalRedirects, '2.22', @@ -83,52 +66,115 @@ export const deprecated = [ '2.0', '11.10.340', ] + +// Versions with legacy asset handling (stored in separate repos before blob storage) export const legacyAssetVersions = ['3.0', '2.22', '2.21'] -// As of GHES 3.2, we started storing the scraped assets and html -// in Azure blob storage. All enterprise deprecated veresions are -// now stored in individual docs-ghes- repos. This -// release number now indicates a change in the way the archived html -// references assets. +// Historical milestones that mark feature/process changes export const firstReleaseStoredInBlobStorage = '3.2' +export const firstVersionDeprecatedOnNewSite = '2.13' +export const lastVersionWithoutArchivedRedirectsFile = '2.17' +export const lastReleaseWithLegacyFormat = '2.18' // Last to use /enterprise//... paths +export const firstReleaseNote = '2.20' +export const firstRestoredAdminGuides = '2.21' + +// ============================================================================ +// COMPUTED VALUES +// ============================================================================ +// All versions (supported + deprecated) export const all = supported.concat(deprecated) + +// Latest and stable version helpers export const latest = supported[0] export const latestStable = releaseCandidate ? supported[1] : latest export const oldestSupported = supported[supported.length - 1] + +// Enhanced dates object with computed display values for templates +export const dates = Object.fromEntries( + Object.entries(rawDates).map(([version, versionData]) => [ + version, + { + ...versionData, + displayCandidateDate: processDateForDisplay(versionData.releaseCandidateDate), + displayReleaseDate: processDateForDisplay(versionData.generalAvailabilityDate), + }, + ]), +) + +// Deprecation tracking export const nextDeprecationDate = dates[oldestSupported].deprecationDate export const isOldestReleaseDeprecated = new Date() > new Date(nextDeprecationDate) + +// Filtered version arrays for different use cases export const deprecatedOnNewSite = deprecated.filter((version) => versionSatisfiesRange(version, '>=2.13'), ) -export const firstVersionDeprecatedOnNewSite = '2.13' -// starting from 2.18, we updated the archival script to create a redirects.json top-level file in the archived repo -export const lastVersionWithoutArchivedRedirectsFile = '2.17' -// last version using paths like /enterprise/////
-// instead of /enterprise-server@///
-export const lastReleaseWithLegacyFormat = '2.18' + export const deprecatedReleasesWithLegacyFormat = deprecated.filter((version) => versionSatisfiesRange(version, '<=2.18'), ) + export const deprecatedReleasesWithNewFormat = deprecated.filter((version) => versionSatisfiesRange(version, '>2.18'), ) + export const deprecatedReleasesOnDeveloperSite = deprecated.filter((version) => versionSatisfiesRange(version, '<=2.16'), ) -export const firstReleaseNote = '2.20' -export const firstRestoredAdminGuides = '2.21' + +// ============================================================================ +// HELPER FUNCTIONS +// ============================================================================ + +/** + * Determines if a release date should be displayed based on current time. + * Only shows dates that have already occurred to avoid showing future release dates. + * @param {string|null} date - ISO date string + * @returns {string|null} - Date string if in the past, null if future or invalid + */ +function processDateForDisplay(date) { + if (!date) return null + const currentTimestamp = Math.floor(Date.now() / 1000) + const dateTimestamp = Math.floor(new Date(date).getTime() / 1000) + return dateTimestamp <= currentTimestamp ? date : null +} + +/** + * Validates that version sequence is correct (each version is exactly one release ahead) + * @param {string} v1 - Current version + * @param {string} v2 - Next version + * @throws {Error} If version sequence is invalid + */ +function isValidNext(v1, v2) { + const semverV1 = semver.coerce(v1).raw + const semverV2 = semver.coerce(v2).raw + const isValid = + semverV2 === semver.inc(semverV1, 'minor') || semverV2 === semver.inc(semverV1, 'major') + if (!isValid) + throw new Error(`The version "${v2}" is not one version ahead of "${v1}" as expected`) +} export const findReleaseNumberIndex = (releaseNum) => { return all.findIndex((i) => i === releaseNum) } + export const getNextReleaseNumber = (releaseNum) => { return all[findReleaseNumberIndex(releaseNum) - 1] } + export const getPreviousReleaseNumber = (releaseNum) => { return all[findReleaseNumberIndex(releaseNum) + 1] } +// Validate that version sequence is correct +isValidNext(supported[0], next) +isValidNext(next, nextNext) + +// ============================================================================ +// DEFAULT EXPORT +// ============================================================================ + export default { next, nextNext,