1
0
mirror of synced 2025-12-23 11:54:18 -05:00

Merge branch 'main' into sophie-6156-rename

This commit is contained in:
Sophie
2022-05-12 18:08:38 -04:00
2349 changed files with 3165574 additions and 45364 deletions

View File

@@ -13,7 +13,7 @@ module.exports = {
babelOptions: { configFile: './.babelrc' },
sourceType: 'module',
},
ignorePatterns: ['tmp/*', '!/.*', '/.next/', 'script/bookmarklets/*'],
ignorePatterns: ['tmp/*', '!/.*', '/.next/', 'script/bookmarklets/*', 'lib/sigsci.js'],
rules: {
'import/no-extraneous-dependencies': ['error', { packageDir: '.' }],
},

View File

@@ -3,6 +3,12 @@
import * as github from '@actions/github'
import { setOutput } from '@actions/core'
import { getContents } from '../../script/helpers/git-utils.js'
import parse from '../../lib/read-frontmatter.js'
import getApplicableVersions from '../../lib/get-applicable-versions.js'
import nonEnterpriseDefaultVersion from '../../lib/non-enterprise-default-version.js'
import { allVersionShortnames } from '../../lib/all-versions.js'
const { GITHUB_TOKEN, APP_URL } = process.env
const context = github.context
@@ -14,13 +20,14 @@ if (!APP_URL) {
throw new Error(`APP_URL environment variable not set`)
}
const PROD_URL = 'https://docs.github.com'
const octokit = github.getOctokit(GITHUB_TOKEN)
const response = await octokit.rest.repos.compareCommits({
// get the list of file changes from the PR
const response = await octokit.rest.repos.compareCommitsWithBasehead({
owner: context.repo.owner,
repo: context.payload.repository.name,
base: context.payload.pull_request.base.sha,
head: context.payload.pull_request.head.sha,
basehead: `${context.payload.pull_request.base.sha}...${context.payload.pull_request.head.sha}`,
})
const { files } = response.data
@@ -36,16 +43,74 @@ for (const file of articleFiles) {
const sourceUrl = file.blob_url
const fileName = file.filename.slice(pathPrefix.length)
const fileUrl = fileName.slice(0, fileName.lastIndexOf('.'))
const previewLink = `${APP_URL}/${fileUrl}`
const productionLink = `https://docs.github.com/${fileUrl}`
let markdownLine = ''
if (file.status === 'modified') {
markdownLine = `| [content/${fileName}](${sourceUrl}) | [Modified](${previewLink}) | [Original](${productionLink}) | |\n`
} else if (file.status === 'added') {
markdownLine = `| New file: [content/${fileName}](${sourceUrl}) | [Modified](${previewLink}) | | |\n`
// get the file contents and decode them
// this script is called from the main branch, so we need the API call to get the contents from the branch, instead
const fileContents = await getContents(
context.repo.owner,
context.payload.repository.name,
context.payload.pull_request.head.sha,
file.filename
)
// parse the frontmatter
const { data } = parse(fileContents)
let contentCell = ''
let previewCell = ''
let prodCell = ''
if (file.status === 'added') contentCell = `New file: `
contentCell += `[\`${fileName}\`](${sourceUrl})`
try {
// the try/catch is needed because getApplicableVersions() returns either [] or throws an error when it can't parse the versions frontmatter
// try/catch can be removed if docs-engineering#1821 is resolved
// i.e. for feature based versioning, like ghae: 'issue-6337'
const fileVersions = getApplicableVersions(data.versions)
for (const plan in allVersionShortnames) {
// plan is the shortName (i.e., fpt)
// allVersionShortNames[plan] is the planName (i.e., free-pro-team)
// walk by the plan names since we generate links differently for most plans
const versions = fileVersions.filter((fileVersion) =>
fileVersion.includes(allVersionShortnames[plan])
)
if (versions.length === 1) {
// for fpt, ghec, and ghae
if (versions.toString() === nonEnterpriseDefaultVersion) {
// omit version from fpt url
previewCell += `[${plan}](${APP_URL}/${fileUrl})<br>`
prodCell += `[${plan}](${PROD_URL}/${fileUrl})<br>`
} else {
// for non-versioned releases (ghae, ghec) use full url
previewCell += `[${plan}](${APP_URL}/${versions}/${fileUrl})<br>`
prodCell += `[${plan}](${PROD_URL}/${versions}/${fileUrl})<br>`
}
markdownTable += markdownLine
}
} else if (versions.length) {
// for ghes releases, link each version
previewCell += `${plan}@ `
prodCell += `${plan}@ `
versions.forEach((version) => {
previewCell += `[${version.split('@')[1]}](${APP_URL}/${version}/${fileUrl}) `
prodCell += `[${version.split('@')[1]}](${PROD_URL}/${version}/${fileUrl}) `
})
previewCell += '<br>'
prodCell += '<br>'
}
}
} catch (e) {
console.error(
`Version information for ${file.filename} couldn't be determined from its frontmatter.`
)
}
markdownTable += `| ${contentCell} | ${previewCell} | ${prodCell} | |\n`
}
setOutput('changesTable', markdownTable)

View File

@@ -110,20 +110,20 @@ This file should be automatically updated, but you can also run `script/update-e
### 🚢 🛳️ 🚢 Shipping the release branch
- [ ] The megabranch creator should push the search index LFS objects for the public `github/docs` repo. The LFS objects were already pushed for the internal repo after the `sync-english-index-for-<PLAN@RELEASE>` was added to the megabranch. To push the LFS objects to the public repo:
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-<RELEASE>-megabranch` version megabranch you're working on
- Version: `enterprise-server@<RELEASE>`
- Language: `en`
4. Run the job. The workflow job may fail on the first run—so retry the failed job if needed.
- [ ] Remove `[DO NOT MERGE]` and other meta information from the PR title 😜.
- [ ] The `github/docs-internal` repo is frozen, and the `Repo Freeze Check / Prevent merging during deployment freezes (pull_request_target)` test is expected to fail.
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, the megabranch creator should push the search index LFS objects for the public `github/docs` repo. The LFS objects were already pushed for the internal repo after the `sync-english-index-for-<PLAN@RELEASE>` was added to the megabranch. To push the LFS objects to the public repo:
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 version megabranch you're working on
- Version: `enterprise-server@<RELEASE>`
- Language: `en`
4. Run the job. The workflow job may fail on the first run—so retry the failed job if needed.
- [ ] 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).

View File

@@ -1,7 +1,7 @@
## Author self-review
- [ ] The changes in this PR meet the user experience and goals outlined in the content design plan.
- [ ] I've compared my PR's source changes to staging and reviewed for versioning issues, redirects, the [style guide](https://github.com/github/docs/blob/main/contributing/content-style-guide.md), [content model](https://github.com/github/docs-content-strategy/blob/main/content-design/models.md), or [localization checklist](https://github.com/github/docs/blob/main/contributing/localization-checklist.md) rendering problems, typos, and wonky screenshots.
- [ ] I've compared my PR's source changes to staging and reviewed for versioning issues, redirects, the [style guide](https://github.com/github/docs/blob/main/contributing/content-style-guide.md), [content model](https://github.com/github/docs/blob/main/contributing/content-model.md), or [localization checklist](https://github.com/github/docs/blob/main/contributing/localization-checklist.md) rendering problems, typos, and wonky screenshots.
- [ ] I've worked through build failures and tests are passing.
- [ ] For REST API content, I've verified that endpoints, parameters, and responses are correct and work as expected and provided curl samples below.

View File

@@ -187,6 +187,8 @@ jobs:
cache-from: type=registry,ref=${{ secrets.NONPROD_REGISTRY_SERVER }}/${{ github.repository }}:main-preview
# `main-docker-cache.yml` handles updating the remote cache so we don't pollute it with PR specific code
cache-to: ''
build-args: |
BUILD_SHA=${{ github.sha }}
# Succeed despite any non-zero exit code (e.g. if there is no deployment to cancel)
- name: 'Cancel any existing deployments for this PR'

View File

@@ -85,6 +85,8 @@ jobs:
tags: ${{ env.DOCKER_IMAGE }}, ${{ env.DOCKER_IMAGE_CACHE_REF }}
cache-from: type=registry,ref=${{ env.DOCKER_IMAGE_CACHE_REF }}
cache-to: type=registry,mode=max,ref=${{ env.DOCKER_IMAGE_CACHE_REF }}
build-args: |
BUILD_SHA=${{ github.sha }}
- name: 'Update docker-compose.prod.yaml template file'
run: |

View File

@@ -16,7 +16,7 @@ permissions:
jobs:
add-issue-comment:
if: ${{ github.repository == 'github/docs' && (github.event.label.name == 'needs SME' && github.event_name == 'issues' || github.event_name == 'pull_request_target') }}
if: ${{ github.repository == 'github/docs' && (github.event.label.name == 'needs SME' && github.event_name == 'issues') }}
runs-on: ubuntu-latest
steps:
- uses: peter-evans/create-or-update-comment@a35cf36e5301d70b76f316e867e7788a55a31dae
@@ -26,7 +26,7 @@ jobs:
Thanks for opening an issue! We've triaged this issue for technical review by a subject matter expert :eyes:
add-pr-comment:
if: ${{ github.repository == 'github/docs' && github.event.label.name == 'needs SME' }}
if: ${{ github.repository == 'github/docs' && (github.event.label.name == 'needs SME' && github.event_name == 'pull_request_target') }}
runs-on: ubuntu-latest
steps:
- uses: peter-evans/create-or-update-comment@a35cf36e5301d70b76f316e867e7788a55a31dae

View File

@@ -78,6 +78,11 @@ ENV PORT 4000
ENV ENABLED_LANGUAGES "en"
# This makes it possible to set `--build-arg BUILD_SHA=abc123`
# and it then becomes available as an environment variable in the docker run.
ARG BUILD_SHA
ENV BUILD_SHA=$BUILD_SHA
# Copy only what's needed to run the server
COPY --chown=node:node package.json ./
COPY --chown=node:node assets ./assets

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 852 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 222 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -9,7 +9,8 @@ import { useTranslation } from 'components/hooks/useTranslation'
export const ProductGuides = () => {
const { title, learningTracks, includeGuides } = useProductGuidesContext()
const { t } = useTranslation('sub_landing')
const { t } = useTranslation('product_guides')
return (
<DefaultLayout>
<LandingSection className="pt-3">

View File

@@ -83,9 +83,9 @@ export function getGHExample(operation: Operation, codeSample: CodeSample) {
requestBodyParams = Object.keys(codeSample.request.bodyParameters)
.map((key) => {
if (typeof codeSample.request.bodyParameters[key] === 'string') {
return `-f ${key}='${codeSample.request.bodyParameters[key]}'`
return `-f ${key}='${codeSample.request.bodyParameters[key]}'\n`
} else {
return `-F ${key}=${codeSample.request.bodyParameters[key]}`
return `-F ${key}=${codeSample.request.bodyParameters[key]}\n`
}
})
.join(' ')
@@ -97,7 +97,9 @@ export function getGHExample(operation: Operation, codeSample: CodeSample) {
requestPath,
requestBodyParams,
].filter(Boolean)
return `gh api \\\n ${args.join(' \\\n ')}`
return `# GitHub CLI api\n# https://cli.github.com/manual/gh_api\n\ngh api \\\n ${args.join(
' \\\n '
)}`
}
/*
@@ -135,8 +137,14 @@ export function getJSExample(operation: Operation, codeSample: CodeSample) {
queryParameters = `{?${queryParms.join(',')}}`
}
}
const comment = `// Octokit.js\n// https://github.com/octokit/core.js#readme\n`
const require = `const octokit = new Octokit(${stringify(
{ auth: 'personal-access-token123' },
null,
2
)})\n\n`
return `await octokit.request('${operation.verb.toUpperCase()} ${
return `${comment}${require}await octokit.request('${operation.verb.toUpperCase()} ${
operation.requestPath
}${queryParameters}', ${stringify(parameters, null, 2)})`
}

View File

@@ -1,45 +0,0 @@
import { Fragment } from 'react'
import type { BodyParameter } from './types'
import { useTranslation } from 'components/hooks/useTranslation'
import { ChildBodyParametersRows } from './ChildBodyParametersRows'
type Props = {
slug: string
bodyParameters: BodyParameter[]
}
export function BodyParameterRows({ slug, bodyParameters }: Props) {
const { t } = useTranslation('products')
return (
<>
{bodyParameters.map((bodyParam) => {
return (
<Fragment key={bodyParam.name + bodyParam.description}>
<tr>
<td>
<code>{bodyParam.name}</code>
</td>
<td>{bodyParam.type}</td>
<td>{bodyParam.in}</td>
<td>
<div dangerouslySetInnerHTML={{ __html: bodyParam.description }} />
{bodyParam.default && (
<p>
{t('rest.reference.default')}: <code>{bodyParam.default}</code>
</p>
)}
</td>
</tr>
{bodyParam.childParamsGroups && bodyParam.childParamsGroups.length > 0 && (
<ChildBodyParametersRows
slug={slug}
childParamsGroups={bodyParam.childParamsGroups}
/>
)}
</Fragment>
)
})}
</>
)
}

View File

@@ -1,4 +1,5 @@
import { useTranslation } from 'components/hooks/useTranslation'
import { ParameterRow } from './ParameterRow'
import type { ChildParamsGroup } from './types'
type Props = {
@@ -12,14 +13,9 @@ export function ChildBodyParametersRows({ slug, childParamsGroups }: Props) {
return (
<tr className="border-none">
<td colSpan={4} className="has-nested-table">
{childParamsGroups?.map((childParamGroup) => {
return (
{childParamsGroups?.map((childParamGroup) => (
<details key={childParamGroup.id}>
<summary
role="button"
aria-expanded="false"
className="keyboard-focus color-fg-muted"
>
<summary role="button" aria-expanded="false" className="keyboard-focus color-fg-muted">
<span className="d-inline-block mb-3" id={`${slug}-${childParamGroup.id}`}>
Properties of the
<code>{childParamGroup.parentName}</code>
@@ -30,32 +26,33 @@ export function ChildBodyParametersRows({ slug, childParamsGroups }: Props) {
id={`${childParamGroup.parentName}-object`}
className="ml-4 mb-4 mt-2 color-bg-subtle"
>
<thead>
<thead className="visually-hidden">
<tr>
<th>
{t('rest.reference.name')} ({t('rest.reference.type')})
{`${t('rest.reference.name')}, ${t('rest.reference.type')}, ${t(
'rest.reference.description'
)}`}
</th>
<th>{t('rest.reference.description')}</th>
</tr>
</thead>
<tbody>
{childParamGroup.params.map((childParam) => {
return (
<tr key={`${childParam.name}-${childParam.description}`}>
<td className="color-bg-subtle">
<code>{childParam.name}</code> ({childParam.type})
</td>
<td className="color-bg-subtle">
<div dangerouslySetInnerHTML={{ __html: childParam.description }} />
</td>
</tr>
)
})}
{childParamGroup.params.map((childParam, index) => (
<ParameterRow
name={childParam.name}
description={childParam.description}
type={childParam.type}
isRequired={childParam.isRequired}
defaultValue={childParam.default}
enumValues={childParam.enum}
slug={slug}
isChild={true}
key={`${index}-${childParam}`}
/>
))}
</tbody>
</table>
</details>
)
})}
))}
</td>
</tr>
)

View File

@@ -1,14 +0,0 @@
.codeBlock {
max-height: 32rem;
overflow: auto;
padding: 1rem;
margin-bottom: 1rem;
line-height: 1.45;
background-color: var(--color-canvas-subtle);
white-space: pre-wrap;
word-break: break-all;
code {
background-color: transparent;
}
}

View File

@@ -1,49 +0,0 @@
import cx from 'classnames'
import { CheckIcon, CopyIcon } from '@primer/octicons-react'
import { Tooltip } from '@primer/react'
import useClipboard from 'components/hooks/useClipboard'
import styles from './CodeBlock.module.scss'
import type { ReactNode } from 'react'
type Props = {
verb?: string
headingLang?: ReactNode | string
codeBlock: string
highlight?: string
}
export function CodeBlock({ verb, headingLang, codeBlock, highlight }: Props) {
const [isCopied, setCopied] = useClipboard(codeBlock, {
successDuration: 1400,
})
return (
<div className={headingLang ? 'code-extra' : undefined}>
{/* Only Code samples should have a copy icon
If there's a headingLang it's a code sample */}
{headingLang && (
<header className="d-flex flex-justify-between flex-items-center p-2 text-small rounded-top-1 border">
{headingLang}
<Tooltip direction="w" aria-label={isCopied ? 'Copied!' : 'Copy to clipboard'}>
<button className="js-btn-copy btn-octicon" onClick={() => setCopied()}>
{isCopied ? <CheckIcon /> : <CopyIcon />}
</button>
</Tooltip>
</header>
)}
<div className={cx(styles.codeBlock, 'rounded-1 border')} data-highlight={highlight}>
<code>
{verb && (
<>
<span className="color-bg-accent-emphasis color-fg-on-emphasis rounded-1 text-uppercase p-1">
{verb}
</span>
<> </>
</>
)}
{codeBlock}
</code>
</div>
</div>
)
}

View File

@@ -0,0 +1,90 @@
import { useTranslation } from 'components/hooks/useTranslation'
import { ChildBodyParametersRows } from './ChildBodyParametersRows'
import type { ChildParamsGroup } from './types'
type Props = {
name: string
type: string | string[]
description: string
isRequired?: boolean
defaultValue?: string
enumValues?: string[]
slug: string
childParamsGroups?: ChildParamsGroup[] | null
numPreviews?: number
isChild?: boolean
}
export function ParameterRow({
name,
type,
description,
isRequired,
defaultValue,
enumValues,
slug,
childParamsGroups = null,
numPreviews = 0,
isChild = false,
}: Props) {
const { t } = useTranslation('products')
return (
<>
<tr className={`${isChild ? 'color-bg-subtle' : ''}`}>
<td className={`${isChild ? 'pl-2' : ''}`}>
<div>
<code className={`text-bold ${isChild ? 'f6' : 'f5'}`}>{name}</code>
<span className="color-fg-muted pl-2 f5">
{Array.isArray(type) ? type.join(' or ') : type}
</span>
{isRequired ? (
<span className={`color-fg-attention f5 ${isChild ? 'pl-3' : 'float-right'}`}>
{t('rest.reference.required')}
</span>
) : null}
</div>
<div className="pl-1 pt-2 color-fg-muted f5">
<div dangerouslySetInnerHTML={{ __html: description }} />
{numPreviews > 0 && (
<a href={`#${slug}-preview-notices`} className="d-inline">
{numPreviews > 1
? ` ${t('rest.reference.see_preview_notices')}`
: ` ${t('rest.reference.see_preview_notice')}`}
</a>
)}
<div className="pt-2">
{defaultValue && (
<p>
<span>{t('rest.reference.default')}: </span>
<code>{defaultValue.toString()}</code>
</p>
)}
{enumValues && (
<p>
<span>{t('rest.reference.enum_description_title')}: </span>
{enumValues.map((item, index) => {
return index !== enumValues.length - 1 ? (
<span key={item + index}>
<code>{item}</code>,{' '}
</span>
) : (
<span key={item + index}>
<code>{item}</code>
</span>
)
})}
</p>
)}
</div>
</div>
</td>
</tr>
{childParamsGroups && childParamsGroups.length > 0 && (
<ChildBodyParametersRows slug={slug} childParamsGroups={childParamsGroups} />
)}
</>
)
}

View File

@@ -1,34 +0,0 @@
import { Parameter } from './types'
import { useTranslation } from 'components/hooks/useTranslation'
type Props = {
parameters: Parameter[]
}
export function ParameterRows({ parameters }: Props) {
const { t } = useTranslation('products')
return (
<>
{parameters.map((param) => (
<tr key={`${param.name}-${param.descriptionHTML}`}>
<td>
<code>{param.name}</code>
</td>
<td>{param.schema.type}</td>
<td>{param.in}</td>
<td>
{param.descriptionHTML && (
<div dangerouslySetInnerHTML={{ __html: param.descriptionHTML }} />
)}
{param.schema.default && (
<p>
{t('rest.reference.default')}: <code>{param.schema.default}</code>
</p>
)}
</td>
</tr>
))}
</>
)
}

View File

@@ -1,33 +0,0 @@
import { useTranslation } from 'components/hooks/useTranslation'
type Props = {
slug: string
numPreviews: number
}
export function PreviewsRow({ slug, numPreviews }: Props) {
const { t } = useTranslation('products')
return (
<tr>
<td>
<code>accept</code>
</td>
<td>string</td>
<td>header</td>
<td>
<p className="m-0">
Setting to
<code>application/vnd.github.v3+json</code> is recommended.
{numPreviews > 0 && (
<a href={`#${slug}-preview-notices`} className="d-inline">
{numPreviews > 1
? ` ${t('rest.reference.see_preview_notices')}`
: ` ${t('rest.reference.see_preview_notice')}`}
</a>
)}
</p>
</td>
</tr>
)
}

View File

@@ -0,0 +1,31 @@
@import "@primer/css/support/index.scss";
.codeBlock {
overflow: auto;
margin-bottom: 1rem;
line-height: 1.45;
background-color: var(--color-canvas-subtle);
font-size: 90%;
code {
background-color: transparent;
padding: 8px 8px 16px;
white-space: pre;
@include breakpoint(lg) {
max-width: 40vw;
}
}
}
.method {
font-size: 80%;
}
.requestCodeBlock {
max-height: 30vh;
min-height: 70px;
}
.responseCodeBlock {
min-height: 70px;
}

View File

@@ -1,92 +1,333 @@
import type { Operation } from './types'
import { useState, useEffect, useRef, FormEvent } from 'react'
import { FormControl, Select, Tooltip, UnderlineNav } from '@primer/react'
import { CheckIcon, CopyIcon } from '@primer/octicons-react'
import Cookies from 'js-cookie'
import cx from 'classnames'
import hljs from 'highlight.js/lib/core'
import json from 'highlight.js/lib/languages/json'
import javascript from 'highlight.js/lib/languages/javascript'
import hljsCurl from 'highlightjs-curl'
import { useTranslation } from 'components/hooks/useTranslation'
import { CodeBlock } from './CodeBlock'
import { getShellExample, getGHExample, getJSExample } from '../lib/get-rest-code-samples'
import useClipboard from 'components/hooks/useClipboard'
import { getShellExample, getGHExample, getJSExample } from 'components/lib/get-rest-code-samples'
import styles from './RestCodeSamples.module.scss'
import { RestMethod } from './RestMethod'
import type { Operation, ExampleT, LanguageOptionT } from './types'
type Props = {
slug: string
operation: Operation
}
const GHCLIKEY = 'ghcli'
const JSKEY = 'javascript'
const CURLKEY = 'curl'
// Add as needed. It's pretty cheap to add but please don't use
// highlight.js import that loads all and everything.
hljs.registerLanguage('json', json)
hljs.registerLanguage('javascript', javascript)
hljs.registerLanguage('curl', hljsCurl)
const responseSelectOptions = [
{ key: 'example', text: 'Example response' },
{ key: 'schema', text: 'Response schema' },
]
function getLanguageHighlight(selectedLanguage: string) {
return selectedLanguage === JSKEY ? 'javascript' : 'curl'
}
export function RestCodeSamples({ operation, slug }: Props) {
const { t } = useTranslation('products')
const JAVASCRIPT_HEADING = (
<span>
JavaScript{' '}
<a className="text-underline" href="https://github.com/octokit/core.js#readme">
@octokit/core.js
</a>
</span>
)
// Refs to track the request example, response example
// and the first render
const requestCodeExample = useRef<HTMLSpanElement>(null)
const responseCodeExample = useRef<HTMLSpanElement>(null)
const firstRender = useRef(true)
const scrollRef = useRef<HTMLDivElement>(null)
const GH_CLI_HEADING = (
<span>
GitHub CLI{' '}
<a className="text-underline" href="https://cli.github.com/manual/gh_api">
gh api
</a>
</span>
)
// Format the example properties into different language examples
const languageExamples = operation.codeExamples.map((sample) => {
const languageExamples = {
// Get format examples for each language
const languageExamples = operation.codeExamples.map((sample) => ({
description: sample.request.description,
curl: getShellExample(operation, sample),
javascript: getJSExample(operation, sample),
ghcli: getGHExample(operation, sample),
response: sample.response,
}))
// Menu options for the language selector
const languageSelectOptions: LanguageOptionT[] = [
{ key: CURLKEY, text: 'cURL' },
{ key: JSKEY, text: 'JavaScript' },
]
// Not all examples support the GH CLI language option. If any of
// the examples don't support it, we don't show GH CLI as an option.
if (!languageExamples.some((example) => example.ghcli === undefined)) {
languageSelectOptions.push({ key: GHCLIKEY, text: 'GitHub CLI' })
}
return Object.assign({}, sample, languageExamples)
// Menu options for the example selector
const exampleSelectOptions = languageExamples.map((example, index) => ({
text: example.description,
// maps to the index of the example in the languageExamples array
languageIndex: index,
}))
const [selectedLanguage, setSelectedLanguage] = useState<keyof ExampleT>(
languageSelectOptions[0].key
)
const [selectedExample, setSelectedExample] = useState(exampleSelectOptions[0])
const [selectedResponse, setSelectedResponse] = useState(responseSelectOptions[0].key)
const [responseMaxHeight, setResponseMaxHeight] = useState(0)
const isSingleExample = languageExamples.length === 1
const displayedExample: ExampleT = languageExamples[selectedExample.languageIndex]
const handleExampleSelection = (event: FormEvent<HTMLSelectElement>) => {
setSelectedExample(exampleSelectOptions[Number(event.currentTarget.value)])
}
const handleResponseSelection = (responseKey: string) => {
setSelectedResponse(responseKey)
}
const handleLanguageSelection = (languageKey: keyof ExampleT) => {
setSelectedLanguage(languageKey)
Cookies.set('codeSampleLanguagePreferred', languageKey, {
sameSite: 'strict',
secure: true,
})
}
const handleResponseResize = () => {
if (requestCodeExample.current) {
const requestCodeHeight = requestCodeExample.current.clientHeight || 0
const { innerHeight: height } = window
if (responseCodeExample) {
// 520 pixels roughly accounts for the space taken up by the
// nav bar, headers, language picker, method section, and response
// picker
setResponseMaxHeight(height - requestCodeHeight - 520)
}
}
}
// Change the language based on cookies
useEffect(() => {
// If the user previously selected a language preference and the language
// is available in this component set it as the selected language
const cookieValue = Cookies.get('codeSampleLanguagePreferred')
const preferredCodeLanguage = languageSelectOptions.find((item) => item.key === cookieValue)
if (cookieValue && preferredCodeLanguage) {
setSelectedLanguage(cookieValue as keyof ExampleT)
}
}, [])
// Handle syntax higlighting when the language changes or
// a cookie is set
useEffect(() => {
const reqElem = requestCodeExample.current
// Do not highlight on the first render because the
// intersection observer syntax highlighting
// (ClientSideHighlightJS) will have already handled highlighting
if (reqElem && !firstRender.current) {
reqElem.className = 'hljs'
hljs.highlightElement(reqElem)
handleResponseResize()
}
}, [selectedLanguage])
// Handle syntax highlighting and scroll position when the language changes or
// a cookie is set, changing the default language
useEffect(() => {
const reqElem = responseCodeExample.current
const scrollElem = scrollRef.current
// Reset scroll position to the top when switching between example response and
// response schema
if (scrollElem) {
scrollElem.scrollTop = 0
}
// Do not highlight on the first render because the
// intersection observer syntax highlighting
// (ClientSideHighlightJS) will have already handled highlighting
if (reqElem && !firstRender.current) {
reqElem.className = 'hljs'
hljs.highlightElement(reqElem)
}
}, [selectedResponse])
// Handle highlighting when there is more than one example and
// the example changes.
useEffect(() => {
const reqElem = requestCodeExample.current
if (reqElem) {
reqElem.className = 'hljs'
hljs.highlightElement(reqElem)
}
const resElem = responseCodeExample.current
if (resElem) {
resElem.className = 'hljs'
hljs.highlightElement(resElem)
}
}, [selectedExample])
// Keep track of the first render so we can skip highlighting
useEffect(() => {
if (firstRender.current) {
firstRender.current = false
}
}, [])
// Handle the resizing of the response section when the window is resized
useEffect(() => {
handleResponseResize()
window.addEventListener('resize', handleResponseResize)
return () => {
window.removeEventListener('resize', handleResponseResize)
}
})
const [isCopied, setCopied] = useClipboard(displayedExample[selectedLanguage] as string, {
successDuration: 1400,
})
return (
<>
<h4 id={`${slug}--code-samples`}>
<h3 className="mt-0 pt-0 h4" id={`${slug}--code-samples`}>
<a href={`#${slug}--code-samples`}>{`${t('rest.reference.code_samples')}`}</a>
</h4>
{languageExamples.map((sample, index) => (
<div key={`${JSON.stringify(sample)}-${index}`}>
</h3>
{/* Display an example selector if more than one example */}
{!isSingleExample && (
<div className="pb-5 pt-2">
<FormControl id="example-type-picker">
<FormControl.Label visuallyHidden>Select the example type</FormControl.Label>
<Select onChange={handleExampleSelection}>
{exampleSelectOptions.map((option) => (
<Select.Option key={option.languageIndex} value={option.languageIndex.toString()}>
{option.text}
</Select.Option>
))}
</Select>
</FormControl>
</div>
)}
{/* Request example section */}
<div className="rounded-1 border">
<div className="my-0 p-3">
<RestMethod verb={operation.verb} requestPath={operation.requestPath} />
</div>
<div className="border-top d-inline-flex flex-justify-between width-full flex-items-center">
<div className="d-inline-flex ml-2">
<UnderlineNav aria-label="Example language selector">
{languageSelectOptions.map((option) => (
<UnderlineNav.Link
key={option.key}
as="button"
onClick={() => {
handleLanguageSelection(option.key)
}}
href={option.key}
selected={option.key === selectedLanguage}
className="pr-3 mr-0 keyboard-focus"
>
{option.text}
</UnderlineNav.Link>
))}
</UnderlineNav>
</div>
<div className="mr-2">
<Tooltip
className="mr-2"
direction="w"
aria-label={isCopied ? 'Copied!' : 'Copy to clipboard'}
>
<button className="js-btn-copy btn-octicon" onClick={() => setCopied()}>
{isCopied ? <CheckIcon /> : <CopyIcon />}
</button>
</Tooltip>
</div>
</div>
{/* Example requests */}
{sample.request && (
<>
{/* Title of the code sample block */}
<h5 dangerouslySetInnerHTML={{ __html: sample.request.description }} />
{sample.curl && (
<CodeBlock headingLang="Shell" codeBlock={sample.curl} highlight="curl" />
)}
{sample.javascript && (
<CodeBlock
headingLang={JAVASCRIPT_HEADING}
codeBlock={sample.javascript}
highlight="javascript"
/>
)}
{sample.ghcli && (
<CodeBlock headingLang={GH_CLI_HEADING} codeBlock={sample.ghcli} highlight="curl" />
)}
</>
<div
className={cx(
styles.codeBlock,
styles.requestCodeBlock,
`border-top rounded-1 my-0 ${getLanguageHighlight(selectedLanguage)}`
)}
data-highlight={getLanguageHighlight(selectedLanguage)}
>
<code ref={requestCodeExample}>{displayedExample[selectedLanguage]}</code>
</div>
</div>
{/* Title of the response */}
{sample.response && (
<>
<h5 dangerouslySetInnerHTML={{ __html: sample.response.description }} />
{/* Response section */}
<h5
dangerouslySetInnerHTML={{
__html: displayedExample.response.description || t('rest.reference.response'),
}}
></h5>
<div className="border rounded-1">
{displayedExample.response.schema ? (
<UnderlineNav aria-label="Example response format selector">
{responseSelectOptions.map((option) => {
if (!displayedExample.response.schema) return null
return (
<UnderlineNav.Link
key={option.key}
as="button"
onClick={() => {
handleResponseSelection(option.key)
}}
href={option.key}
selected={option.key === selectedResponse}
className="pr-3 mr-0 keyboard-focus ml-2"
>
{option.text}
</UnderlineNav.Link>
)
})}
</UnderlineNav>
) : null}
<div className="">
{/* Status code */}
{sample.response.statusCode && (
<CodeBlock codeBlock={`Status: ${sample.response.statusCode}`} />
{displayedExample.response.statusCode && (
<div className={cx(styles.codeBlock, 'rounded-1 p-3 my-0 color-bg-default')}>
<code>{`Status: ${displayedExample.response.statusCode}`}</code>
</div>
)}
{/* Example response */}
{sample.response.example && (
<CodeBlock
codeBlock={JSON.stringify(sample.response.example, null, 2)}
highlight="json"
/>
{displayedExample.response.example && (
<div
ref={scrollRef}
className={cx(
styles.codeBlock,
styles.responseCodeBlock,
'border-top rounded-1 my-0'
)}
</>
data-highlight={'json'}
style={{ maxHeight: responseMaxHeight }}
>
<code ref={responseCodeExample}>
{selectedResponse === 'example'
? JSON.stringify(displayedExample.response.example, null, 2)
: JSON.stringify(displayedExample.response.schema, null, 2)}
</code>
</div>
)}
</div>
))}
</div>
</>
)
}

View File

@@ -0,0 +1,22 @@
import cx from 'classnames'
import styles from './RestCodeSamples.module.scss'
type RestMethodT = {
verb: string
requestPath: string
}
export function RestMethod({ verb, requestPath }: RestMethodT) {
// If the path is long, we want to break it up into multiple lines,
// breaking before the / character.
const displayPath = requestPath.length > 25 ? requestPath.replaceAll('/', '<wbr/>/') : requestPath
return (
<div className={cx(styles.method, 'my-0 text-mono d-flex flex-row flex-items-start ')}>
<span className="IssueLabel IssueLabel--big color-bg-accent-emphasis color-fg-on-emphasis text-uppercase mr-2">
{verb}
</span>
<span dangerouslySetInnerHTML={{ __html: displayPath }} />
</div>
)
}

View File

@@ -1,22 +0,0 @@
import { useRouter } from 'next/router'
import { useTranslation } from 'components/hooks/useTranslation'
import { Link } from 'components/Link'
export function RestNotes() {
const { t } = useTranslation('products')
const router = useRouter()
return (
<>
<h4 className="pt-4">{t('rest.reference.notes')}</h4>
<ul className="mt-2 pl-3 pb-2">
<li>
<Link href={`/${router.locale}/developers/apps`}>
{t('rest.reference.works_with_github_apps')}
</Link>
</li>
</ul>
</>
)
}

View File

@@ -0,0 +1,14 @@
.restOperation {
h2,
h3,
h4 {
a {
text-decoration: none;
color: var(--color-fg-default);
}
}
}
.statusTable {
table-layout: fixed !important;
}

View File

@@ -1,23 +1,25 @@
import { useRouter } from 'next/router'
import slugger from 'github-slugger'
import { CheckCircleFillIcon } from '@primer/octicons-react'
import cx from 'classnames'
import { RestOperationHeading } from './RestOperationHeading'
import { CodeBlock } from './CodeBlock'
import { Link } from 'components/Link'
import { useTranslation } from 'components/hooks/useTranslation'
import { RestPreviewNotice } from './RestPreviewNotice'
import styles from './RestOperation.module.scss'
import { RestParameterTable } from './RestParameterTable'
import { RestCodeSamples } from './RestCodeSamples'
import { RestStatusCodes } from './RestStatusCodes'
import { Operation } from './types'
import { RestNotes } from './RestNotes'
import { RestPreviewNotice } from './RestPreviewNotice'
import { useTranslation } from 'components/hooks/useTranslation'
type Props = {
operation: Operation
}
export function RestOperation({ operation }: Props) {
const { t } = useTranslation('products')
const slug = slugger.slug(operation.title)
const { t } = useTranslation('products')
const router = useRouter()
const numPreviews = operation.previews.length
const hasStatusCodes = operation.statusCodes.length > 0
@@ -25,16 +27,26 @@ export function RestOperation({ operation }: Props) {
const hasParameters = operation.parameters.length > 0 || operation.bodyParameters.length > 0
return (
<div>
<RestOperationHeading
slug={slug}
title={operation.title}
descriptionHTML={operation.descriptionHTML}
/>
{operation.requestPath && (
<CodeBlock verb={operation.verb} codeBlock={operation.requestPath}></CodeBlock>
<div className="pb-8">
<h2 className="d-flex flex-md-row mb-6" id={slug}>
<a href={`#${slug}`}>{operation.title}</a>
</h2>
{operation.enabledForGitHubApps && (
<div className="d-flex">
<span className="mr-2 d-flex flex-items-center">
<CheckCircleFillIcon size={16} />
</span>
<span>
{t('rest.reference.works_with') + ' '}
<Link className="" href={`/${router.locale}/developers/apps`}>
GitHub Apps
</Link>
</span>
</div>
)}
<div className={cx(styles.restOperation, 'd-flex flex-wrap gutter mt-4')}>
<div className="col-md-12 col-lg-6">
<div dangerouslySetInnerHTML={{ __html: operation.descriptionHTML }} />
{hasParameters && (
<RestParameterTable
@@ -45,18 +57,17 @@ export function RestOperation({ operation }: Props) {
/>
)}
{hasStatusCodes && <RestStatusCodes statusCodes={operation.statusCodes} slug={slug} />}
</div>
<div
className="col-md-12 col-lg-6 position-sticky flex-self-start"
style={{ top: '6.5em' }}
>
{hasCodeSamples && <RestCodeSamples operation={operation} slug={slug} />}
{hasStatusCodes && (
<RestStatusCodes
heading={t('rest.reference.status_codes')}
statusCodes={operation.statusCodes}
/>
)}
{operation.enabledForGitHubApps && <RestNotes />}
{numPreviews > 0 && <RestPreviewNotice slug={slug} previews={operation.previews} />}
</div>
</div>
</div>
)
}

View File

@@ -1,21 +0,0 @@
import { LinkIcon } from '@primer/octicons-react'
type Props = {
slug: string
title: string
descriptionHTML: string
}
export function RestOperationHeading({ slug, title, descriptionHTML }: Props) {
return (
<>
<h2 id={slug}>
<a href={`#${slug}`}>
<LinkIcon size={16} className="m-1" />
</a>
{title}
</h2>
<div dangerouslySetInnerHTML={{ __html: descriptionHTML }} />
</>
)
}

View File

@@ -1,44 +0,0 @@
.parameterTable {
table-layout: fixed !important;
z-index: 0;
thead {
tr {
border-top: none;
th {
border: 0;
font-weight: normal;
}
}
}
tr:nth-child(2n) {
background: none !important;
}
td {
padding: 0.75rem 0.5rem !important;
border: 0 !important;
vertical-align: top;
width: 100%;
}
tbody {
tr td {
width: auto;
}
tr td:first-child {
font-weight: bold;
}
table tr td:not(:first-child) {
font-weight: normal;
}
}
code {
background-color: var(--color-canvas-subtle);
}
}

View File

@@ -1,10 +1,8 @@
import cx from 'classnames'
import { useTranslation } from 'components/hooks/useTranslation'
import { ParameterRow } from './ParameterRow'
import { BodyParameter, Parameter } from './types'
import styles from './RestParameterTable.module.scss'
import { PreviewsRow } from './PreviewsRow'
import { ParameterRows } from './ParameterRows'
import { BodyParameterRows } from './BodyParametersRows'
type Props = {
slug: string
@@ -15,25 +13,131 @@ type Props = {
export function RestParameterTable({ slug, numPreviews, parameters, bodyParameters }: Props) {
const { t } = useTranslation('products')
const queryParams = parameters.filter((param) => param.in === 'query')
const pathParams = parameters.filter((param) => param.in === 'path')
return (
<>
<h4 className="mt-4 mb-3 pt-3" id={`${slug}--parameters`}>
<h3 className="mt-4 mb-3 pt-3 h4" id={`${slug}--parameters`}>
<a href={`#${slug}--parameters`}>{t('rest.reference.parameters')}</a>
</h4>
<table className={cx(styles.parameterTable)}>
</h3>
<table
className={cx('d-block')}
summary="Column one has the type of parameter and the other columns show the name, type, and description of the parameters"
>
<thead>
<tr className="text-left">
<th>{t('rest.reference.name')}</th>
<th>{t('rest.reference.type')}</th>
<th>{t('rest.reference.in')}</th>
<th>{t('rest.reference.description')}</th>
<tr>
<th id="header" scope="col" className="text-bold pl-0">
{t('rest.reference.headers')}
</th>
</tr>
<tr className="visually-hidden">
<th scope="col">
{`${t('rest.reference.name')}, ${t('rest.reference.type')}, ${t(
'rest.reference.description'
)}`}
</th>
</tr>
</thead>
<tbody>
<PreviewsRow slug={slug} numPreviews={numPreviews} />
<ParameterRows parameters={parameters} />
<BodyParameterRows slug={slug} bodyParameters={bodyParameters} />
<ParameterRow
name={'accept'}
type={'string'}
description={`<p>Setting to <code>application/vnd.github.v3+json</code> is recommended.</p>`}
isRequired={false}
slug={slug}
numPreviews={numPreviews}
/>
{pathParams.length > 0 && (
<>
<tr className="border-top-0">
<th className="text-bold pl-0 border-top-0" scope="colgroup">
{t('rest.reference.path')}
</th>
</tr>
<tr className="visually-hidden">
<th scope="colgroup">
{`${t('rest.reference.name')}, ${t('rest.reference.type')}, ${t(
'rest.reference.description'
)}`}
</th>
</tr>
{pathParams.map((param, index) => (
<ParameterRow
name={param.name}
type={param.schema.type}
description={param.description}
isRequired={param.required}
defaultValue={param.schema.default}
enumValues={param.schema.enum}
slug={slug}
key={`${index}-${param}`}
/>
))}
</>
)}
{queryParams.length > 0 && (
<>
<tr className="border-top-0">
<th className="text-bold pl-0" scope="colgroup">
{t('rest.reference.query')}
</th>
</tr>
<tr className="visually-hidden">
<th scope="colgroup">
{`${t('rest.reference.name')}, ${t('rest.reference.type')}, ${t(
'rest.reference.description'
)}`}
</th>
</tr>
{queryParams.map((param, index) => (
<ParameterRow
name={param.name}
type={param.schema.type}
description={param.description}
isRequired={param.required}
defaultValue={param.schema.default}
enumValues={param.schema.enum}
slug={slug}
key={`${index}-${param}`}
/>
))}
</>
)}
{bodyParameters.length > 0 && (
<>
<tr className="border-top-0">
<th scope="colgroup" className="text-bold pl-0">
{t('rest.reference.body')}
</th>
</tr>
<tr className="visually-hidden">
<th scope="colgroup">
{`${t('rest.reference.name')}, ${t('rest.reference.type')}, ${t(
'rest.reference.description'
)}`}
</th>
</tr>
{bodyParameters.map((param, index) => (
<ParameterRow
name={param.name}
type={param.type}
description={param.description}
isRequired={param.isRequired}
defaultValue={param.default}
enumValues={param.enum}
slug={slug}
childParamsGroups={param.childParamsGroups}
key={`${index}-${param}`}
/>
))}
</>
)}
</tbody>
</table>
</>

View File

@@ -7,14 +7,15 @@ type Props = {
export function RestPreviewNotice({ slug, previews }: Props) {
const { t } = useTranslation('products')
return (
<>
<h4 id={`${slug}-preview-notices`}>
<h3 className="h4" id={`${slug}-preview-notices`}>
<a href={`${slug}-preview-notices`}>
{previews.length > 1
? `${t('rest.reference.preview_notices')}`
: `${t('rest.reference.preview_notice')}`}
</h4>
</a>
</h3>
{previews.map((preview, index) => (
<div
className="extended-markdown note border rounded-1 mb-6 p-3 color-border-accent-emphasis color-bg-accent f5"

View File

@@ -1,13 +1,15 @@
import React, { useState, useEffect } from 'react'
import { useRouter } from 'next/router'
import dynamic from 'next/dynamic'
import cx from 'classnames'
import { DefaultLayout } from 'components/DefaultLayout'
import { MarkdownContent } from 'components/ui/MarkdownContent'
import { Lead } from 'components/ui/Lead'
import { RestOperation } from './RestOperation'
import styles from './RestOperation.module.scss'
import { useRestContext } from 'components/context/RestContext'
import { Operation } from './types'
import { RestOperation } from './RestOperation'
const ClientSideHighlightJS = dynamic(() => import('components/article/ClientSideHighlightJS'), {
ssr: false,
@@ -95,7 +97,7 @@ export const RestReferencePage = ({ restOperations }: StructuredContentT) => {
never render anything. It always just return null. */}
{loadClientsideRedirectExceptions && <ClientSideRedirectExceptions />}
{lazyLoadHighlightJS && <ClientSideHighlightJS />}
<div className={'px-3 px-md-6 my-4 mx-xl-12 mx-lg-12'}>
<div className={cx(styles.restOperation, 'px-3 px-md-6 my-4 container-xl')}>
<h1 className="mb-3">{title}</h1>
{intro && (
<Lead data-testid="lead" data-search="lead" className="markdown-body">
@@ -103,14 +105,18 @@ export const RestReferencePage = ({ restOperations }: StructuredContentT) => {
</Lead>
)}
<MarkdownContent>
<MarkdownContent>{renderedPage}</MarkdownContent>
{renderedPage && <MarkdownContent className="pt-3 pb-4">{renderedPage}</MarkdownContent>}
{restOperations &&
restOperations.length > 0 &&
restOperations.map((operation, index) => (
<React.Fragment
key={`${operation.title}-${operation.category}-${operation.subcategory}`}
>
<RestOperation
key={`restOperation-${operation.title}-${index}`}
operation={operation}
/>
</React.Fragment>
))}
</MarkdownContent>
</div>

View File

@@ -1,49 +0,0 @@
.restResponseTable {
table-layout: fixed !important;
thead {
tr {
border-top: none;
th {
border: 0;
font-weight: normal;
background-color: transparent;
}
th:first-child {
width: 25%;
}
th:nth-child(2) {
width: 75%;
}
}
}
tr:nth-child(2n) {
background: none !important;
}
td {
padding: 0.75rem 0.5rem !important;
border: 0 !important;
vertical-align: top;
width: 100%;
}
tbody {
tr td:first-child {
width: 30%;
font-weight: bold;
}
tr td:nth-child(2) {
width: 70%;
}
table tr td:not(:first-child) {
font-weight: normal;
}
}
}

View File

@@ -1,34 +1,37 @@
import cx from 'classnames'
import { StatusCode } from './types'
import { useTranslation } from 'components/hooks/useTranslation'
import styles from './RestResponseTable.module.scss'
import styles from './RestOperation.module.scss'
import { StatusCode } from './types'
type Props = {
heading: string
statusCodes: Array<StatusCode>
slug: string
}
export function RestStatusCodes({ heading, statusCodes }: Props) {
export function RestStatusCodes({ statusCodes, slug }: Props) {
const { t } = useTranslation('products')
return (
<>
<h4>{heading}</h4>
<table className={cx(styles.restResponseTable)}>
<h3 className="mt-4 mb-3 pt-3 h4" id={`${slug}--status-codes`}>
<a href={`#${slug}--status-codes`}>{t('rest.reference.http_status_code')}</a>
</h3>
<table className={cx(styles.restResponseTable, 'd-block')}>
<thead>
<tr className="text-left">
<th>{t('rest.reference.http_status_code')}</th>
<th>{t('rest.reference.status_code')}</th>
<th>{t('rest.reference.description')}</th>
</tr>
</thead>
<tbody>
{statusCodes.map((statusCode, index) => {
return (
{statusCodes.map((statusCode, index) => (
<tr key={`${statusCode.description}-${index}}`}>
<td>
<code>{statusCode.httpStatusCode}</code>
</td>
<td>
<td className="color-fg-muted">
{statusCode.description ? (
<div dangerouslySetInnerHTML={{ __html: statusCode.description }} />
) : (
@@ -36,8 +39,7 @@ export function RestStatusCodes({ heading, statusCodes }: Props) {
)}
</td>
</tr>
)
})}
))}
</tbody>
</table>
</>

View File

@@ -17,11 +17,12 @@ export interface Operation {
export interface Parameter {
in: string
name: string
descriptionHTML: string
description: string
required: boolean
schema: {
type: string
default?: string
enum?: Array<string>
}
}
@@ -51,10 +52,12 @@ export interface CodeSample {
export interface BodyParameter {
in: string
name: string
childParamsGroups?: Array<ChildParamsGroup>
default?: string
description: string
type: string
isRequired: boolean
default?: string
enum?: Array<string>
childParamsGroups?: Array<ChildParamsGroup>
}
export interface ChildParamsGroup {
@@ -68,4 +71,26 @@ export interface ChildParameter {
name: string
description: string
type: string
isRequired: boolean
enum?: Array<string>
default?: string
}
export type ExampleT = {
description: string
curl: string
javascript: string
ghcli?: string
response: {
statusCode: string
contentType?: string
description: string
example?: Object
schema?: Object
}
}
export type LanguageOptionT = {
key: keyof ExampleT
text: string
}

View File

@@ -29,6 +29,8 @@ export const RestCollapsibleSection = (props: SectionProps) => {
const { routePath, defaultOpen, title, page, isStandaloneCategory } = props
const [isOpen, setIsOpen] = useState(defaultOpen)
const [currentAnchor, setCurrentAnchor] = useState('')
const [visibleAnchor, setVisibleAnchor] = useState('')
const onToggle = (e: SyntheticEvent) => {
const newIsOpen = (e.target as HTMLDetailsElement).open
setIsOpen(newIsOpen)
@@ -38,6 +40,14 @@ export const RestCollapsibleSection = (props: SectionProps) => {
})
}
const miniTocItems =
router.query.productId === 'rest' ||
// These pages need the Article Page mini tocs instead of the Rest Pages
router.asPath.includes('/rest/guides') ||
router.asPath.includes('/rest/overview')
? []
: useRestContext().miniTocItems
useEffect(() => {
if (!currentAnchor) {
setCurrentAnchor(window.location.hash)
@@ -54,14 +64,36 @@ export const RestCollapsibleSection = (props: SectionProps) => {
}
}, [])
const miniTocItems =
router.query.productId === 'rest' ||
// These pages need the Article Page mini tocs instead of the Rest Pages
router.asPath.includes('/rest/guides') ||
router.asPath.includes('/rest/overview')
? []
: useRestContext().miniTocItems
useEffect(() => {
if (!router.asPath.includes('guides') && !router.asPath.includes('overview')) {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.target.id) {
const anchor = '#' + entry.target.id.split('--')[0]
if (entry.isIntersecting === true) setVisibleAnchor(anchor)
} else if (router.asPath.includes('#')) {
setVisibleAnchor('#' + router.asPath.split('#')[1])
} else {
setVisibleAnchor('')
}
})
},
{ rootMargin: '0px 0px -85% 0px' }
)
// TODO: When we add the ## About the {title} API to each operation
// we can remove the h2 here
const headingsList = Array.from(document.querySelectorAll('h2, h3'))
headingsList.forEach((heading) => {
observer.observe(heading)
})
return () => {
observer.disconnect()
}
}
}, [miniTocItems])
// This wrapper solves the issue of having standalone categories not
// link to the new page. We want standalone categories to have links
// just like maptopics/subcategories.
@@ -71,8 +103,7 @@ export const RestCollapsibleSection = (props: SectionProps) => {
const renderRestAnchorLink = (miniTocItem: MiniTocItem) => {
const miniTocAnchor = miniTocItem.contents.href
const title = miniTocItem.contents.title
const isCurrent = currentAnchor === miniTocAnchor
const isCurrent = visibleAnchor === miniTocAnchor
return {
text: title,
renderItem: () => (
@@ -96,6 +127,7 @@ export const RestCollapsibleSection = (props: SectionProps) => {
'd-block pl-6 pr-5 py-1 no-underline width-full',
isCurrent ? 'color-fg-accent' : 'color-fg-default'
)}
onClick={() => setVisibleAnchor(miniTocAnchor)}
href={miniTocAnchor}
>
{title}
@@ -117,7 +149,10 @@ export const RestCollapsibleSection = (props: SectionProps) => {
)}
>
<div className="d-flex flex-justify-between">
<div className="pl-4 pr-1 py-2 f5 d-block flex-auto mr-3 color-fg-default no-underline text-bold">
<div
data-testid="rest-category"
className="pl-4 pr-1 py-2 f5 d-block flex-auto mr-3 color-fg-default no-underline text-bold"
>
{title}
</div>
<span style={{ marginTop: 7 }} className="flex-shrink-0 pr-3">
@@ -132,7 +167,7 @@ export const RestCollapsibleSection = (props: SectionProps) => {
{/* <!-- Render the maptopic level subcategory operation links e.g. --> */}
<ul className="list-style-none position-relative">
{page.childPages.length <= 0 ? (
<div data-testid="sidebar-article-group" className="pb-0">
<div className="pb-0">
{miniTocItems.length > 0 && (
<ActionList
{...{ as: 'ul' }}
@@ -159,12 +194,7 @@ export const RestCollapsibleSection = (props: SectionProps) => {
className="details-reset"
>
<summary>
<div
data-testid="sidebar-rest-subcategory"
className={cx('pl-4 pr-5 py-2 no-underline')}
>
{childTitle}
</div>
<div className={cx('pl-4 pr-5 py-2 no-underline')}>{childTitle}</div>
</summary>
<div className="pb-0">
{miniTocItems.length > 0 && (
@@ -183,7 +213,7 @@ export const RestCollapsibleSection = (props: SectionProps) => {
// We're not on the current page so don't have any minitoc
// data so just render a link to the category page.
return (
<li key={childTitle} data-testid="sidebar-article-group" className="pb-0">
<li data-testid="rest-subcategory" key={childTitle} className="pb-0">
<Link
href={childPage.href}
className={cx(

View File

@@ -87,7 +87,7 @@ export const SidebarProduct = () => {
)
return (
<>
<li className="my-3" data-testid="rest-sidebar-items">
<li className="my-3">
<ul className="list-style-none">
{conceptualPages.map((childPage, i) => {
const isStandaloneCategory = childPage.page.documentType === 'article'
@@ -135,7 +135,6 @@ export const SidebarProduct = () => {
const defaultOpen = hasExactCategory ? isActive : false
return (
<li
data-testid="rest-sidebar-items"
key={childPage.href + i}
data-is-active-category={isActive}
data-is-current-page={isActive && isStandaloneCategory}

View File

@@ -2,6 +2,7 @@
[class~="highlight"] pre,
pre {
margin-top: 0.5rem;
border: 1px var(--color-border-default) solid;
}
[class~="height-constrained-code-block"] pre {

View File

@@ -51,7 +51,7 @@ You can also view whether an enterprise owner has a specific role in the organiz
| Enterprise owner | Organization owner | Able to configure organization settings and manage access to the organization's resources through teams, etc. |
| Enterprise owner | Organization member | Able to access organization resources and content, such as repositories, without access to the organization's settings. |
To review all roles in an organization, see "[Roles in an organization](/organizations/managing-peoples-access-to-your-organization-with-roles/roles-in-an-organization)." {% ifversion ghec %} An organization member can also have a custom role for a specific repository. For more information, see "[Managing custom repository roles for an organization](/organizations/managing-peoples-access-to-your-organization-with-roles/managing-custom-repository-roles-for-an-organization)."{% endif %}
To review all roles in an organization, see "[Roles in an organization](/organizations/managing-peoples-access-to-your-organization-with-roles/roles-in-an-organization)." {% if custom-repository-roles %} An organization member can also have a custom role for a specific repository. For more information, see "[Managing custom repository roles for an organization](/organizations/managing-peoples-access-to-your-organization-with-roles/managing-custom-repository-roles-for-an-organization)."{% endif %}
For more information about the enterprise owner role, see "[Roles in an enterprise](/admin/user-management/managing-users-in-your-enterprise/roles-in-an-enterprise#enterprise-owner)."

View File

@@ -22,7 +22,7 @@ shortTitle: Build & test Java & Gradle
## Introduction
This guide shows you how to create a workflow that performs continuous integration (CI) for your Java project using the Gradle build system. The workflow you create will allow you to see when commits to a pull request cause build or test failures against your default branch; this approach can help ensure that your code is always healthy. You can extend your CI workflow to cache files and upload artifacts from a workflow run.
This guide shows you how to create a workflow that performs continuous integration (CI) for your Java project using the Gradle build system. The workflow you create will allow you to see when commits to a pull request cause build or test failures against your default branch; this approach can help ensure that your code is always healthy. You can extend your CI workflow to {% if actions-caching %}cache files and{% endif %} upload artifacts from a workflow run.
{% ifversion ghae %}
{% data reusables.actions.self-hosted-runners-software %}
@@ -110,12 +110,16 @@ steps:
arguments: -b ci.gradle package
```
{% if actions-caching %}
## Caching dependencies
When using {% data variables.product.prodname_dotcom %}-hosted runners, your build dependencies can be cached to speed up your workflow runs. After a successful run, the `gradle/gradle-build-action` caches important parts of the Gradle user home directory. In future jobs, the cache will be restored so that build scripts won't need to be recompiled and dependencies won't need to be downloaded from remote package repositories.
Your build dependencies can be cached to speed up your workflow runs. After a successful run, the `gradle/gradle-build-action` caches important parts of the Gradle user home directory. In future jobs, the cache will be restored so that build scripts won't need to be recompiled and dependencies won't need to be downloaded from remote package repositories.
Caching is enabled by default when using the `gradle/gradle-build-action` action. For more information, see [`gradle/gradle-build-action`](https://github.com/gradle/gradle-build-action#caching).
{% endif %}
## Packaging workflow data as artifacts
After your build has succeeded and your tests have passed, you may want to upload the resulting Java packages as a build artifact. This will store the built packages as part of the workflow run, and allow you to download them. Artifacts can help you test and debug pull requests in your local environment before they're merged. For more information, see "[Persisting workflow data using artifacts](/actions/automating-your-workflow-with-github-actions/persisting-workflow-data-using-artifacts)."

View File

@@ -22,7 +22,7 @@ shortTitle: Build & test Java with Maven
## Introduction
This guide shows you how to create a workflow that performs continuous integration (CI) for your Java project using the Maven software project management tool. The workflow you create will allow you to see when commits to a pull request cause build or test failures against your default branch; this approach can help ensure that your code is always healthy. You can extend your CI workflow to cache files and upload artifacts from a workflow run.
This guide shows you how to create a workflow that performs continuous integration (CI) for your Java project using the Maven software project management tool. The workflow you create will allow you to see when commits to a pull request cause build or test failures against your default branch; this approach can help ensure that your code is always healthy. You can extend your CI workflow to {% if actions-caching %}cache files and{% endif %} upload artifacts from a workflow run.
{% ifversion ghae %}
{% data reusables.actions.self-hosted-runners-software %}
@@ -99,9 +99,11 @@ steps:
run: mvn --batch-mode --update-snapshots verify
```
{% if actions-caching %}
## Caching dependencies
When using {% data variables.product.prodname_dotcom %}-hosted runners, you can cache your dependencies to speed up your workflow runs. After a successful run, your local Maven repository will be stored on GitHub Actions infrastructure. In future workflow runs, the cache will be restored so that dependencies don't need to be downloaded from remote Maven repositories. You can cache dependencies simply using the [`setup-java` action](https://github.com/marketplace/actions/setup-java-jdk) or can use [`cache` action](https://github.com/actions/cache) for custom and more advanced configuration.
You can cache your dependencies to speed up your workflow runs. After a successful run, your local Maven repository will be stored in a cache. In future workflow runs, the cache will be restored so that dependencies don't need to be downloaded from remote Maven repositories. You can cache dependencies simply using the [`setup-java` action](https://github.com/marketplace/actions/setup-java-jdk) or can use [`cache` action](https://github.com/actions/cache) for custom and more advanced configuration.
```yaml{:copy}
steps:
@@ -118,6 +120,8 @@ steps:
This workflow will save the contents of your local Maven repository, located in the `.m2` directory of the runner's home directory. The cache key will be the hashed contents of _pom.xml_, so changes to _pom.xml_ will invalidate the cache.
{% endif %}
## Packaging workflow data as artifacts
After your build has succeeded and your tests have passed, you may want to upload the resulting Java packages as a build artifact. This will store the built packages as part of the workflow run, and allow you to download them. Artifacts can help you test and debug pull requests in your local environment before they're merged. For more information, see "[Persisting workflow data using artifacts](/actions/automating-your-workflow-with-github-actions/persisting-workflow-data-using-artifacts)."

View File

@@ -120,7 +120,7 @@ steps:
run: dotnet add package Newtonsoft.Json --version 12.0.1
```
{% ifversion fpt or ghec %}
{% if actions-caching %}
### Caching dependencies

View File

@@ -136,7 +136,7 @@ If you don't specify a Node.js version, {% data variables.product.prodname_dotco
{% data variables.product.prodname_dotcom %}-hosted runners have npm and Yarn dependency managers installed. You can use npm and Yarn to install dependencies in your workflow before building and testing your code. The Windows and Linux {% data variables.product.prodname_dotcom %}-hosted runners also have Grunt, Gulp, and Bower installed.
When using {% data variables.product.prodname_dotcom %}-hosted runners, you can also cache dependencies to speed up your workflow. For more information, see "<a href="/actions/guides/caching-dependencies-to-speed-up-workflows" class="dotcom-only">Caching dependencies to speed up workflows</a>."
{% if actions-caching %}You can also cache dependencies to speed up your workflow. For more information, see "[Caching dependencies to speed up workflows](/actions/using-workflows/caching-dependencies-to-speed-up-workflows)."{% endif %}
### Example using npm
@@ -228,9 +228,11 @@ The example above creates an *.npmrc* file with the following contents:
always-auth=true
```
{% if actions-caching %}
### Example caching dependencies
When using {% data variables.product.prodname_dotcom %}-hosted runners, you can cache and restore the dependencies using the [`setup-node` action](https://github.com/actions/setup-node).
You can cache and restore the dependencies using the [`setup-node` action](https://github.com/actions/setup-node).
The following example caches dependencies for npm.
@@ -278,7 +280,9 @@ steps:
- run: pnpm test
```
If you have a custom requirement or need finer controls for caching, you can use the [`cache` action](https://github.com/marketplace/actions/cache). For more information, see "<a href="/actions/guides/caching-dependencies-to-speed-up-workflows" class="dotcom-only">Caching dependencies to speed up workflows</a>".
If you have a custom requirement or need finer controls for caching, you can use the [`cache` action](https://github.com/marketplace/actions/cache). For more information, see "[Caching dependencies to speed up workflows](/actions/using-workflows/caching-dependencies-to-speed-up-workflows)."
{% endif %}
## Building and testing your code

View File

@@ -104,7 +104,7 @@ The table below describes the locations for various PowerShell modules in each {
{% endnote %}
When using {% data variables.product.prodname_dotcom %}-hosted runners, you can also cache dependencies to speed up your workflow. For more information, see "<a href="/actions/guides/caching-dependencies-to-speed-up-workflows" class="dotcom-only">Caching dependencies to speed up workflows</a>."
{% if actions-caching %}You can also cache dependencies to speed up your workflow. For more information, see "[Caching dependencies to speed up workflows](/actions/using-workflows/caching-dependencies-to-speed-up-workflows)."{% endif %}
For example, the following job installs the `SqlServer` and `PSScriptAnalyzer` modules:
@@ -128,9 +128,11 @@ jobs:
{% endnote %}
{% if actions-caching %}
### Caching dependencies
When using {% data variables.product.prodname_dotcom %}-hosted runners, you can cache PowerShell dependencies using a unique key, which allows you to restore the dependencies for future workflows with the [`cache`](https://github.com/marketplace/actions/cache) action. For more information, see "<a href="/actions/guides/caching-dependencies-to-speed-up-workflows" class="dotcom-only">Caching dependencies to speed up workflows</a>."
You can cache PowerShell dependencies using a unique key, which allows you to restore the dependencies for future workflows with the [`cache`](https://github.com/marketplace/actions/cache) action. For more information, see "[Caching dependencies to speed up workflows](/actions/using-workflows/caching-dependencies-to-speed-up-workflows)."
PowerShell caches its dependencies in different locations, depending on the runner's operating system. For example, the `path` location used in the following Ubuntu example will be different for a Windows operating system.
@@ -151,6 +153,8 @@ steps:
Install-Module SqlServer, PSScriptAnalyzer -ErrorAction Stop
```
{% endif %}
## Testing your code
You can use the same commands that you use locally to build and test your code.

View File

@@ -197,7 +197,7 @@ We recommend using `setup-python` to configure the version of Python used in you
{% data variables.product.prodname_dotcom %}-hosted runners have the pip package manager installed. You can use pip to install dependencies from the PyPI package registry before building and testing your code. For example, the YAML below installs or upgrades the `pip` package installer and the `setuptools` and `wheel` packages.
When using {% data variables.product.prodname_dotcom %}-hosted runners, you can also cache dependencies to speed up your workflow. For more information, see "<a href="/actions/guides/caching-dependencies-to-speed-up-workflows" class="dotcom-only">Caching dependencies to speed up workflows</a>."
{% if actions-caching %}You can also cache dependencies to speed up your workflow. For more information, see "[Caching dependencies to speed up workflows](/actions/using-workflows/caching-dependencies-to-speed-up-workflows)."{% endif %}
```yaml{:copy}
steps:
@@ -227,9 +227,11 @@ steps:
pip install -r requirements.txt
```
{% if actions-caching %}
### Caching Dependencies
When using {% data variables.product.prodname_dotcom %}-hosted runners, you can cache and restore the dependencies using the [`setup-python` action](https://github.com/actions/setup-python).
You can cache and restore the dependencies using the [`setup-python` action](https://github.com/actions/setup-python).
The following example caches dependencies for pip.
@@ -244,10 +246,12 @@ steps:
- run: pip test
```
By default, the `setup-python` action searches for the dependency file (`requirements.txt` for pip or `Pipfile.lock` for pipenv) in the whole repository. For more information, see "<a href="/actions/guides/caching-dependencies-to-speed-up-workflows" class="dotcom-only">Caching packages dependencies</a>" in the `setup-python` actions README.
By default, the `setup-python` action searches for the dependency file (`requirements.txt` for pip or `Pipfile.lock` for pipenv) in the whole repository. For more information, see "[Caching packages dependencies](https://github.com/actions/setup-python#caching-packages-dependencies)" in the `setup-python` README.
If you have a custom requirement or need finer controls for caching, you can use the [`cache` action](https://github.com/marketplace/actions/cache). Pip caches dependencies in different locations, depending on the operating system of the runner. The path you'll need to cache may differ from the Ubuntu example above, depending on the operating system you use. For more information, see [Python caching examples](https://github.com/actions/cache/blob/main/examples.md#python---pip) in the `cache` action repository.
{% endif %}
## Testing your code
You can use the same commands that you use locally to build and test your code.

View File

@@ -144,9 +144,11 @@ steps:
- run: bundle install
```
{% if actions-caching %}
### Caching dependencies
If you are using {% data variables.product.prodname_dotcom %}-hosted runners, the `setup-ruby` actions provides a method to automatically handle the caching of your gems between runs.
The `setup-ruby` actions provides a method to automatically handle the caching of your gems between runs.
To enable caching, set the following.
@@ -159,11 +161,11 @@ steps:
```
{% endraw %}
This will configure bundler to install your gems to `vendor/cache`. For each successful run of your workflow, this folder will be cached by Actions and re-downloaded for subsequent workflow runs. A hash of your gemfile.lock and the Ruby version are used as the cache key. If you install any new gems, or change a version, the cache will be invalidated and bundler will do a fresh install.
This will configure bundler to install your gems to `vendor/cache`. For each successful run of your workflow, this folder will be cached by {% data variables.product.prodname_actions %} and re-downloaded for subsequent workflow runs. A hash of your gemfile.lock and the Ruby version are used as the cache key. If you install any new gems, or change a version, the cache will be invalidated and bundler will do a fresh install.
**Caching without setup-ruby**
For greater control over caching, if you are using {% data variables.product.prodname_dotcom %}-hosted runners, you can use the `actions/cache` Action directly. For more information, see "<a href="/actions/guides/caching-dependencies-to-speed-up-workflows" class="dotcom-only">Caching dependencies to speed up workflows</a>."
For greater control over caching, you can use the `actions/cache` action directly. For more information, see "[Caching dependencies to speed up workflows](/actions/using-workflows/caching-dependencies-to-speed-up-workflows)."
```yaml
steps:
@@ -195,6 +197,8 @@ steps:
bundle install --jobs 4 --retry 3
```
{% endif %}
## Matrix testing your code
The following example matrix tests all stable releases and head versions of MRI, JRuby and TruffleRuby on Ubuntu and macOS.

View File

@@ -75,7 +75,7 @@ Before you begin, you'll create a repository on {% ifversion ghae %}{% data vari
outputs:
random-number:
description: "Random number"
value: ${{ steps.random-number-generator.outputs.random-id }}
value: ${{ steps.random-number-generator.outputs.random-number }}
runs:
using: "composite"
steps:

View File

@@ -286,7 +286,7 @@ steps:
**Example: Using status check functions**
The `my backup step` only runs when the previous step of a composite action fails. For more information, see "[Expressions](/actions/learn-github-actions/expressions#job-status-check-functions)."
The `my backup step` only runs when the previous step of a composite action fails. For more information, see "[Expressions](/actions/learn-github-actions/expressions#status-check-functions)."
```yaml
steps:

View File

@@ -31,7 +31,7 @@ You can configure your CD workflow to run when a {% data variables.product.produ
{% data variables.product.prodname_actions %} provides features that give you more control over deployments. For example, you can use environments to require approval for a job to proceed, restrict which branches can trigger a workflow, or limit access to secrets. {% ifversion fpt or ghae or ghes > 3.1 or ghec %}You can use concurrency to limit your CD pipeline to a maximum of one in-progress deployment and one pending deployment. {% endif %}For more information about these features, see "[Deploying with GitHub Actions](/actions/deployment/deploying-with-github-actions)" and "[Using environments for deployment](/actions/deployment/using-environments-for-deployment)."
{% ifversion fpt or ghec or ghae-issue-4856 %}
{% ifversion fpt or ghec or ghae-issue-4856 or ghes > 3.4 %}
## Using OpenID Connect to access cloud resources

View File

@@ -26,7 +26,7 @@ This guide explains how to use {% data variables.product.prodname_actions %} to
On every new push to `main` in your {% data variables.product.company_short %} repository, the {% data variables.product.prodname_actions %} workflow builds and pushes a new container image to Amazon ECR, and then deploys a new task definition to Amazon ECS.
{% ifversion fpt or ghec or ghae-issue-4856 %}
{% ifversion fpt or ghec or ghae-issue-4856 or ghes > 3.4 %}
{% note %}

View File

@@ -21,7 +21,7 @@ topics:
This guide explains how to use {% data variables.product.prodname_actions %} to build and deploy a Docker container to [Azure App Service](https://azure.microsoft.com/services/app-service/).
{% ifversion fpt or ghec or ghae-issue-4856 %}
{% ifversion fpt or ghec or ghae-issue-4856 or ghes > 3.4 %}
{% note %}

View File

@@ -20,7 +20,7 @@ topics:
This guide explains how to use {% data variables.product.prodname_actions %} to build and deploy a Java project to [Azure App Service](https://azure.microsoft.com/services/app-service/).
{% ifversion fpt or ghec or ghae-issue-4856 %}
{% ifversion fpt or ghec or ghae-issue-4856 or ghes > 3.4 %}
{% note %}

View File

@@ -19,7 +19,7 @@ topics:
This guide explains how to use {% data variables.product.prodname_actions %} to build and deploy a .NET project to [Azure App Service](https://azure.microsoft.com/services/app-service/).
{% ifversion fpt or ghec or ghae-issue-4856 %}
{% ifversion fpt or ghec or ghae-issue-4856 or ghes > 3.4 %}
{% note %}

View File

@@ -25,7 +25,7 @@ topics:
This guide explains how to use {% data variables.product.prodname_actions %} to build, test, and deploy a Node.js project to [Azure App Service](https://azure.microsoft.com/services/app-service/).
{% ifversion fpt or ghec or ghae-issue-4856 %}
{% ifversion fpt or ghec or ghae-issue-4856 or ghes > 3.4 %}
{% note %}

View File

@@ -19,7 +19,7 @@ topics:
This guide explains how to use {% data variables.product.prodname_actions %} to build and deploy a PHP project to [Azure App Service](https://azure.microsoft.com/services/app-service/).
{% ifversion fpt or ghec or ghae-issue-4856 %}
{% ifversion fpt or ghec or ghae-issue-4856 or ghes > 3.4 %}
{% note %}

View File

@@ -20,7 +20,7 @@ topics:
This guide explains how to use {% data variables.product.prodname_actions %} to build and deploy a Python project to [Azure App Service](https://azure.microsoft.com/services/app-service/).
{% ifversion fpt or ghec or ghae-issue-4856 %}
{% ifversion fpt or ghec or ghae-issue-4856 or ghes > 3.4 %}
{% note %}

View File

@@ -20,7 +20,7 @@ topics:
This guide explains how to use {% data variables.product.prodname_actions %} to build and deploy a project to [Azure Kubernetes Service](https://azure.microsoft.com/services/kubernetes-service/).
{% ifversion fpt or ghec or ghae-issue-4856 %}
{% ifversion fpt or ghec or ghae-issue-4856 or ghes > 3.4 %}
{% note %}

View File

@@ -20,7 +20,7 @@ topics:
This guide explains how to use {% data variables.product.prodname_actions %} to build and deploy a web app to [Azure Static Web Apps](https://azure.microsoft.com/services/app-service/static/).
{% ifversion fpt or ghec or ghae-issue-4856 %}
{% ifversion fpt or ghec or ghae-issue-4856 or ghes > 3.4 %}
{% note %}

View File

@@ -26,7 +26,7 @@ This guide explains how to use {% data variables.product.prodname_actions %} to
GKE is a managed Kubernetes cluster service from Google Cloud that can host your containerized workloads in the cloud or in your own datacenter. For more information, see [Google Kubernetes Engine](https://cloud.google.com/kubernetes-engine).
{% ifversion fpt or ghec or ghae-issue-4856 %}
{% ifversion fpt or ghec or ghae-issue-4856 or ghes > 3.4 %}
{% note %}

View File

@@ -7,6 +7,7 @@ versions:
fpt: '*'
ghae: issue-4856
ghec: '*'
ghes: '>=3.5'
type: tutorial
topics:
- Security
@@ -66,11 +67,14 @@ The following example OIDC token uses a subject (`sub`) that references a job en
"jti": "example-id",
"sub": "repo:octo-org/octo-repo:environment:prod",
"environment": "prod",
"aud": "https://github.com/octo-org",
"aud": "{% ifversion ghes %}https://HOSTNAME{% else %}https://github.com{% endif %}/octo-org",
"ref": "refs/heads/main",
"sha": "example-sha",
"repository": "octo-org/octo-repo",
"repository_owner": "octo-org",
"actor_id": "12",
"repository_id": "74",
"repository_owner_id": "65",
"run_id": "example-run-id",
"run_number": "10",
"run_attempt": "2",
@@ -81,21 +85,22 @@ The following example OIDC token uses a subject (`sub`) that references a job en
"event_name": "workflow_dispatch",
"ref_type": "branch",
"job_workflow_ref": "octo-org/octo-automation/.github/workflows/oidc.yml@refs/heads/main",
"iss": "https://token.actions.githubusercontent.com",
"iss": "{% ifversion ghes %}https://HOSTNAME/_services/token{% else %}https://token.actions.githubusercontent.com{% endif %}",
"nbf": 1632492967,
"exp": 1632493867,
"iat": 1632493567
}
```
To see all the claims supported by {% data variables.product.prodname_dotcom %}'s OIDC provider, review the `claims_supported` entries at https://token.actions.githubusercontent.com/.well-known/openid-configuration.
To see all the claims supported by {% data variables.product.prodname_dotcom %}'s OIDC provider, review the `claims_supported` entries at
{% ifversion ghes %}`https://HOSTNAME/_services/token/.well-known/openid-configuration`{% else %}https://token.actions.githubusercontent.com/.well-known/openid-configuration{% endif %}.
The token includes the standard audience, issuer, and subject claims:
| Claim | Description |
| ----------- | ---------------------- |
| `aud`| _(Audience)_ By default, this is the URL of the repository owner, such as the organization that owns the repository. This is the only claim that can be customized. You can set a custom audience with a toolkit command: [`core.getIDToken(audience)`](https://www.npmjs.com/package/@actions/core/v/1.6.0) |
| `iss`| _(Issuer)_ The issuer of the OIDC token: `https://token.actions.githubusercontent.com` |
| `iss`| _(Issuer)_ The issuer of the OIDC token: {% ifversion ghes %}`https://HOSTNAME/_services/token`{% else %}`https://token.actions.githubusercontent.com`{% endif %} |
| `sub`| _(Subject)_ Defines the subject claim that is to be validated by the cloud provider. This setting is essential for making sure that access tokens are only allocated in a predictable way.|
The OIDC token also includes additional standard claims:
@@ -115,6 +120,7 @@ The token also includes custom claims provided by {% data variables.product.prod
| Claim | Description |
| ----------- | ---------------------- |
| `actor`| The personal account that initiated the workflow run. |
| `actor_id`| The ID of personal account that initiated the workflow run. |
| `base_ref`| The target branch of the pull request in a workflow run. |
| `environment`| The name of the environment used by the job. |
| `event_name`| The name of the event that triggered the workflow run. |
@@ -123,7 +129,9 @@ The token also includes custom claims provided by {% data variables.product.prod
| `ref`| _(Reference)_ The git ref that triggered the workflow run. |
| `ref_type`| The type of `ref`, for example: "branch". |
| `repository`| The repository from where the workflow is running. |
| `repository_id`| The ID of the repository from where the workflow is running. |
| `repository_owner`| The name of the organization in which the `repository` is stored. |
| `repository_owner_id`| The ID of the organization in which the `repository` is stored. |
| `run_id`| The ID of the workflow run that triggered the workflow. |
| `run_number`| The number of times this workflow has been run. |
| `run_attempt`| The number of times this workflow run has been retried. |
@@ -201,7 +209,7 @@ To configure the subject in your cloud provider's trust relationship, you must a
| | |
| ------ | ----------- |
| Amazon Web Services | `"token.actions.githubusercontent.com:sub": "repo:octo-org/octo-repo:ref:refs/heads/demo-branch"` |
| Amazon Web Services | `"{% ifversion ghes %}HOSTNAME/_services/token{% else %}token.actions.githubusercontent.com{% endif %}:sub": "repo:octo-org/octo-repo:ref:refs/heads/demo-branch"` |
| Azure| `repo:octo-org/octo-repo:ref:refs/heads/demo-branch` |
| Google Cloud Platform| `(assertion.sub=='repo:octo-org/octo-repo:ref:refs/heads/demo-branch')` |
| HashiCorp Vault| `bound_subject="repo:octo-org/octo-repo:ref:refs/heads/demo-branch" ` |

View File

@@ -7,6 +7,7 @@ versions:
fpt: '*'
ghae: issue-4856
ghec: '*'
ghes: '>=3.5'
type: tutorial
topics:
- Security
@@ -31,7 +32,7 @@ This guide explains how to configure AWS to trust {% data variables.product.prod
To add the {% data variables.product.prodname_dotcom %} OIDC provider to IAM, see the [AWS documentation](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_oidc.html).
- For the provider URL: Use `https://token.actions.githubusercontent.com`
- For the provider URL: Use {% ifversion ghes %}`https://HOSTNAME/_services/token`{% else %}`https://token.actions.githubusercontent.com`{% endif %}
- For the "Audience": Use `sts.amazonaws.com` if you are using the [official action](https://github.com/aws-actions/configure-aws-credentials).
### Configuring the role and trust policy
@@ -43,8 +44,8 @@ Edit the trust relationship to add the `sub` field to the validation conditions.
```json{:copy}
"Condition": {
"ForAllValues:StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
"token.actions.githubusercontent.com:sub": "repo:octo-org/octo-repo:ref:refs/heads/octo-branch"
"{% ifversion ghes %}HOSTNAME/_services/token{% else %}token.actions.githubusercontent.com{% endif %}:aud": "sts.amazonaws.com",
"{% ifversion ghes %}HOSTNAME/_services/token{% else %}token.actions.githubusercontent.com{% endif %}:sub": "repo:octo-org/octo-repo:ref:refs/heads/octo-branch"
}
}
```

View File

@@ -7,6 +7,7 @@ versions:
fpt: '*'
ghae: issue-4856
ghec: '*'
ghes: '>=3.5'
type: tutorial
topics:
- Security

View File

@@ -7,6 +7,7 @@ versions:
fpt: '*'
ghae: issue-4856
ghec: '*'
ghes: '>=3.5'
type: tutorial
topics:
- Security

View File

@@ -7,6 +7,7 @@ versions:
fpt: '*'
ghae: issue-4856
ghec: '*'
ghes: '>=3.5'
type: tutorial
topics:
- Security
@@ -39,7 +40,7 @@ Additional guidance for configuring the identity provider:
- For security hardening, make sure you've reviewed ["Configuring the OIDC trust with the cloud"](/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#configuring-the-oidc-trust-with-the-cloud). For an example, see ["Configuring the subject in your cloud provider"](/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#configuring-the-subject-in-your-cloud-provider).
- For the service account to be available for configuration, it needs to be assigned to the `roles/iam.workloadIdentityUser` role. For more information, see [the GCP documentation](https://cloud.google.com/iam/docs/workload-identity-federation?_ga=2.114275588.-285296507.1634918453#conditions).
- The Issuer URL to use: `https://token.actions.githubusercontent.com`
- The Issuer URL to use: {% ifversion ghes %}`https://HOSTNAME/_services/token`{% else %}`https://token.actions.githubusercontent.com`{% endif %}
## Updating your {% data variables.product.prodname_actions %} workflow

Some files were not shown because too many files have changed in this diff Show More