diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 750dc0410c..0acc05d843 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -27,6 +27,9 @@ package.json @github/docs-engineering # Content strategy /contributing/content-markup-reference.md @github/docs-content-strategy /contributing/content-style-guide.md @github/docs-content-strategy +/contributing/content-model.md @github/docs-content-strategy +/contributing/content-style-guide.md @github/docs-content-strategy +/contributing/content-templates.md @github/docs-content-strategy # Make sure that Octokit maintainers get notified about changes # relevant to the Octokit libraries (https://github.com/octokit) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 80cb00960c..07a9a39dec 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -24,7 +24,7 @@ Closes [issue link] ### Check off the following: -- [ ] I have reviewed my changes in staging (look for the **deploy-to-heroku** link in your pull request, then click **View deployment**). +- [ ] I have reviewed my changes in staging (look for the latest deployment event in your pull request's timeline, then click **View deployment**). - [ ] For content changes, I have completed the [self-review checklist](https://github.com/github/docs/blob/main/CONTRIBUTING.md#self-review). ### Writer impact (This section is for GitHub staff members only): diff --git a/.github/workflows/staging-deploy-pr.yml b/.github/workflows/staging-deploy-pr.yml new file mode 100644 index 0000000000..03f1624c50 --- /dev/null +++ b/.github/workflows/staging-deploy-pr.yml @@ -0,0 +1,201 @@ +name: Staging - Deploy PR + +# **What it does**: To deploy PRs to a Heroku staging environment. +# **Why we have it**: To deploy with high visibility in case of failures. +# **Who does it impact**: All contributors. + +on: + pull_request: + types: + - opened + - reopened + - synchronize + - unlocked + workflow_dispatch: + inputs: + pullRequestUrl: + description: 'Pull Request URL' + required: true + default: 'https://github.com/github/docs/pull/1234' + forceRebuild: + description: 'Force the Heroku App to be rebuilt from scratch? (true/false)' + required: false + default: 'false' + +jobs: + validate-inputs: + if: ${{ github.repository == 'github/docs-internal' || github.repository == 'github/docs' }} + name: Validate inputs + runs-on: ubuntu-latest + timeout-minutes: 2 + outputs: + headRef: ${{ steps.validate.outputs.headRef }} + steps: + - if: ${{ github.event_name == 'workflow_dispatch' }} + name: Check out repo + uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f + with: + # Enables cloning the Early Access repo later with the relevant PAT + persist-credentials: 'false' + + - if: ${{ github.event_name == 'workflow_dispatch' }} + name: Setup node + uses: actions/setup-node@c46424eee26de4078d34105d3de3cc4992202b1e + with: + node-version: 16.x + + - if: ${{ github.event_name == 'workflow_dispatch' }} + name: Get npm cache directory + id: npm-cache + run: | + echo "::set-output name=dir::$(npm config get cache)" + + - if: ${{ github.event_name == 'workflow_dispatch' }} + name: Cache node modules + uses: actions/cache@0781355a23dac32fd3bac414512f4b903437991a + with: + path: ${{ steps.npm-cache.outputs.dir }} + key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-node- + + - if: ${{ github.event_name == 'workflow_dispatch' }} + name: Install dependencies + run: npm ci + + - if: ${{ github.event_name == 'workflow_dispatch' }} + name: Validate and get head.ref + id: validate + uses: actions/github-script@2b34a689ec86a68d8ab9478298f91d5401337b7d + env: + PR_URL: ${{ github.event.inputs.pullRequestUrl }} + FORCE_REBUILD: ${{ github.event.inputs.forceRebuild }} + with: + script: | + const parsePrUrl = require('./script/deployment/parse-pr-url') + + // Manually resolve workflow_dispatch inputs + const { PR_URL, FORCE_REBUILD } = process.env + + if (!['true', 'false'].includes(FORCE_REBUILD)) { + throw new Error(`'forceRebuild' input must be either 'true' or 'false' but was '${FORCE_REBUILD}'`) + } + + const { owner, repo, pullNumber } = parsePrUrl(PR_URL) + if (!owner || !repo || !pullNumber) { + throw new Error(`'pullRequestUrl' input must match URL format 'https://github.com/github/(docs|docs-internal)/pull/123' but was '${PR_URL}'`) + } + + const { data: pullRequest } = await github.pulls.get({ + owner, + repo, + pull_number: pullNumber + }) + + core.setOutput('headRef', pullRequest.head.ref) + + deploy: + if: ${{ github.repository == 'github/docs-internal' || github.repository == 'github/docs' }} + needs: validate-inputs + name: Deploy + runs-on: ubuntu-latest + timeout-minutes: 10 + concurrency: + group: staging_${{ needs.validate-inputs.outputs.headRef || github.head_ref }} + cancel-in-progress: true + steps: + - name: Check out repo + uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f + with: + # Enables cloning the Early Access repo later with the relevant PAT + persist-credentials: 'false' + + - name: Setup node + uses: actions/setup-node@c46424eee26de4078d34105d3de3cc4992202b1e + with: + node-version: 16.x + + - name: Get npm cache directory + id: npm-cache + run: | + echo "::set-output name=dir::$(npm config get cache)" + + - name: Cache node modules + uses: actions/cache@0781355a23dac32fd3bac414512f4b903437991a + with: + path: ${{ steps.npm-cache.outputs.dir }} + key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-node- + + - name: Install dependencies + run: npm ci + + - name: Deploy + uses: actions/github-script@2b34a689ec86a68d8ab9478298f91d5401337b7d + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + HEROKU_API_TOKEN: ${{ secrets.HEROKU_API_TOKEN }} + DOCUBOT_REPO_PAT: ${{ secrets.DOCUBOT_REPO_PAT }} + HYDRO_ENDPOINT: ${{ secrets.HYDRO_ENDPOINT }} + HYDRO_SECRET: ${{ secrets.HYDRO_SECRET }} + PR_URL: ${{ github.event.inputs.pullRequestUrl }} + FORCE_REBUILD: ${{ github.event.inputs.forceRebuild }} + with: + script: | + const { GITHUB_TOKEN, HEROKU_API_TOKEN } = process.env + + // Exit if GitHub Actions PAT is not found + if (!GITHUB_TOKEN) { + throw new Error('You must supply a GITHUB_TOKEN environment variable!') + } + + // Exit if Heroku API token is not found + if (!HEROKU_API_TOKEN) { + throw new Error('You must supply a HEROKU_API_TOKEN environment variable!') + } + + const parsePrUrl = require('./script/deployment/parse-pr-url') + const getOctokit = require('./script/helpers/github') + const deployToStaging = require('./script/deployment/deploy-to-staging') + + // This helper uses the `GITHUB_TOKEN` implicitly! + // We're using our usual version of Octokit vs. the provided `github` + // instance to avoid versioning discrepancies. + const octokit = getOctokit() + + try { + let pullRequest = null + let forceRebuild = false + + // Manually resolve workflow_dispatch inputs + if (context.eventName === 'workflow_dispatch') { + const { PR_URL, FORCE_REBUILD } = process.env + + forceRebuild = FORCE_REBUILD === 'true' + + const { owner, repo, pullNumber } = parsePrUrl(PR_URL) + if (!owner || !repo || !pullNumber) { + throw new Error(`'pullRequestUrl' input must match URL format 'https://github.com/github/(docs|docs-internal)/pull/123' but was '${PR_URL}'`) + } + + const { data: pr } = await octokit.pulls.get({ + owner, + repo, + pull_number: pullNumber + }) + pullRequest = pr + } + + await deployToStaging({ + herokuToken: HEROKU_API_TOKEN, + octokit, + pullRequest: pullRequest || context.payload.pull_request, + forceRebuild, + runId: context.runId + }) + } catch (error) { + console.error(`Failed to deploy to staging: ${error.message}`) + console.error(error) + throw error + } diff --git a/.github/workflows/staging-undeploy-pr.yml b/.github/workflows/staging-undeploy-pr.yml new file mode 100644 index 0000000000..e112cc0144 --- /dev/null +++ b/.github/workflows/staging-undeploy-pr.yml @@ -0,0 +1,88 @@ +name: Staging - Undeploy PR + +# **What it does**: To undeploy PRs from a Heroku staging environment, i.e. destroy the Heroku App. +# **Why we have it**: To save money spent on deployments for closed PRs. +# **Who does it impact**: All contributors. + +on: + pull_request: + types: + - closed + - locked + +jobs: + undeploy: + if: ${{ github.repository == 'github/docs-internal' || github.repository == 'github/docs' }} + name: Undeploy + runs-on: ubuntu-latest + timeout-minutes: 2 + concurrency: + group: staging_${{ github.head_ref }} + cancel-in-progress: true + steps: + - name: Check out repo + uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f + with: + # Enables cloning the Early Access repo later with the relevant PAT + persist-credentials: 'false' + + - name: Setup node + uses: actions/setup-node@c46424eee26de4078d34105d3de3cc4992202b1e + with: + node-version: 16.x + + - name: Get npm cache directory + id: npm-cache + run: | + echo "::set-output name=dir::$(npm config get cache)" + + - name: Cache node modules + uses: actions/cache@0781355a23dac32fd3bac414512f4b903437991a + with: + path: ${{ steps.npm-cache.outputs.dir }} + key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-node- + + - name: Install dependencies + run: npm ci + + - name: Undeploy + uses: actions/github-script@2b34a689ec86a68d8ab9478298f91d5401337b7d + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + HEROKU_API_TOKEN: ${{ secrets.HEROKU_API_TOKEN }} + with: + script: | + const { GITHUB_TOKEN, HEROKU_API_TOKEN } = process.env + + // Exit if GitHub Actions PAT is not found + if (!GITHUB_TOKEN) { + throw new Error('You must supply a GITHUB_TOKEN environment variable!') + } + + // Exit if Heroku API token is not found + if (!HEROKU_API_TOKEN) { + throw new Error('You must supply a HEROKU_API_TOKEN environment variable!') + } + + const getOctokit = require('./script/helpers/github') + const undeployFromStaging = require('./script/deployment/undeploy-from-staging') + + // This helper uses the `GITHUB_TOKEN` implicitly! + // We're using our usual version of Octokit vs. the provided `github` + // instance to avoid versioning discrepancies. + const octokit = getOctokit() + + try { + await undeployFromStaging({ + herokuToken: HEROKU_API_TOKEN, + octokit, + pullRequest: context.payload.pull_request, + runId: context.runId + }) + } catch (error) { + console.error(`Failed to undeploy from staging: ${error.message}`) + console.error(error) + throw error + } diff --git a/.github/workflows/workflow-lint.yml b/.github/workflows/workflow-lint.yml index a3f3c57ede..6779e97adf 100644 --- a/.github/workflows/workflow-lint.yml +++ b/.github/workflows/workflow-lint.yml @@ -26,4 +26,4 @@ jobs: - name: Run linter uses: cschleiden/actions-linter@0ff16d6ac5103cca6c92e6cbc922b646baaea5be with: - workflows: '[".github/workflows/*.yml"]' + workflows: '[".github/workflows/*.yml", "!.github/workflows/staging-deploy-pr.yml", "!.github/workflows/staging-undeploy-pr.yml"]' diff --git a/components/Header.tsx b/components/Header.tsx index 09f686914e..d472d09063 100644 --- a/components/Header.tsx +++ b/components/Header.tsx @@ -34,7 +34,7 @@ export const Header = () => { style={{ zIndex: 2 }} > {/* desktop header */} -