1
0
mirror of synced 2025-12-22 11:26:57 -05:00

Merge branch 'main' into patch-1

This commit is contained in:
⚡ Aditya Patel ⚡
2021-05-25 09:38:07 +05:30
committed by GitHub
34 changed files with 4762 additions and 9498 deletions

View File

@@ -5,7 +5,7 @@ module.exports = {
es2020: true, es2020: true,
node: true node: true
}, },
parser: 'babel-eslint', parser: '@babel/eslint-parser',
extends: [ extends: [
'eslint:recommended', 'eslint:recommended',
'standard' 'standard'

View File

@@ -13,7 +13,7 @@ jobs:
comment-that-approved: comment-that-approved:
name: Add review template name: Add review template
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.event.label.name == 'docs-content-ready-for-review' && github.repository == 'github/docs-internal' if: github.event.label.name == 'add-review-template' && github.repository == 'github/docs-internal'
steps: steps:
- name: check out repo content - name: check out repo content

View File

@@ -39,7 +39,7 @@ jobs:
${{ runner.os }}-node- ${{ runner.os }}-node-
- name: Install dependencies - name: Install dependencies
run: npm ci run: npm ci --include=optional
- name: Run brower-test - name: Run brower-test
run: npm run browser-test run: npm run browser-test

View File

@@ -23,4 +23,4 @@ jobs:
- name: Check out repo - name: Check out repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
- name: Build the container - name: Build the container
run: docker build . run: docker build --target production .

View File

@@ -35,7 +35,7 @@ jobs:
${{ runner.os }}-node- ${{ runner.os }}-node-
- name: Install dependencies - name: Install dependencies
run: npm ci run: npm ci --include=optional
- name: Run build scripts - name: Run build scripts
run: npm run build run: npm run build

View File

@@ -1,31 +0,0 @@
name: Send Crowdin PRs to boards
# **What it does**: Sends PRs opened on the crowdin branch to the ready for work column in this board: https://github.com/orgs/github/projects/1269#column-13447153
# **Why we have it**: To make sure the first responder sees crowdin translations that need to be merged as they review the Task board.
# **Who does it impact**: Docs localization and Docs Engineering
on:
pull_request:
types:
- opened
jobs:
triage:
if: github.repository == 'github/docs-internal' && github.event.pull_request.head.ref == 'translations'
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@626af12fe9a53dc2972b48385e7fe7dec79145c9
with:
github-token: ${{ secrets.DOCUBOT_READORG_REPO_WORKFLOW_SCOPES }}
script: |
var squadBoardColumnId = 13447153; // Add to the team task board
try {
await github.projects.createCard({
column_id: squadBoardColumnId,
content_id: context.payload.pull_request.id,
content_type: "PullRequest"
});
} catch (error) {
console.log(error);
}

View File

@@ -22,7 +22,9 @@ on:
jobs: jobs:
# This workflow contains a single job called "build" # This workflow contains a single job called "build"
copy-file: copy-file:
if: github.repository == 'github/docs-internal' if: |
github.event.pull_request.merged == true &&
github.repository == 'github/docs-internal'
# The type of runner that the job will run on # The type of runner that the job will run on
runs-on: ubuntu-latest runs-on: ubuntu-latest

View File

@@ -3,25 +3,62 @@
# It uses two multi-stage builds: `install` and the main build to keep the image size down. # It uses two multi-stage builds: `install` and the main build to keep the image size down.
# -------------------------------------------------------------------------------- # --------------------------------------------------------------------------------
# INSTALL IMAGE # BASE IMAGE
# A temporary image that installs production-only dependencies and builds the production-ready front-end bundles. # --------------------------------------------------------------------------------
FROM node:16.2.0-alpine as base
RUN apk add --no-cache make g++ git
FROM node:14-alpine as install
RUN apk add --no-cache python make g++
ENV NODE_ENV production
WORKDIR /usr/src/docs WORKDIR /usr/src/docs
# ---------------
# ALL DEPS
# ---------------
FROM base as all_deps
COPY package*.json ./ COPY package*.json ./
RUN npm ci
# ---------------
# PROD DEPS
# ---------------
FROM all_deps as prod_deps
RUN npm prune --production
# ---------------
# BUILDER
# ---------------
FROM all_deps as builder
ENV NODE_ENV production
COPY javascripts ./javascripts COPY javascripts ./javascripts
COPY stylesheets ./stylesheets COPY stylesheets ./stylesheets
COPY pages ./pages
COPY components ./components
COPY lib ./lib COPY lib ./lib
# one part of the build relies on this content file to pull all-products
COPY content/index.md ./content/index.md
COPY webpack.config.js ./webpack.config.js COPY webpack.config.js ./webpack.config.js
RUN npm ci --production COPY next.config.js ./next.config.js
COPY tsconfig.json ./tsconfig.json
RUN npx tsc
RUN npm run build RUN npm run build
# -------------------------------------------------------------------------------- # --------------------------------------------------------------------------------
# MAIN IMAGE # MAIN IMAGE
# --------------------------------------------------------------------------------
FROM node:14-alpine FROM node:16.2.0-alpine as production
# Let's make our home # Let's make our home
WORKDIR /usr/src/docs WORKDIR /usr/src/docs
@@ -32,11 +69,12 @@ RUN chown node:node /usr/src/docs -R
# This should be our normal running user # This should be our normal running user
USER node USER node
# Copy our dependencies # Copy just our prod dependencies
COPY --chown=node:node --from=install /usr/src/docs/node_modules /usr/src/docs/node_modules COPY --chown=node:node --from=prod_deps /usr/src/docs/node_modules /usr/src/docs/node_modules
# Copy our front-end code # Copy our front-end code
COPY --chown=node:node --from=install /usr/src/docs/dist /usr/src/docs/dist COPY --chown=node:node --from=builder /usr/src/docs/dist /usr/src/docs/dist
COPY --chown=node:node --from=builder /usr/src/docs/.next /usr/src/docs/.next
# We should always be running in production mode # We should always be running in production mode
ENV NODE_ENV production ENV NODE_ENV production

View File

@@ -2,7 +2,6 @@
"name": "docs.github.com", "name": "docs.github.com",
"env": { "env": {
"NODE_ENV": "production", "NODE_ENV": "production",
"NPM_CONFIG_PRODUCTION": "true",
"ENABLED_LANGUAGES": "en", "ENABLED_LANGUAGES": "en",
"WEB_CONCURRENCY": "1" "WEB_CONCURRENCY": "1"
}, },

View File

@@ -20,7 +20,11 @@ export const Contribution = () => {
</a> </a>
<p className="color-text-secondary f6 mt-2"> <p className="color-text-secondary f6 mt-2">
{t`or`}{' '} {t`or`}{' '}
<a href="https://github.com/github/docs/blob/main/CONTRIBUTING.md" target="_blank" rel="noopener"> <a
href="https://github.com/github/docs/blob/main/CONTRIBUTING.md"
target="_blank"
rel="noopener"
>
{t`to_guidelines`} {t`to_guidelines`}
</a> </a>
</p> </p>

View File

@@ -50,7 +50,11 @@ export const Header = () => {
<div className="width-full"> <div className="width-full">
<div className="d-inline-block width-full d-md-flex" style={{ zIndex: 1 }}> <div className="d-inline-block width-full d-md-flex" style={{ zIndex: 1 }}>
<div className="float-right d-md-none position-relative" style={{ zIndex: 3 }}> <div className="float-right d-md-none position-relative" style={{ zIndex: 3 }}>
<ButtonOutline css onClick={() => setIsMenuOpen(!isMenuOpen)} aria-label="Navigation Menu"> <ButtonOutline
css
onClick={() => setIsMenuOpen(!isMenuOpen)}
aria-label="Navigation Menu"
>
{isMenuOpen ? <XIcon size="small" /> : <ThreeBarsIcon size="small" />} {isMenuOpen ? <XIcon size="small" /> : <ThreeBarsIcon size="small" />}
</ButtonOutline> </ButtonOutline>
</div> </div>

View File

@@ -1,6 +1,6 @@
import cx from 'classnames' import cx from 'classnames'
import Link from 'next/link' import Link from 'next/link'
import { format } from 'date-fns' import dayjs from 'dayjs'
import { ArrowRightIcon } from '@primer/octicons-react' import { ArrowRightIcon } from '@primer/octicons-react'
import { FeaturedLink, useProductLandingContext } from 'components/context/ProductLandingContext' import { FeaturedLink, useProductLandingContext } from 'components/context/ProductLandingContext'
@@ -93,9 +93,9 @@ const ArticleList = ({ title, viewAllHref, articles }: ArticleListProps) => {
{link.date && ( {link.date && (
<time <time
className="tooltipped tooltipped-n color-text-tertiary text-mono mt-1" className="tooltipped tooltipped-n color-text-tertiary text-mono mt-1"
aria-label={format(new Date(link.date), 'PPP')} aria-label={dayjs(link.date).format('LLL')}
> >
{format(new Date(link.date), 'MMMM dd')} {dayjs(link.date).format('MMMM DD')}
</time> </time>
)} )}
</a> </a>

View File

@@ -126,11 +126,31 @@ For a list of REST API endpoints you can use to get high-level information about
### Authenticating as an installation ### Authenticating as an installation
Authenticating as an installation lets you perform actions in the API for that installation. Before authenticating as an installation, you must create an installation access token. These installation access tokens are used by {% data variables.product.prodname_github_app %}s to authenticate. Authenticating as an installation lets you perform actions in the API for that installation. Before authenticating as an installation, you must create an installation access token. Ensure that you have already installed your GitHub App to at least one repository; it is impossible to create an installation token without a single installation. These installation access tokens are used by {% data variables.product.prodname_github_app %}s to authenticate. For more information, see "[Installing GitHub Apps](/developers/apps/managing-github-apps/installing-github-apps)."
By default, installation access tokens are scoped to all the repositories that an installation can access. You can limit the scope of the installation access token to specific repositories by using the `repository_ids` parameter. See the [Create an installation access token for an app](/rest/reference/apps#create-an-installation-access-token-for-an-app) endpoint for more details. Installation access tokens have the permissions configured by the {% data variables.product.prodname_github_app %} and expire after one hour. By default, installation access tokens are scoped to all the repositories that an installation can access. You can limit the scope of the installation access token to specific repositories by using the `repository_ids` parameter. See the [Create an installation access token for an app](/rest/reference/apps#create-an-installation-access-token-for-an-app) endpoint for more details. Installation access tokens have the permissions configured by the {% data variables.product.prodname_github_app %} and expire after one hour.
To create an installation access token, include the JWT [generated above](#jwt-payload) in the Authorization header in the API request: To list the installations for an authenticated app, include the JWT [generated above](#jwt-payload) in the Authorization header in the API request:
{% if currentVersion ver_lt "enterprise-server@2.22" %}
```shell
$ curl -i -X POST \
-H "Authorization: Bearer YOUR_JWT" \
-H "Accept: application/vnd.github.machine-man-preview+json" \
{% data variables.product.api_url_pre %}/app/installations
```
{% else %}
```shell
$ curl -i -X POST \
-H "Authorization: Bearer YOUR_JWT" \
-H "Accept: application/vnd.github.v3+json" \
{% data variables.product.api_url_pre %}/app/installations
```
{% endif %}
The response will include a list of installations where each installation's `id` can be used for creating an installation access token. For more information about the response format, see "[List installations for the authenticated app](/rest/reference/apps#list-installations-for-the-authenticated-app)."
To create an installation access token, include the JWT [generated above](#jwt-payload) in the Authorization header in the API request and replace `:installation_id` with the installation's `id`:
{% if enterpriseServerVersions contains currentVersion and currentVersion ver_lt "enterprise-server@2.22" %} {% if enterpriseServerVersions contains currentVersion and currentVersion ver_lt "enterprise-server@2.22" %}
```shell ```shell

View File

@@ -13,7 +13,7 @@ topics:
--- ---
Before adding a new SSH key to your {% data variables.product.product_name %} account, you should have: Before adding a new SSH key to your {% data variables.product.product_name %} account, you should have:
* [Checked for existing SSH keys](/articles/checking-for-existing-ssh-keys) * [Checked for existing SSH keys](/articles/checking-for-existing-ssh-keys)
* [Generated a new SSH key and added it to the ssh-agent](/articles/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent) * [Generating a new SSH key and adding it to the ssh-agent](/articles/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent)
After adding a new SSH key to your {% data variables.product.product_name %} account, you can reconfigure any local repositories to use SSH. For more information, see "[Switching remote URLs from HTTPS to SSH](/github/getting-started-with-github/managing-remote-repositories/#switching-remote-urls-from-https-to-ssh)." After adding a new SSH key to your {% data variables.product.product_name %} account, you can reconfigure any local repositories to use SSH. For more information, see "[Switching remote URLs from HTTPS to SSH](/github/getting-started-with-github/managing-remote-repositories/#switching-remote-urls-from-https-to-ssh)."

View File

@@ -1,43 +0,0 @@
---
title: About GitHub Jobs
redirect_from:
- /articles/how-to-write-a-great-job-post/
- /articles/about-github-jobs
intro: 'You can post a job on {% data variables.product.prodname_jobs %} to find talent for your business.'
versions:
free-pro-team: '*'
topics:
- Jobs
---
{% warning %}
Deprecation note: GitHub Jobs is now deprecated. The last date to post a job was May 19, 2021. The GitHub Jobs site will shut down entirely on August 19, 2021. For more information, see the [GitHub blog post](https://github.blog/changelog/2021-04-19-deprecation-notice-github-jobs-site/).
{% endwarning %}
Before you can post a job, you must create a {% data variables.product.prodname_dotcom %} account and verify your email address. For more information, see "[Signing up for a new {% data variables.product.prodname_dotcom %} account](/articles/signing-up-for-a-new-github-account)" and "[Verifying your email address](/articles/verifying-your-email-address)."
### Best practices for writing a job post
When you write a job post, make the job title as descriptive as possible. Try to mention the language or technologies the job requires.
In the job description, include information about your company's culture and any benefits you offer. Describe the position in detail, including information about job responsibilities and the team the candidate will work with. List the main skills required for the job in order of importance.
To help candidates find your job post, check **Hot Searches** on the bottom of the main page. If any of these categories apply to your open position, include them in your job description.
![Hot Searches section of {% data variables.product.prodname_dotcom %}](/assets/images/help/jobs/hot-searches.png)
{% tip %}
**Tip:** {% data variables.product.prodname_dotcom %} does not track views of your job post or click-throughs. If you want to track click-throughs, you can add tracking parameters to the URL you include in the **How to Apply** field. For more information, see Google's "[URL parameters](https://support.google.com/google-ads/answer/6277564?hl=en)."
{% endtip %}
### Formatting your job post
You can use markdown to format your job post. For more information, see "[Basic writing and formatting syntax](/articles/basic-writing-and-formatting-syntax)."
### Further reading
- [How {% data variables.product.prodname_jobs %} works](https://jobs.github.com/faq) on {% data variables.product.prodname_jobs %}

View File

@@ -1,14 +0,0 @@
---
title: Finding talent with GitHub Jobs
redirect_from:
- /categories/72/articles/
- /categories/jobs/
- /categories/finding-talent-with-github-jobs
versions:
free-pro-team: '*'
topics:
- Jobs
children:
- /about-github-jobs
---

View File

@@ -32,7 +32,6 @@ children:
- /managing-large-files - /managing-large-files
- /customizing-your-github-workflow - /customizing-your-github-workflow
- /extending-github - /extending-github
- /finding-talent-with-github-jobs
- /working-with-github-support - /working-with-github-support
- /understanding-how-github-uses-and-protects-your-data - /understanding-how-github-uses-and-protects-your-data
- /site-policy - /site-policy

View File

@@ -1,6 +1,6 @@
--- ---
title: Managing the forking policy for your organization title: Managing the forking policy for your organization
intro: 'You can can allow or prevent the forking of any private{% if currentVersion == "free-pro-team@latest" or currentVersion ver_gt "enterprise-server@2.19" or currentVersion == "github-ae@latest" %} and internal{% endif %} repositories owned by your organization.' intro: 'You can allow or prevent the forking of any private{% if currentVersion == "free-pro-team@latest" or currentVersion ver_gt "enterprise-server@2.19" or currentVersion == "github-ae@latest" %} and internal{% endif %} repositories owned by your organization.'
redirect_from: redirect_from:
- /articles/allowing-people-to-fork-private-repositories-in-your-organization - /articles/allowing-people-to-fork-private-repositories-in-your-organization
- /github/setting-up-and-managing-organizations-and-teams/allowing-people-to-fork-private-repositories-in-your-organization - /github/setting-up-and-managing-organizations-and-teams/allowing-people-to-fork-private-repositories-in-your-organization

View File

@@ -104,6 +104,8 @@ Lob | Lob Test API Key | lob_test_api_key{% endif %}
Mailchimp | Mailchimp API Key | mailchimp_api_key{% endif %} Mailchimp | Mailchimp API Key | mailchimp_api_key{% endif %}
{%- if currentVersion == "free-pro-team@latest" or currentVersion ver_gt "enterprise-server@2.22" or currentVersion == "github-ae@next" %} {%- if currentVersion == "free-pro-team@latest" or currentVersion ver_gt "enterprise-server@2.22" or currentVersion == "github-ae@next" %}
Mailgun | Mailgun API Key | mailgun_api_key{% endif %} Mailgun | Mailgun API Key | mailgun_api_key{% endif %}
{%- if currentVersion == "free-pro-team@latest" or currentVersion ver_gt "enterprise-server@3.1" or currentVersion == "github-ae@next" %}
MessageBird | MessageBird API Key | messagebird_api_key{% endif %}
{%- if currentVersion == "free-pro-team@latest" or currentVersion ver_gt "enterprise-server@2.22" or currentVersion == "github-ae@next" %} {%- if currentVersion == "free-pro-team@latest" or currentVersion ver_gt "enterprise-server@2.22" or currentVersion == "github-ae@next" %}
npm | npm Access Token | npm_access_token{% endif %} npm | npm Access Token | npm_access_token{% endif %}
{%- if currentVersion == "free-pro-team@latest" or currentVersion ver_gt "enterprise-server@2.22" or currentVersion == "github-ae@next" %} {%- if currentVersion == "free-pro-team@latest" or currentVersion ver_gt "enterprise-server@2.22" or currentVersion == "github-ae@next" %}
@@ -162,6 +164,8 @@ Stripe | Stripe Test API Secret Key | stripe_test_secret_key{% endif %}
Stripe | Stripe Live API Restricted Key | stripe_live_restricted_key{% endif %} Stripe | Stripe Live API Restricted Key | stripe_live_restricted_key{% endif %}
{%- if currentVersion == "free-pro-team@latest" or currentVersion ver_gt "enterprise-server@3.0" or currentVersion == "github-ae@next" %} {%- if currentVersion == "free-pro-team@latest" or currentVersion ver_gt "enterprise-server@3.0" or currentVersion == "github-ae@next" %}
Stripe | Stripe Test API Restricted Key | stripe_test_restricted_key{% endif %} Stripe | Stripe Test API Restricted Key | stripe_test_restricted_key{% endif %}
{%- if currentVersion == "free-pro-team@latest" or currentVersion ver_gt "enterprise-server@3.1" or currentVersion == "github-ae@next" %}
Stripe | Stripe Webhook Signing Secret | stripe_webhook_signing_secret{% endif %}
{%- if currentVersion == "free-pro-team@latest" or currentVersion ver_gt "enterprise-server@2.22" or currentVersion == "github-ae@next" %} {%- if currentVersion == "free-pro-team@latest" or currentVersion ver_gt "enterprise-server@2.22" or currentVersion == "github-ae@next" %}
Tencent Cloud | Tencent Cloud Secret ID | tencent_cloud_secret_id{% endif %} Tencent Cloud | Tencent Cloud Secret ID | tencent_cloud_secret_id{% endif %}
{%- if currentVersion == "free-pro-team@latest" or currentVersion ver_gt "enterprise-server@2.22" or currentVersion == "github-ae@next" %} {%- if currentVersion == "free-pro-team@latest" or currentVersion ver_gt "enterprise-server@2.22" or currentVersion == "github-ae@next" %}

View File

@@ -77,3 +77,4 @@ Stripe | Stripe Test API Restricted Key
Tencent Cloud | Tencent Cloud Secret ID Tencent Cloud | Tencent Cloud Secret ID
Twilio | Twilio Account String Identifier Twilio | Twilio Account String Identifier
Twilio | Twilio API Key Twilio | Twilio API Key
Valour | Valour Access Token

View File

@@ -1 +1 @@
You cannot verify email addresses from disposable domains. If you'd like to keep your email address private, you can use a {% data variables.product.product_name %}-provided `noreply` email address. For more information, see "[Setting your commit email address on {% data variables.product.prodname_dotcom %}](/articles/setting-your-commit-email-address#setting-your-commit-email-address-on-github)." You cannot verify email addresses from disposable email address services (services that allow you to receive email at a temporary address that expires after a certain time). If you'd like to keep your email address private, you can use a {% data variables.product.product_name %}-provided `noreply` email address. For more information, see "[Setting your commit email address on {% data variables.product.prodname_dotcom %}](/articles/setting-your-commit-email-address#setting-your-commit-email-address-on-github)."

View File

@@ -11,7 +11,6 @@ const Permalink = require('./permalink')
const languages = require('./languages') const languages = require('./languages')
const renderContent = require('./render-content') const renderContent = require('./render-content')
const processLearningTracks = require('./process-learning-tracks') const processLearningTracks = require('./process-learning-tracks')
const { renderReact } = require('./react/engine')
const { productMap } = require('./all-products') const { productMap } = require('./all-products')
const slash = require('slash') const slash = require('slash')
const statsd = require('./statsd') const statsd = require('./statsd')
@@ -168,31 +167,8 @@ class Page {
this.introLinks.overview = await renderContent(this.introLinks.rawOverview, context, { textOnly: true }) this.introLinks.overview = await renderContent(this.introLinks.rawOverview, context, { textOnly: true })
} }
let markdown = this.markdown
// If the article is interactive parse the React!
if (this.interactive) {
// Search for the react code comments to find the react components
const reactComponents = markdown.match(/<!--react-->(.*?)<!--end-react-->/gs)
// Render each of the react components in the markdown
await Promise.all(reactComponents.map(async (reactComponent) => {
let componentStr = reactComponent
// Remove the React comment indicators
componentStr = componentStr.replace('<!--react-->\n', '').replace('<!--react-->', '')
componentStr = componentStr.replace('\n<!--end-react-->', '').replace('<!--end-react-->', '')
// Get the rendered component
const renderedComponent = await renderReact(componentStr)
// Replace the react component with the rendered markdown
markdown = markdown.replace(reactComponent, renderedComponent)
}))
}
context.relativePath = this.relativePath context.relativePath = this.relativePath
const html = await renderContent(markdown, context) const html = await renderContent(this.markdown, context)
// product frontmatter may contain liquid // product frontmatter may contain liquid
if (this.product) { if (this.product) {

View File

@@ -1,22 +0,0 @@
const babel = require('@babel/core')
const reactBabelOptions = {
presets: [
'@babel/preset-env',
'@babel/preset-react'
],
plugins: [
'@babel/plugin-transform-react-jsx',
'@babel/plugin-proposal-object-rest-spread',
'@babel/plugin-proposal-class-properties',
'@babel/transform-runtime'
]
}
const transform = code =>
babel.transform(code, reactBabelOptions).code
module.exports = {
transform: transform,
reactBabelOptions: reactBabelOptions
}

View File

@@ -1,57 +0,0 @@
const { renderToString } = require('react-dom/server')
const { transform } = require('./babel')
const React = require('react')
const fs = require('fs')
const path = require('path')
const dirTree = require('directory-tree')
// Name of directory for saving transformed components that should be gitignored
const dist = 'dist'
// Build React components
// This loops through the react components and transpiles them to /dist
// so they can be used by Node.js when we do server side rendering
const tree = dirTree('./react/')
if (tree) {
for (const index in tree.children) {
const file = tree.children[index]
if (file.type === 'file' && file.extension === '.js') {
if (!fs.existsSync(path.join(dist, 'react'))) {
fs.mkdirSync(path.join(dist, 'react'), { recursive: true })
}
const content = transform(fs.readFileSync(file.path, 'utf8'))
fs.writeFileSync(path.join(dist, file.path), content)
}
}
}
// End Build React Components
// Register components
const components = {
// CodeBlock: require('../../dist/react/CodeBlock'),
// CodeEditor: require('../../dist/react/CodeEditor')
}
const renderReact = async componentStr => {
// Get component name as string so we can use it in the class name
// which will be needed later if we choose to do client side React hydration
const componentName = componentStr.match(/<([a-zA-Z]+)\s/)[1]
// Add the wrapper and class name so we can later use React hydration on the client
// side
const jsx = `<div className="react-component-${componentName}">\n${componentStr}\n</div>`
const component = transform(jsx)
// eslint-disable-next-line
const getComponent = new Function(
'React',
...Object.keys(components),
`${component.replace('React', 'return React')}`
)
return renderToString(getComponent(React, ...Object.values(components)))
}
module.exports = {
renderReact
}

View File

@@ -2,6 +2,11 @@ const { productIds } = require('./lib/all-products')
const languages = require('./lib/languages') const languages = require('./lib/languages')
module.exports = { module.exports = {
// speed up production `next build` by ignoring typechecking during that step of build.
// type-checking still occurs in the Dockerfile build
typescript: {
ignoreBuildErrors: true
},
i18n: { i18n: {
locales: Object.values(languages).map(({ code }) => code), locales: Object.values(languages).map(({ code }) => code),
defaultLocale: 'en' defaultLocale: 'en'

13489
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -8,19 +8,11 @@
"url": "https://github.com/github/docs" "url": "https://github.com/github/docs"
}, },
"license": "(MIT AND CC-BY-4.0)", "license": "(MIT AND CC-BY-4.0)",
"cacheDirectories": [
"node_modules",
".next/cache"
],
"dependencies": { "dependencies": {
"@babel/core": "^7.14.3",
"@babel/plugin-proposal-class-properties": "^7.13.0",
"@babel/plugin-syntax-class-properties": "^7.12.1",
"@babel/plugin-transform-modules-amd": "^7.14.2",
"@babel/plugin-transform-modules-commonjs": "^7.14.0",
"@babel/plugin-transform-react-jsx": "^7.14.3",
"@babel/plugin-transform-runtime": "^7.14.3",
"@babel/preset-env": "^7.14.2",
"@babel/preset-react": "^7.13.13",
"@babel/runtime": "^7.14.0",
"@graphql-inspector/core": "^2.5.0",
"@graphql-tools/load": "^6.2.8",
"@primer/components": "^28.0.4", "@primer/components": "^28.0.4",
"@primer/css": "^16.2.0", "@primer/css": "^16.2.0",
"@primer/octicons": "^14.1.0", "@primer/octicons": "^14.1.0",
@@ -29,8 +21,6 @@
"ajv-formats": "^2.1.0", "ajv-formats": "^2.1.0",
"algoliasearch": "^4.9.1", "algoliasearch": "^4.9.1",
"assert": "^2.0.0", "assert": "^2.0.0",
"babel-loader": "^8.2.2",
"babel-preset-env": "^1.7.0",
"browser-date-formatter": "^3.0.3", "browser-date-formatter": "^3.0.3",
"change-case": "^4.1.2", "change-case": "^4.1.2",
"cheerio": "^1.0.0-rc.3", "cheerio": "^1.0.0-rc.3",
@@ -39,13 +29,9 @@
"connect-datadog": "0.0.9", "connect-datadog": "0.0.9",
"connect-slashes": "^1.4.0", "connect-slashes": "^1.4.0",
"cookie-parser": "^1.4.5", "cookie-parser": "^1.4.5",
"copy-webpack-plugin": "^8.1.1",
"cors": "^2.8.5", "cors": "^2.8.5",
"cross-env": "^7.0.3",
"css-loader": "^5.2.4",
"csurf": "^1.11.0", "csurf": "^1.11.0",
"date-fns": "^2.21.3", "dayjs": "^1.10.4",
"directory-tree": "^2.2.9",
"dotenv": "^9.0.2", "dotenv": "^9.0.2",
"express": "^4.17.1", "express": "^4.17.1",
"express-rate-limit": "^5.2.6", "express-rate-limit": "^5.2.6",
@@ -73,7 +59,6 @@
"lunr": "^2.3.9", "lunr": "^2.3.9",
"lunr-languages": "^1.4.0", "lunr-languages": "^1.4.0",
"mdast-util-from-markdown": "^0.8.4", "mdast-util-from-markdown": "^0.8.4",
"mini-css-extract-plugin": "^1.6.0",
"mkdirp": "^1.0.4", "mkdirp": "^1.0.4",
"morgan": "^1.10.0", "morgan": "^1.10.0",
"next": "^10.2.0", "next": "^10.2.0",
@@ -94,51 +79,54 @@
"remark-gemoji-to-emoji": "^1.1.0", "remark-gemoji-to-emoji": "^1.1.0",
"remark-parse": "^7.0.2", "remark-parse": "^7.0.2",
"remark-rehype": "^5.0.0", "remark-rehype": "^5.0.0",
"resolve-url-loader": "^4.0.0",
"revalidator": "^0.3.1", "revalidator": "^0.3.1",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"rss-parser": "^3.12.0", "rss-parser": "^3.12.0",
"sass": "^1.32.13",
"sass-loader": "^11.1.1",
"search-with-your-keyboard": "1.1.0", "search-with-your-keyboard": "1.1.0",
"semver": "^7.3.5", "semver": "^7.3.5",
"slash": "^3.0.0", "slash": "^3.0.0",
"strip-html-comments": "^1.0.0", "strip-html-comments": "^1.0.0",
"style-loader": "^2.0.0",
"styled-components": "^5.3.0", "styled-components": "^5.3.0",
"throng": "^5.0.0", "throng": "^5.0.0",
"unified": "^8.4.2", "unified": "^8.4.2",
"unist-util-visit": "^2.0.3", "unist-util-visit": "^2.0.3",
"uuid": "^8.3.2", "uuid": "^8.3.2",
"walk-sync": "^2.2.0", "walk-sync": "^2.2.0"
"webpack": "^5.37.0",
"webpack-cli": "^4.7.0"
}, },
"devDependencies": { "devDependencies": {
"@actions/core": "^1.2.7", "@actions/core": "^1.2.7",
"@actions/github": "^5.0.0", "@actions/github": "^5.0.0",
"@babel/core": "^7.14.3",
"@babel/eslint-parser": "^7.14.3",
"@babel/plugin-transform-runtime": "^7.14.3",
"@babel/preset-env": "^7.14.2",
"@graphql-inspector/core": "^2.5.0",
"@graphql-tools/load": "^6.2.8",
"@octokit/rest": "^18.5.3", "@octokit/rest": "^18.5.3",
"@types/lodash": "^4.14.169", "@types/lodash": "^4.14.169",
"@types/react": "^17.0.6", "@types/react": "^17.0.6",
"@types/react-dom": "^17.0.5", "@types/react-dom": "^17.0.5",
"async": "^3.2.0", "async": "^3.2.0",
"await-sleep": "0.0.1", "await-sleep": "0.0.1",
"babel-eslint": "^10.1.0", "babel-loader": "^8.2.2",
"babel-plugin-styled-components": "^1.12.0", "babel-plugin-styled-components": "^1.12.0",
"babel-preset-env": "^1.7.0",
"chalk": "^4.1.1", "chalk": "^4.1.1",
"commander": "^7.2.0", "commander": "^7.2.0",
"copy-webpack-plugin": "^8.1.1",
"count-array-values": "^1.2.1", "count-array-values": "^1.2.1",
"cross-env": "^7.0.3",
"csp-parse": "0.0.2", "csp-parse": "0.0.2",
"css-loader": "^4.3.0",
"csv-parse": "^4.15.4", "csv-parse": "^4.15.4",
"csv-parser": "^3.0.0", "csv-parser": "^3.0.0",
"dedent": "^0.7.0", "dedent": "^0.7.0",
"del": "^6.0.0",
"domwaiter": "^1.3.0", "domwaiter": "^1.3.0",
"eslint": "^7.26.0", "eslint": "^7.26.0",
"eslint-config-standard": "^16.0.2", "eslint-config-standard": "^16.0.2",
"eslint-plugin-import": "^2.23.2", "eslint-plugin-import": "^2.23.2",
"eslint-plugin-node": "^11.1.0", "eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^5.1.0", "eslint-plugin-promise": "^4.2.1",
"event-to-promise": "^0.8.0", "event-to-promise": "^0.8.0",
"glob": "^7.1.7", "glob": "^7.1.7",
"graphql": "^15.5.0", "graphql": "^15.5.0",
@@ -151,31 +139,41 @@
"jest": "^26.6.3", "jest": "^26.6.3",
"jest-expect-message": "^1.0.2", "jest-expect-message": "^1.0.2",
"jest-github-actions-reporter": "^1.0.3", "jest-github-actions-reporter": "^1.0.3",
"jest-puppeteer": "^5.0.3",
"jest-silent-reporter": "^0.5.0", "jest-silent-reporter": "^0.5.0",
"jest-slow-test-reporter": "^1.0.0", "jest-slow-test-reporter": "^1.0.0",
"jimp": "^0.16.1",
"make-promises-safe": "^5.1.0", "make-promises-safe": "^5.1.0",
"mime": "^2.4.4", "mime": "^2.4.4",
"mini-css-extract-plugin": "^1.6.0",
"mock-express-response": "^0.3.0", "mock-express-response": "^0.3.0",
"mockdate": "^3.0.5", "mockdate": "^3.0.5",
"nock": "^13.0.11", "nock": "^13.0.11",
"nodemon": "^2.0.7", "nodemon": "^2.0.7",
"npm-merge-driver-install": "^2.0.1", "npm-merge-driver-install": "^2.0.1",
"object-hash": "^2.1.1", "object-hash": "^2.1.1",
"pa11y-ci": "^2.4.1", "postcss": "^8.2.15",
"prettier": "^2.3.0", "prettier": "^2.3.0",
"process": "^0.11.10", "process": "^0.11.10",
"puppeteer": "^9.1.1",
"replace": "^1.2.1", "replace": "^1.2.1",
"resolve-url-loader": "^4.0.0",
"robots-parser": "^2.3.0", "robots-parser": "^2.3.0",
"sass": "^1.32.13",
"sass-loader": "^11.1.1",
"start-server-and-test": "^1.12.2", "start-server-and-test": "^1.12.2",
"strip-ansi": "^7.0.0", "strip-ansi": "^7.0.0",
"style-loader": "^2.0.0",
"supertest": "^6.1.3", "supertest": "^6.1.3",
"typescript": "^4.2.4", "typescript": "^4.2.4",
"url-template": "^2.0.8", "url-template": "^2.0.8",
"webpack": "^5.37.0",
"webpack-cli": "^4.7.0",
"webpack-dev-middleware": "^4.2.0", "webpack-dev-middleware": "^4.2.0",
"website-scraper": "^4.2.3", "website-scraper": "^4.2.3"
},
"optionalDependencies": {
"jest-puppeteer": "^5.0.3",
"jimp": "^0.16.1",
"pa11y-ci": "^2.4.1",
"puppeteer": "^9.1.1",
"xlsx-populate": "^1.21.0" "xlsx-populate": "^1.21.0"
}, },
"scripts": { "scripts": {
@@ -183,7 +181,8 @@
"dev": "npm start", "dev": "npm start",
"debug": "cross-env NODE_ENV=development ENABLED_LANGUAGES='en,ja' nodemon --inspect server.js", "debug": "cross-env NODE_ENV=development ENABLED_LANGUAGES='en,ja' nodemon --inspect server.js",
"rest-dev": "script/rest/update-files.js && npm run dev", "rest-dev": "script/rest/update-files.js && npm run dev",
"build": "cross-env NODE_ENV=production npx webpack --mode production", "build": "npm run webpack-build && next build",
"webpack-build": "cross-env NODE_ENV=production npx webpack --mode production",
"start-all-languages": "cross-env NODE_ENV=development nodemon server.js", "start-all-languages": "cross-env NODE_ENV=development nodemon server.js",
"lint": "eslint --fix . && prettier -w \"**/*.{yml,yaml}\" && npm run lint-tsc", "lint": "eslint --fix . && prettier -w \"**/*.{yml,yaml}\" && npm run lint-tsc",
"lint-translation": "TEST_TRANSLATION=true jest content/lint-files", "lint-translation": "TEST_TRANSLATION=true jest content/lint-files",

View File

@@ -9,7 +9,7 @@ import '@primer/css/index.scss'
import { defaultThemeProps } from 'components/lib/getThemeProps' import { defaultThemeProps } from 'components/lib/getThemeProps'
type MyAppProps = AppProps & { csrfToken: string, themeProps: typeof defaultThemeProps } type MyAppProps = AppProps & { csrfToken: string; themeProps: typeof defaultThemeProps }
const MyApp = ({ Component, pageProps, csrfToken, themeProps }: MyAppProps) => { const MyApp = ({ Component, pageProps, csrfToken, themeProps }: MyAppProps) => {
return ( return (
<> <>
@@ -43,9 +43,10 @@ const MyApp = ({ Component, pageProps, csrfToken, themeProps }: MyAppProps) => {
MyApp.getInitialProps = async (appContext: AppContext) => { MyApp.getInitialProps = async (appContext: AppContext) => {
const { ctx } = appContext const { ctx } = appContext
// calls page's `getInitialProps` and fills `appProps.pageProps` // calls page's `getInitialProps` and fills `appProps.pageProps`
const appProps = await App.getInitialProps(appContext); const appProps = await App.getInitialProps(appContext)
const req: any = ctx.req
return { ...appProps, themeProps: getThemeProps(ctx.req), csrfToken: (ctx.req as any).csrfToken() } return { ...appProps, themeProps: getThemeProps(req), csrfToken: req?.csrfToken?.() || '' }
} }
const SetTheme = ({ themeProps }: { themeProps: typeof defaultThemeProps }) => { const SetTheme = ({ themeProps }: { themeProps: typeof defaultThemeProps }) => {

View File

@@ -1,38 +0,0 @@
# React (Experimental)
The files in this directory are React components. We can use these components within Markdown files by adding `interactive: true` frontmatter to a Markdown file. Then, using the following syntax to embed the component:
```
<!--react-->
<ReactComponentHere some='prop' other={`prop`} />
<!--end-react-->
```
Theoretically React can be embedded anywhere on the site with a little bit of creativity but that is yet to be tested.
## Defining new components
Start by adding frontmatter `interactive:true` to any Markdown file that you want to support React. This prevents React render time from slowing down any other page and keeping the impact on performance very low.
1. Create the component in the `./react` directory
2. Register the component for webpack in `./javascripts/index.js` so the component works with client side rendering
3. Register the component in `./lib/react/engine.js` so the component works with server side rendering
4. If the component needs to be evaluated client side, see [this example](https://github.com/github/docs/blob/a48998c7890b71c8f58eda1fa31b50df348a0042/react/CodeEditor.js) for how to ensure the client side component rendered by the server gets [Hydrated](https://reactjs.org/docs/react-dom.html#hydrate).
## A Complete Example
The following example demonstrates the addition of an interactive CodeEditor on one of our pages:
- [Defining the CodeEditor React Component](https://github.com/github/docs/blob/a48998c7890b71c8f58eda1fa31b50df348a0042/react/CodeEditor.js)
- [Configuring the CodeEditor for client-side rendering](https://github.com/github/docs/blob/a48998c7890b71c8f58eda1fa31b50df348a0042/javascripts/index.js#L45)
- [Configuring the CodeEditor for server-side rendering](https://github.com/github/docs/blob/a48998c7890b71c8f58eda1fa31b50df348a0042/lib/react/engine.js#L30)
- CodeEditor added to a markdown file
- [Enabling React components on a page](https://github.com/github/docs/blame/a48998c7890b71c8f58eda1fa31b50df348a0042/content/github/getting-started-with-github/access-permissions-on-github.md#L12)
- [Adding the CodeEditor to a page](https://github.com/github/docs/blame/a48998c7890b71c8f58eda1fa31b50df348a0042/content/github/getting-started-with-github/access-permissions-on-github.md#L47)
## Gotchas
- When requiring React components from npm you will often need to explicitly call `require('component').default` to make sure you get the component
- Some code examples you get from external React packages won't work out of the box, you often need to dig into the module and determine whether it's exporting to default or not which will in many cases cause you to need to `require('package').default`
- `import` doesn't always work. Use `require` for a more consistent experience with React in this codebase.
- If components require you to load CSS, you need to load that CSS in the `javascripts/index.js` file as shown [here](https://github.com/github/docs/blob/a48998c7890b71c8f58eda1fa31b50df348a0042/javascripts/index.js#L22)

View File

@@ -2,7 +2,7 @@
const fs = require('fs') const fs = require('fs')
const path = require('path') const path = require('path')
const XlsxPopulate = require('xlsx-populate') const XlsxPopulate = require('xlsx-populate') // this is an optional dependency, install with `npm i --include=optional`
const readFrontmatter = require('../../lib/read-frontmatter') const readFrontmatter = require('../../lib/read-frontmatter')
const START_ROW = 2 const START_ROW = 2

View File

@@ -3,7 +3,7 @@
const fs = require('fs') const fs = require('fs')
const path = require('path') const path = require('path')
const walk = require('walk-sync') const walk = require('walk-sync')
const jimp = require('jimp') const jimp = require('jimp') // this is an optional dependency, install with `npm i --include=optional`
// iterate through enterprise images from most recent to oldest // iterate through enterprise images from most recent to oldest
// check if the image in the /assets/enterprise/... directory // check if the image in the /assets/enterprise/... directory

View File

@@ -7,7 +7,7 @@
// //
// [end-readme] // [end-readme]
const fs = require('fs') const fs = require('fs/promises')
const path = require('path') const path = require('path')
const matter = require('gray-matter') const matter = require('gray-matter')
const walk = require('walk-sync') const walk = require('walk-sync')
@@ -27,29 +27,40 @@ async function main () {
}) })
.map(filename => `translations/${filename}`) .map(filename => `translations/${filename}`)
const extractFrontmatter = async (path) => { console.log(
(await Promise.all(
translatedMarkdownFiles
.map(async relPath => updateTranslatedMarkdownFile(relPath)
.catch(e => `Error in ${relPath}: ${e.message}`)
)
)).filter(Boolean).join('\n')
)
}
async function extractFrontmatter (path) {
const fileContents = await readFileAsync(path, 'utf8') const fileContents = await readFileAsync(path, 'utf8')
return fm(fileContents) return fm(fileContents)
} }
for (const relPath of translatedMarkdownFiles) { async function updateTranslatedMarkdownFile (relPath) {
const localisedAbsPath = path.posix.join(__dirname, '../..', relPath) const localisedAbsPath = path.posix.join(__dirname, '../..', relPath)
// find the corresponding english file by removing the first 2 path segments: /translations/<language code> // find the corresponding english file by removing the first 2 path segments: /translations/<language code>
const engAbsPath = path.posix.join(__dirname, '../..', relPath.split(path.sep).slice(2).join(path.sep)) const engAbsPath = path.posix.join(__dirname, '../..', relPath.split(path.sep).slice(2).join(path.sep))
if (!fs.existsSync(engAbsPath)) { // Load frontmatter from the source english file
let englishFrontmatter
try {
englishFrontmatter = await extractFrontmatter(engAbsPath)
} catch {
// This happens when an English file has been moved or deleted and translations are not in sync. // This happens when an English file has been moved or deleted and translations are not in sync.
// It does mean this script will not homogenous those translated files, but the docs site does not // It does mean this script will not homogenous those translated files, but the docs site does not
// load translated files that don't correlate to an English file, so those translated files can't break things. // load translated files that don't correlate to an English file, so those translated files can't break things.
console.log(`English file does not exist: ${engAbsPath}`) // return `${relPath}: English file does not exist: ${engAbsPath}`
continue return // silence
} }
const localisedFrontmatter = await extractFrontmatter(localisedAbsPath) const localisedFrontmatter = await extractFrontmatter(localisedAbsPath)
if (!localisedFrontmatter) continue if (!localisedFrontmatter) return `${relPath}: No localised frontmatter`
// Load frontmatter from the source english file
const englishFrontmatter = await extractFrontmatter(engAbsPath)
// Look for differences between the english and localised non-translatable properties // Look for differences between the english and localised non-translatable properties
let overwroteSomething = false let overwroteSomething = false
@@ -63,10 +74,9 @@ async function main () {
// rewrite the localised file, if it changed // rewrite the localised file, if it changed
if (overwroteSomething) { if (overwroteSomething) {
const toWrite = matter.stringify(localisedFrontmatter.content, localisedFrontmatter.data, { lineWidth: 10000, forceQuotes: true }) const toWrite = matter.stringify(localisedFrontmatter.content, localisedFrontmatter.data, { lineWidth: 10000, forceQuotes: true })
fs.writeFileSync(localisedAbsPath, toWrite) await fs.writeFile(localisedAbsPath, toWrite)
// if it's fixable, output its path to store in the log file // return `${relPath}: updated`
console.log(relPath) // silence
}
} }
} }

View File

@@ -10,6 +10,16 @@ by Facebook and used by many teams at GitHub. Jest is convenient in that it
provides everything: a test runner, an assertion library, code coverage analysis, provides everything: a test runner, an assertion library, code coverage analysis,
custom reporters for different types of test output, etc. custom reporters for different types of test output, etc.
### Install optional dependencies
We typically rely on CI to run our tests, so we consider some large test-only
dependencies **optional** (for example, puppeteer). In order to run the tests locally you'll
need to make sure optional dependencies are installed by running:
```sh
npm ci --include=optional
```
### Running all the tests ### Running all the tests
Once you've followed the development instructions above, you can run the entire Once you've followed the development instructions above, you can run the entire
@@ -62,17 +72,18 @@ This test checks all internal links and image references in the English site. To
```sh ```sh
npx jest links-and-images npx jest links-and-images
``` ```
It checks images, anchors, and links for every **version** of every **page**. It checks images, anchors, and links for every **version** of every **page**.
It reports five types of problems: It reports five types of problems:
1. **Broken image references** 1. **Broken image references**
* Example: `/assets/images/foo.png` where `foo.png` doesn't exist. - Example: `/assets/images/foo.png` where `foo.png` doesn't exist.
2. **Broken same-page anchors** 2. **Broken same-page anchors**
* Example: `#foo` where the page does not have a heading `Foo`. - Example: `#foo` where the page does not have a heading `Foo`.
3. **Broken links due to page not found** 3. **Broken links due to page not found**
* Example: `/github/using-git/foo` where there is no `foo.md` file at that path. - Example: `/github/using-git/foo` where there is no `foo.md` file at that path.
4. **Broken links due to versioning** 4. **Broken links due to versioning**
* Example: an unversioned link to a Dotcom-only article in a page that has Enterprise versions. - Example: an unversioned link to a Dotcom-only article in a page that has Enterprise versions.
5. **Broken anchors on links** 5. **Broken anchors on links**
* Example: `/some/valid/link#bar` where the linked page can be found but it does not have a heading `Bar`. - Example: `/some/valid/link#bar` where the linked page can be found but it does not have a heading `Bar`.

View File

@@ -2,7 +2,6 @@ const path = require('path')
const MiniCssExtractPlugin = require('mini-css-extract-plugin') const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin') const CopyWebpackPlugin = require('copy-webpack-plugin')
const { EnvironmentPlugin, ProvidePlugin } = require('webpack') const { EnvironmentPlugin, ProvidePlugin } = require('webpack')
const { reactBabelOptions } = require('./lib/react/babel')
module.exports = { module.exports = {
mode: 'development', mode: 'development',
@@ -16,19 +15,9 @@ module.exports = {
stats: 'errors-only', stats: 'errors-only',
module: { module: {
rules: [ rules: [
{
test: /\.js$/,
include: [
path.resolve(__dirname, 'react')
],
use: {
loader: 'babel-loader',
options: reactBabelOptions
}
},
{ {
test: /\.m?js$/, test: /\.m?js$/,
exclude: /(node_modules|bower_components|react)/, exclude: /(node_modules)/,
use: { use: {
loader: 'babel-loader', loader: 'babel-loader',
options: { options: {