diff --git a/data/variables/release_candidate.yml b/data/variables/release_candidate.yml deleted file mode 100644 index 953c8dceba..0000000000 --- a/data/variables/release_candidate.yml +++ /dev/null @@ -1 +0,0 @@ -version: enterprise-server@3.11 diff --git a/src/data-directory/lib/get-data.js b/src/data-directory/lib/get-data.js index 8a0bfbbe7a..e8de85ba4d 100644 --- a/src/data-directory/lib/get-data.js +++ b/src/data-directory/lib/get-data.js @@ -16,10 +16,7 @@ const DEBUG_JIT_DATA_READS = Boolean(JSON.parse(process.env.DEBUG_JIT_DATA_READS // English for. // Having this is safer than trying to wrangle the translations to NOT // have them translated. -const ALWAYS_ENGLISH_YAML_FILES = new Set([ - 'data/variables/product.yml', - 'data/variables/release_candidate.yml', -]) +const ALWAYS_ENGLISH_YAML_FILES = new Set(['data/variables/product.yml']) // Returns all the things inside a directory export const getDeepDataByLanguage = memoize((dottedPath, langCode, dir = null) => { diff --git a/src/fixtures/tests/internal-links.js b/src/fixtures/tests/internal-links.js index ee84b97ecb..fdb4fe45b4 100644 --- a/src/fixtures/tests/internal-links.js +++ b/src/fixtures/tests/internal-links.js @@ -106,7 +106,7 @@ describe('link-rewriting', () => { const link = links.filter((i, element) => $(element).text() === 'Cross Version Linking') expect(link.attr('href')).toMatch( - `/en/enterprise-server@${enterpriseServerReleases.latest}/get-started/`, + `/en/enterprise-server@${enterpriseServerReleases.latestStable}/get-started/`, ) }) }) diff --git a/src/fixtures/tests/page-titles.js b/src/fixtures/tests/page-titles.js index e88980750b..e4694b50c2 100644 --- a/src/fixtures/tests/page-titles.js +++ b/src/fixtures/tests/page-titles.js @@ -15,7 +15,7 @@ describe('page titles', () => { test('ghes article', async () => { const $ = await getDOM(`/enterprise-server@latest/get-started/quickstart/hello-world`) expect($('title').text()).toBe( - `Hello World - GitHub Enterprise Server ${enterpriseServerReleases.latest} Docs`, + `Hello World - GitHub Enterprise Server ${enterpriseServerReleases.latestStable} Docs`, ) }) diff --git a/src/frame/components/context/MainContext.tsx b/src/frame/components/context/MainContext.tsx index 2bd941cb73..843898692b 100644 --- a/src/frame/components/context/MainContext.tsx +++ b/src/frame/components/context/MainContext.tsx @@ -73,7 +73,7 @@ type DataT = { ui: UIStrings reusables: DataReusables variables: { - release_candidate: { version: string } + release_candidate: { version: string | null } } } @@ -202,6 +202,13 @@ export const getMainContext = async (req: any, res: any): Promise } } + // This is a number, like 3.13 or it's possibly null if there is no + // supported release candidate at the moment. + const { releaseCandidate } = req.context.enterpriseServerReleases + // Combine the version number with the prefix so it can appear + // as a full version string if the release candidate is set. + const releaseCandidateVersion = releaseCandidate ? `enterprise-server@${releaseCandidate}` : null + return { breadcrumbs: req.context.breadcrumbs || {}, communityRedirect: req.context.page?.communityRedirect || {}, @@ -216,7 +223,7 @@ export const getMainContext = async (req: any, res: any): Promise variables: { release_candidate: { - version: req.context.getDottedData('variables.release_candidate.version') || null, + version: releaseCandidateVersion, }, }, }, diff --git a/src/ghes-releases/lib/release-steps.md b/src/ghes-releases/lib/release-steps.md index 9db1fc52e5..3e4fff9344 100644 --- a/src/ghes-releases/lib/release-steps.md +++ b/src/ghes-releases/lib/release-steps.md @@ -22,9 +22,9 @@ If you aren't comfortable going through the steps alone, sync up with a docs eng src/ghes-releases/scripts/update-enterprise-dates.js ``` - [ ] Create placeholder data files for automation pipelines and release notes: - + **Note:** The content in `data/release-notes/enterprise-server/PLACEHOLDER-TEMPLATE.yml` is copied to `data/release-notes/enterprise-server//PLACEHOLDER.yml`. All of the content in this file will be updated when the release notes are created in the megabranch including the filename `PLACEHOLDER.yml`. You can update the date or leave it as-is and wait to update it when the release notes are finalized. - + ``` src/ghes-releases/scripts/sync-automated-pipeline-data.js ``` @@ -32,7 +32,6 @@ If you aren't comfortable going through the steps alone, sync up with a docs eng ``` src/ghes-releases/scripts/release-banner.js --action create --version - src/tests/scripts/copy-fixture-data.js // This updates the fixtures to match the updated data/variables/release_candidate.yml file ``` - [ ] Create a PR with the above changes. This PR is used to track all docs changes and smoke tests associated with the release. For example https://github.com/github/docs-internal/pull/22286. @@ -52,7 +51,7 @@ If you aren't comfortable going through the steps alone, sync up with a docs eng - [ ] Copy the previous release's configuration file to a new configuration file for this release `cp app/api/description/config/releases/ghes-.yaml app/api/description/config/releases/ghes-.yaml`. - [ ] Update all references to the old GHES release number in that file to use the new GHES release number. There are about 4 occurrences at the time of this writing: `variables.externalDocsUrl`, `variables.ghesVersion`, and two keys under `patch` for the paths `/info/x-github-release` and `/externalDocs`. - [ ] Update `published` in that file to `false`. **Note:** This is important to ensure that changes for the next version of the OpenAPI schema changes are not made public until the new version is released. - - [ ] Manually update the file `app/api/description/config/release_api_versioning_support.yaml` by copying the previous releases entry to a new entry. This file keeps track of API calendar-date versions that apply to the product version. + - [ ] Manually update the file `app/api/description/config/release_api_versioning_support.yaml` by copying the previous releases entry to a new entry. This file keeps track of API calendar-date versions that apply to the product version. - [ ] Run `./bin/openapi generate-root-files` to generate the `app/api/description/ghes-.yaml` file and merge the changes. - [ ] Create a PR with the two file changes `app/api/description/ghes-.yaml` and `app/api/description/config/releases/ghes-.yaml` - [ ] Create a second PR based on the PR created ☝️ that toggles `published` to `true` in the `app/api/description/config/releases/ghes-.yaml` file. When this PR merges it will publish the new release to the `github/rest-api-description` repo and will trigger a pull request in the `github/docs-internal` repo with the schemas for the next GHES release. There is a step in this list to merge that PR in the "Before shipping the release branch" section. @@ -102,8 +101,8 @@ This file should be automatically updated, but you can also run `src/ghes-releas ### 🚢 🛳️ 🚢 Shipping the release branch - [ ] Sync the search indices for the new release: - 1. First navigate to the [sync search indices workflow](https://github.com/github/docs-internal/actions/workflows/sync-search-indices.yml). - 2. Then, to run the workflow with parameters, click on `Run workflow` button. + 1. First navigate to the [sync search indices workflow](https://github.com/github/docs-internal/actions/workflows/sync-search-indices.yml). + 2. Then, to run the workflow with parameters, click on `Run workflow` button. 3. A modal will pop up where you will set the following inputs: - Branch: The new `ghes--megabranch` version megabranch you're working on - Version: `enterprise-server@` @@ -115,8 +114,8 @@ This file should be automatically updated, but you can also run `src/ghes-releas Use admin permissions to ship the release branch with this failure. Make sure that the merge's commit title does not include anything like `[DO NOT MERGE]`, and remove all the branch's commit details from the merge's commit message except for the co-author list. - [ ] Do any required smoke tests listed in the opening post in the megabranch PR. You can monitor and check when the production deploy completed by viewing the [`docs-internal` deployments page](https://github.com/github/docs-internal/deployments). - [ ] Once smoke tests have passed, you can [unfreeze the repos](https://github.com/github/docs-content/blob/main/docs-content-docs/docs-content-workflows/freezing.md) and post an announcement in Slack. -- [ ] After unfreezing, alert the Ecosystem-API team in #ecosystem-api the docs freeze is finished/thawed and the release has shipped. +- [ ] After unfreezing, alert the Ecosystem-API team in #ecosystem-api the docs freeze is finished/thawed and the release has shipped. - [ ] You (or they) can now remove your blocking review on the auto-generated "Update OpenAPI Descriptions" PR in public REST API description (the `rest-api-descriptions` repo). (although it's likely newer PRs have been created since yours with the blocking review, in which case the Ecosystem-API team will close your PR and perform the next step on the most recent PR). - [ ] The Ecosystem-API team will merge the latest auto-generated "Update OpenAPI Descriptions" PR (which will contain the OpenAPI schema config that changed `published` to `true` for the release). - [ ] After unfreezing, if there were significant or highlighted GraphQL changes in the release, consider manually running the [GraphQL update workflow](https://github.com/github/docs-internal/actions/workflows/sync-graphql.yml) to update our GraphQL schemas. By default this workflow only runs once every 24 hours. -- [ ] After the release, in the `docs-content` repo, add the now live version number to the "Specific GHES version(s)" section in [`.github/ISSUE_TEMPLATE/release-tracking.yml`](https://github.com/github/docs-content/blob/main/.github/ISSUE_TEMPLATE/release-tracking.yml). When the PR is approved, merge it in. +- [ ] After the release, in the `docs-content` repo, add the now live version number to the "Specific GHES version(s)" section in [`.github/ISSUE_TEMPLATE/release-tracking.yml`](https://github.com/github/docs-content/blob/main/.github/ISSUE_TEMPLATE/release-tracking.yml). When the PR is approved, merge it in. diff --git a/src/ghes-releases/scripts/release-banner.js b/src/ghes-releases/scripts/release-banner.js index f564652b21..3537e73364 100755 --- a/src/ghes-releases/scripts/release-banner.js +++ b/src/ghes-releases/scripts/release-banner.js @@ -7,14 +7,11 @@ // [end-readme] import fs from 'fs/promises' -import path from 'path' import { program } from 'commander' -import yaml from 'js-yaml' import { allVersions } from '#src/versions/lib/all-versions.js' -const releaseCandidateFile = 'data/variables/release_candidate.yml' -const releaseCandidateYaml = path.join(process.cwd(), releaseCandidateFile) +const releaseCandidateJSFile = 'src/versions/lib/enterprise-server-releases.js' const allowedActions = ['create', 'remove'] program @@ -41,22 +38,29 @@ if (!Object.keys(allVersions).includes(options.version)) { } // Load the release candidate variable -const releaseCandidateData = yaml.load(await fs.readFile(releaseCandidateYaml, 'utf8')) +let jsCode = await fs.readFile(releaseCandidateJSFile, 'utf8') +const lineRegex = /export const releaseCandidate = .*/ +if (!lineRegex.test(jsCode)) { + throw new Error( + `The file ${releaseCandidateJSFile} does not contain a release candidate variable. ('export const releaseCandidate = ...')`, + ) +} // Create or remove the variable if (options.action === 'create') { - releaseCandidateData.version = options.version -} - -if (options.action === 'remove') { - releaseCandidateData.version = '' + jsCode = jsCode.replace( + lineRegex, + `export const releaseCandidate = '${options.version.split('@')[1]}'`, + ) +} else if (options.action === 'remove') { + jsCode = jsCode.replace(lineRegex, `export const releaseCandidate = null`) } // Update the file -await fs.writeFile(releaseCandidateYaml, yaml.dump(releaseCandidateData)) +await fs.writeFile(releaseCandidateJSFile, jsCode) // Display next steps -console.log(`\nDone! Commit the update to ${releaseCandidateFile}. This ${options.action}s the banner for ${options.version}. +console.log(`\nDone! Commit the update to ${releaseCandidateJSFile}. This ${options.action}s the banner for ${options.version}. - To change the banner text, you can edit header.notices.release_candidate in data/ui.yml. - To change the banner styling, you can edit includes/header-notification.html. diff --git a/src/landings/tests/featured-links.js b/src/landings/tests/featured-links.js index 9120804764..af5608c86e 100644 --- a/src/landings/tests/featured-links.js +++ b/src/landings/tests/featured-links.js @@ -25,7 +25,7 @@ describe('featuredLinks', () => { const $featuredLinks = $('[data-testid=article-list] a') expect($featuredLinks.length).toBeGreaterThan(0) expect($featuredLinks.eq(0).attr('href')).toBe( - `/en/enterprise-server@${enterpriseServerReleases.latest}/get-started/foo/bar`, + `/en/enterprise-server@${enterpriseServerReleases.latestStable}/get-started/foo/bar`, ) expect($featuredLinks.eq(0).children('h3').text()).toMatch('Bar Usually Comes After Foo') expect($featuredLinks.eq(0).children('p').text()).toMatch( diff --git a/src/redirects/lib/get-redirect.js b/src/redirects/lib/get-redirect.js index 076ba5956f..504d125363 100644 --- a/src/redirects/lib/get-redirect.js +++ b/src/redirects/lib/get-redirect.js @@ -3,6 +3,7 @@ import nonEnterpriseDefaultVersion from '#src/versions/lib/non-enterprise-defaul import { allVersions } from '#src/versions/lib/all-versions.js' import { latest, + latestStable, supported, deprecatedWithFunctionalRedirects, } from '#src/versions/lib/enterprise-server-releases.js' @@ -64,7 +65,8 @@ export default function getRedirect(uri, context) { ) { // E.g. '/enterprise-server' or '/enterprise-server/3.0/foo' basicCorrection = - `/${language}` + withoutLanguage.replace('/enterprise-server', `/enterprise-server@${latest}`) + `/${language}` + + withoutLanguage.replace('/enterprise-server', `/enterprise-server@${latestStable}`) // If it's now just the version, without anything after, exit here if (withoutLanguage === '/enterprise-server') { return basicCorrection @@ -73,7 +75,7 @@ export default function getRedirect(uri, context) { // E.g. '/enterprise-server@latest' or '/enterprise-server@latest/3.3/foo' basicCorrection = `/${language}` + - withoutLanguage.replace('/enterprise-server@latest', `/enterprise-server@${latest}`) + withoutLanguage.replace('/enterprise-server@latest', `/enterprise-server@${latestStable}`) // If it was *just* '/enterprise-server@latest' all that's needed is // the language but with 'latest' replaced with the value of `latest` if (withoutLanguage === '/enterprise-server@latest') { diff --git a/src/redirects/tests/redirects.js b/src/redirects/tests/redirects.js index 72e66fd73c..22a411971e 100644 --- a/src/redirects/tests/redirects.js +++ b/src/redirects/tests/redirects.js @@ -42,8 +42,8 @@ describe('redirects', () => { expect(pageRedirects['/about-issues']).toBe('/issues') expect(pageRedirects['/creating-an-issue']).toBe('/issues') expect( - pageRedirects[`/enterprise-server@${enterpriseServerReleases.latest}/about-issues`], - ).toBe(`/enterprise-server@${enterpriseServerReleases.latest}/issues`) + pageRedirects[`/enterprise-server@${enterpriseServerReleases.latestStable}/about-issues`], + ).toBe(`/enterprise-server@${enterpriseServerReleases.latestStable}/issues`) expect( pageRedirects[`/enterprise-server@${enterpriseServerReleases.latest}/creating-an-issue`], ).toBe(`/enterprise-server@${enterpriseServerReleases.latest}/issues`) @@ -170,6 +170,7 @@ describe('redirects', () => { describe('enterprise home page', () => { const enterpriseHome = `/en/enterprise-server@${enterpriseServerReleases.latest}` + const enterpriseStableHome = `/en/enterprise-server@${enterpriseServerReleases.latestStable}` test('/enterprise', async () => { const res = await get('/enterprise') @@ -189,10 +190,10 @@ describe('redirects', () => { expect(res.headers.location).toBe(enterpriseHome) }) - test('hardcoded @latest redirects to latest version', async () => { + test('hardcoded @latest redirects to latest stable version', async () => { const res = await get('/en/enterprise-server@latest') expect(res.statusCode).toBe(302) - expect(res.headers.location).toBe(enterpriseHome) + expect(res.headers.location).toBe(enterpriseStableHome) }) }) diff --git a/src/redirects/tests/unit/get-redirect.js b/src/redirects/tests/unit/get-redirect.js index 2cff7307aa..0a317e0ad2 100644 --- a/src/redirects/tests/unit/get-redirect.js +++ b/src/redirects/tests/unit/get-redirect.js @@ -1,7 +1,7 @@ import { describe, expect } from '@jest/globals' import getRedirect from '../../lib/get-redirect.js' -import { latest, supported } from '#src/versions/lib/enterprise-server-releases.js' +import { latest, latestStable, supported } from '#src/versions/lib/enterprise-server-releases.js' const previousEnterpriserServerVersion = supported[1] describe('getRedirect basics', () => { @@ -41,8 +41,10 @@ describe('getRedirect basics', () => { expect(getRedirect('/enterprise-server@3.7', ctx)).toBe('/en/enterprise-server@3.7') - expect(getRedirect('/enterprise-server@latest', ctx)).toBe(`/en/enterprise-server@${latest}`) - expect(getRedirect('/enterprise-server', ctx)).toBe(`/en/enterprise-server@${latest}`) + expect(getRedirect('/enterprise-server@latest', ctx)).toBe( + `/en/enterprise-server@${latestStable}`, + ) + expect(getRedirect('/enterprise-server', ctx)).toBe(`/en/enterprise-server@${latestStable}`) }) it('should always "remove" the free-pro-team prefix', () => { @@ -138,11 +140,14 @@ describe('getRedirect basics', () => { pages: {}, redirects: { [`/enterprise-server@${latest}/foo`]: `/enterprise-server@${latest}/bar`, + [`/enterprise-server@${latestStable}/foo`]: `/enterprise-server@${latestStable}/bar`, }, } // Nothing's needed here because it's not /admin/guides and // it already has the enterprise-server prefix. - expect(getRedirect('/enterprise-server/foo', ctx)).toBe(`/en/enterprise-server@${latest}/bar`) + expect(getRedirect('/enterprise-server/foo', ctx)).toBe( + `/en/enterprise-server@${latestStable}/bar`, + ) }) it('should work for some deprecated enterprise-server URLs too', () => { diff --git a/src/tests/scripts/copy-fixture-data.js b/src/tests/scripts/copy-fixture-data.js index da36b7db94..0082366af4 100755 --- a/src/tests/scripts/copy-fixture-data.js +++ b/src/tests/scripts/copy-fixture-data.js @@ -26,7 +26,6 @@ const MANDATORY_FILES = [ 'data/reusables/enterprise_deprecation/deprecation_details.md', 'data/reusables/enterprise_deprecation/version_was_deprecated.md', 'data/reusables/enterprise_deprecation/version_will_be_deprecated.md', - 'data/variables/release_candidate.yml', ] const DESTINATION = path.resolve('src/fixtures/fixtures') diff --git a/src/versions/lib/enterprise-server-releases.js b/src/versions/lib/enterprise-server-releases.js index 217a3cca4c..fa32583ce1 100644 --- a/src/versions/lib/enterprise-server-releases.js +++ b/src/versions/lib/enterprise-server-releases.js @@ -14,6 +14,9 @@ export const nextNext = '3.13' export const supported = ['3.11', '3.10', '3.9', '3.8', '3.7', '3.6'] +// Edit this to `null` when it's no longer the release candidate +export const releaseCandidate = '3.11' + // Ensure that: // "next" is ahead of "latest" by one minor or major release. // "nextNext" is ahead of "next" by one minor or major release. @@ -75,6 +78,7 @@ export const firstReleaseStoredInBlobStorage = '3.2' export const all = supported.concat(deprecated) export const latest = supported[0] +export const latestStable = releaseCandidate ? supported[1] : latest export const oldestSupported = supported[supported.length - 1] export const nextDeprecationDate = dates[oldestSupported].deprecationDate export const isOldestReleaseDeprecated = new Date() > new Date(nextDeprecationDate) @@ -117,6 +121,8 @@ export default { legacyAssetVersions, all, latest, + latestStable, + releaseCandidate, oldestSupported, nextDeprecationDate, isOldestReleaseDeprecated,