Skip to content

Commit 641bfa7

Browse files
authored
Merge branch 'main' into desktop-git-tags
2 parents 96a69c9 + a3f5992 commit 641bfa7

File tree

8 files changed

+235
-58
lines changed

8 files changed

+235
-58
lines changed
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
name: Check for External Repo Sync PR
2+
3+
on:
4+
pull_request:
5+
types:
6+
- opened
7+
- reopened
8+
branches:
9+
- main
10+
11+
jobs:
12+
invalid-repo-sync-check:
13+
name: Close external Repo Sync PRs
14+
if: ${{ github.repository == 'github/docs' && github.ref == 'refs/heads/repo-sync' }}
15+
runs-on: ubuntu-latest
16+
steps:
17+
- uses: actions/github-script@626af12fe9a53dc2972b48385e7fe7dec79145c9
18+
with:
19+
github-token: ${{ secrets.DOCUBOT_FR_PROJECT_BOARD_WORKFLOWS_REPO_ORG_READ_SCOPES }}
20+
script: |
21+
22+
const prCreator = context.payload.sender.login
23+
24+
// If the PR creator is the expected account, stop now
25+
if (prCreator === 'Octomerger') {
26+
return
27+
}
28+
29+
try {
30+
await github.teams.getMembershipForUserInOrg({
31+
org: 'github',
32+
team_slug: 'employees',
33+
username: prCreator
34+
})
35+
36+
// If the PR creator is a GitHub employee, stop now
37+
return
38+
} catch (err) {
39+
// An error will be thrown if the user is not a GitHub employee.
40+
// That said, we still want to proceed anyway!
41+
}
42+
43+
const pr = context.payload.pull_request
44+
const { owner, repo } = context.repo
45+
46+
// Close the PR and add the invalid label
47+
await github.issues.update({
48+
owner: owner,
49+
repo: repo,
50+
issue_number: pr.number,
51+
labels: ['invalid'],
52+
state: 'closed'
53+
})
54+
55+
// Comment on the PR
56+
await github.issues.createComment({
57+
owner: owner,
58+
repo: repo,
59+
issue_number: pr.number,
60+
body: "Please leave this `repo-sync` branch to the robots!\n\nI'm going to close this pull request now, but feel free to open a new issue or ask any questions in [discussions](https://github.com/github/docs/discussions)!"
61+
})

.github/workflows/test-translations.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ jobs:
1717
with:
1818
ref: translations # check out the 'translations' branch
1919

20+
- name: Check out tip of main
21+
run: git fetch --depth=1 origin main
22+
2023
- name: Setup node
2124
uses: actions/setup-node@c46424eee26de4078d34105d3de3cc4992202b1e
2225
with:
@@ -41,6 +44,9 @@ jobs:
4144
- name: Run linter
4245
run: npx eslint .
4346

47+
- name: Lint translated content
48+
run: npm run lint-translation
49+
4450
- name: Check dependencies
4551
run: npm run check-deps
4652

.github/workflows/triage-unallowed-contributions.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ jobs:
7878
with:
7979
github-token: ${{secrets.GITHUB_TOKEN}}
8080
script: |
81-
constFilesArr = [
81+
const badFilesArr = [
8282
'translations/**',
8383
'lib/rest/static/**',
8484
'.github/workflows/**',

content/github/administering-a-repository/about-protected-branches.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,12 +82,12 @@ After enabling required status checks, all required status checks must pass befo
8282

8383
{% endnote %}
8484

85-
You can set up required status checks to either be "loose" or "strict." The type of required status check you choose determines whether your branch is required to be up-to-date with the base branch before merging.
85+
You can set up required status checks to either be "loose" or "strict." The type of required status check you choose determines whether your branch is required to be up to date with the base branch before merging.
8686

8787
| Type of required status check | Setting | Merge requirements | Considerations |
8888
| --- | --- | --- | --- |
89-
| **Strict** | The **Require branches to be up-to-date before merging** checkbox is checked. | The branch **must** be up to date with the base branch before merging. | This is the default behavior for required status checks. More builds may be required, as you'll need to bring the head branch up to date after other collaborators merge pull requests to the protected base branch.|
90-
| **Loose** | The **Require branches to be up-to-date before merging** checkbox is **not** checked. | The branch **does not** have to be up to date with the base branch before merging. | You'll have fewer required builds, as you won't need to bring the head branch up to date after other collaborators merge pull requests. Status checks may fail after you merge your branch if there are incompatible changes with the base branch. |
89+
| **Strict** | The **Require branches to be up to date before merging** checkbox is checked. | The branch **must** be up to date with the base branch before merging. | This is the default behavior for required status checks. More builds may be required, as you'll need to bring the head branch up to date after other collaborators merge pull requests to the protected base branch.|
90+
| **Loose** | The **Require branches to be up to date before merging** checkbox is **not** checked. | The branch **does not** have to be up to date with the base branch before merging. | You'll have fewer required builds, as you won't need to bring the head branch up to date after other collaborators merge pull requests. Status checks may fail after you merge your branch if there are incompatible changes with the base branch. |
9191
| **Disabled** | The **Require status checks to pass before merging** checkbox is **not** checked. | The branch has no merge restrictions. | If required status checks aren't enabled, collaborators can merge the branch at any time, regardless of whether it is up to date with the base branch. This increases the possibility of incompatible changes.
9292

9393
For troubleshooting information, see "[Troubleshooting required status checks](/github/administering-a-repository/troubleshooting-required-status-checks)."

jest.config.js

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@
22

33
const isBrowser = process.env.BROWSER
44
const isActions = Boolean(process.env.GITHUB_ACTIONS)
5+
const testTranslation = Boolean(process.env.TEST_TRANSLATION)
6+
7+
let reporters = ['default']
8+
9+
if (testTranslation) {
10+
// only use custom reporter if we are linting translations
11+
reporters = ['<rootDir>/tests/helpers/lint-translation-reporter.js']
12+
} else if (isActions) {
13+
reporters.push('jest-github-actions-reporter')
14+
}
515

616
module.exports = {
717
coverageThreshold: {
@@ -15,9 +25,7 @@ module.exports = {
1525
preset: isBrowser
1626
? 'jest-puppeteer'
1727
: undefined,
18-
reporters: isActions
19-
? ['default', 'jest-github-actions-reporter']
20-
: ['default'],
28+
reporters,
2129
modulePathIgnorePatterns: [
2230
'assets/'
2331
],

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@
170170
"build": "cross-env NODE_ENV=production npx webpack --mode production",
171171
"start-all-languages": "cross-env NODE_ENV=development nodemon server.js",
172172
"lint": "eslint --fix . && prettier -w \"**/*.{yml,yaml}\"",
173+
"lint-translation": "TEST_TRANSLATION=true jest content/lint-files",
173174
"test": "jest && eslint . && prettier -c \"**/*.{yml,yaml}\" && npm run check-deps",
174175
"prebrowser-test": "npm run build",
175176
"browser-test": "start-server-and-test browser-test-server 4001 browser-test-tests",

tests/content/lint-files.js

Lines changed: 109 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ const path = require('path')
22
const slash = require('slash')
33
const fs = require('fs')
44
const walk = require('walk-sync')
5-
const { zip } = require('lodash')
5+
const { zip, groupBy } = require('lodash')
66
const yaml = require('js-yaml')
77
const revalidator = require('revalidator')
88
const generateMarkdownAST = require('mdast-util-from-markdown')
@@ -12,12 +12,14 @@ const languages = require('../../lib/languages')
1212
const { tags } = require('../../lib/liquid-tags/extended-markdown')
1313
const ghesReleaseNotesSchema = require('../../lib/release-notes-schema')
1414
const renderContent = require('../../lib/render-content')
15+
const { execSync } = require('child_process')
1516

1617
const rootDir = path.join(__dirname, '../..')
1718
const contentDir = path.join(rootDir, 'content')
1819
const reusablesDir = path.join(rootDir, 'data/reusables')
1920
const variablesDir = path.join(rootDir, 'data/variables')
2021
const glossariesDir = path.join(rootDir, 'data/glossaries')
22+
const ghesReleaseNotesDir = path.join(rootDir, 'data/release-notes')
2123

2224
const languageCodes = Object.keys(languages)
2325

@@ -149,13 +151,26 @@ const oldVariableErrorText = 'Found article uses old {{ site.data... }} syntax.
149151
const oldOcticonErrorText = 'Found octicon variables with the old {{ octicon-name }} syntax. Use {% octicon "name" %} instead!'
150152
const oldExtendedMarkdownErrorText = 'Found extended markdown tags with the old {{#note}} syntax. Use {% note %}/{% endnote %} instead!'
151153

152-
describe('lint-files', () => {
153-
const mdWalkOptions = {
154-
globs: ['**/*.md'],
155-
ignore: ['**/README.md'],
156-
directories: false,
157-
includeBasePath: true
158-
}
154+
const mdWalkOptions = {
155+
globs: ['**/*.md'],
156+
ignore: ['**/README.md'],
157+
directories: false,
158+
includeBasePath: true
159+
}
160+
161+
// Also test the "data/variables/" YAML files
162+
163+
const yamlWalkOptions = {
164+
globs: ['**/*.yml'],
165+
directories: false,
166+
includeBasePath: true
167+
}
168+
169+
// different lint rules apply to different content types
170+
let mdToLint, ymlToLint, releaseNotesToLint
171+
172+
if (!process.env.TEST_TRANSLATION) {
173+
// compile lists of all the files we want to lint
159174

160175
const contentMarkdownAbsPaths = walk(contentDir, mdWalkOptions).sort()
161176
const contentMarkdownRelPaths = contentMarkdownAbsPaths.map(p => slash(path.relative(rootDir, p)))
@@ -165,16 +180,81 @@ describe('lint-files', () => {
165180
const reusableMarkdownRelPaths = reusableMarkdownAbsPaths.map(p => slash(path.relative(rootDir, p)))
166181
const reusableMarkdownTuples = zip(reusableMarkdownRelPaths, reusableMarkdownAbsPaths)
167182

168-
describe.each([...contentMarkdownTuples, ...reusableMarkdownTuples])(
169-
'in "%s"',
183+
mdToLint = [...contentMarkdownTuples, ...reusableMarkdownTuples]
184+
185+
// data/variables
186+
const variableYamlAbsPaths = walk(variablesDir, yamlWalkOptions).sort()
187+
const variableYamlRelPaths = variableYamlAbsPaths.map(p => slash(path.relative(rootDir, p)))
188+
const variableYamlTuples = zip(variableYamlRelPaths, variableYamlAbsPaths)
189+
190+
// data/glossaries
191+
const glossariesYamlAbsPaths = walk(glossariesDir, yamlWalkOptions).sort()
192+
const glossariesYamlRelPaths = glossariesYamlAbsPaths.map(p => slash(path.relative(rootDir, p)))
193+
const glossariesYamlTuples = zip(glossariesYamlRelPaths, glossariesYamlAbsPaths)
194+
195+
ymlToLint = [...variableYamlTuples, ...glossariesYamlTuples]
196+
197+
// GHES release notes
198+
const ghesReleaseNotesYamlAbsPaths = walk(ghesReleaseNotesDir, yamlWalkOptions).sort()
199+
const ghesReleaseNotesYamlRelPaths = ghesReleaseNotesYamlAbsPaths.map(p => path.relative(rootDir, p))
200+
releaseNotesToLint = zip(ghesReleaseNotesYamlRelPaths, ghesReleaseNotesYamlAbsPaths)
201+
} else {
202+
console.log('testing translations.')
203+
204+
// get all translated markdown or yaml files by comparing files changed to main branch
205+
const changedFilesRelPaths = execSync('git diff --name-only origin/main | egrep "^translations/.*/.+.(yml|md)$"').toString().split('\n')
206+
console.log(`Found ${changedFilesRelPaths.length} translated files.`)
207+
208+
const { mdRelPaths, ymlRelPaths, releaseNotesRelPaths } = groupBy(changedFilesRelPaths, (path) => {
209+
// separate the changed files to different groups
210+
if (path.endsWith('README.md')) {
211+
return 'throwAway'
212+
} else if (path.endsWith('.md')) {
213+
return 'mdRelPaths'
214+
} else if (path.match(/\/data\/(variables|glossaries)\//i)) {
215+
return 'ymlRelPaths'
216+
} else if (path.match(/\/data\/release-notes\//i)) {
217+
return 'releaseNotesRelPaths'
218+
} else {
219+
// we aren't linting the rest
220+
return 'throwAway'
221+
}
222+
})
223+
224+
const [mdTuples, ymlTuples, releaseNotesTuples] = [mdRelPaths, ymlRelPaths, releaseNotesRelPaths].map(relPaths => {
225+
const absPaths = relPaths.map(p => path.join(rootDir, p))
226+
return zip(relPaths, absPaths)
227+
})
228+
229+
mdToLint = mdTuples
230+
ymlToLint = ymlTuples
231+
releaseNotesToLint = releaseNotesTuples
232+
}
233+
234+
function formatLinkError (message, links) {
235+
return `${message}\n - ${links.join('\n - ')}`
236+
}
237+
238+
// Returns `content` if its a string, or `content.description` if it can.
239+
// Used for getting the nested `description` key in glossary files.
240+
function getContent (content) {
241+
if (typeof content === 'string') return content
242+
if (typeof content.description === 'string') return content.description
243+
return null
244+
}
245+
246+
describe('lint markdown content', () => {
247+
describe.each(mdToLint)(
248+
'%s',
170249
(markdownRelPath, markdownAbsPath) => {
171-
let content, ast, links, isHidden, isEarlyAccess, isSitePolicy
250+
let content, ast, links, isHidden, isEarlyAccess, isSitePolicy, frontmatterErrors
172251

173252
beforeAll(async () => {
174253
const fileContents = await fs.promises.readFile(markdownAbsPath, 'utf8')
175-
const { data, content: bodyContent } = frontmatter(fileContents)
254+
const { data, content: bodyContent, errors } = frontmatter(fileContents)
176255

177256
content = bodyContent
257+
frontmatterErrors = errors
178258
ast = generateMarkdownAST(content)
179259
isHidden = data.hidden === true
180260
isEarlyAccess = markdownRelPath.split('/').includes('early-access')
@@ -307,34 +387,20 @@ describe('lint-files', () => {
307387
.resolves
308388
.toBeTruthy()
309389
})
390+
391+
if (!markdownRelPath.includes('data/reusables')) {
392+
test('contains valid frontmatter', () => {
393+
const errorMessage = frontmatterErrors.map(error => `- [${error.property}]: ${error.actual}, ${error.message}`).join('\n')
394+
expect(frontmatterErrors.length, errorMessage).toBe(0)
395+
})
396+
}
310397
}
311398
)
399+
})
312400

313-
// Also test the "data/variables/" YAML files
314-
const yamlWalkOptions = {
315-
globs: ['**/*.yml'],
316-
directories: false,
317-
includeBasePath: true
318-
}
319-
320-
const variableYamlAbsPaths = walk(variablesDir, yamlWalkOptions).sort()
321-
const variableYamlRelPaths = variableYamlAbsPaths.map(p => slash(path.relative(rootDir, p)))
322-
const variableYamlTuples = zip(variableYamlRelPaths, variableYamlAbsPaths)
323-
324-
const glossariesYamlAbsPaths = walk(glossariesDir, yamlWalkOptions).sort()
325-
const glossariesYamlRelPaths = glossariesYamlAbsPaths.map(p => slash(path.relative(rootDir, p)))
326-
const glossariesYamlTuples = zip(glossariesYamlRelPaths, glossariesYamlAbsPaths)
327-
328-
// Returns `content` if its a string, or `content.description` if it can.
329-
// Used for getting the nested `description` key in glossary files.
330-
function getContent (content) {
331-
if (typeof content === 'string') return content
332-
if (typeof content.description === 'string') return content.description
333-
return null
334-
}
335-
336-
describe.each([...variableYamlTuples, ...glossariesYamlTuples])(
337-
'in "%s"',
401+
describe('lint yaml content', () => {
402+
describe.each(ymlToLint)(
403+
'%s',
338404
(yamlRelPath, yamlAbsPath) => {
339405
let dictionary, isEarlyAccess
340406

@@ -518,16 +584,12 @@ describe('lint-files', () => {
518584
})
519585
}
520586
)
587+
})
521588

522-
// GHES release notes
523-
const ghesReleaseNotesDir = path.join(__dirname, '../../data/release-notes')
524-
const ghesReleaseNotesYamlAbsPaths = walk(ghesReleaseNotesDir, yamlWalkOptions).sort()
525-
const ghesReleaseNotesYamlRelPaths = ghesReleaseNotesYamlAbsPaths.map(p => path.relative(rootDir, p))
526-
const ghesReleaseNotesYamlTuples = zip(ghesReleaseNotesYamlRelPaths, ghesReleaseNotesYamlAbsPaths)
527-
528-
if (ghesReleaseNotesYamlTuples.length > 0) {
529-
describe.each(ghesReleaseNotesYamlTuples)(
530-
'in "%s"',
589+
describe('lint release notes', () => {
590+
if (releaseNotesToLint.length > 0) {
591+
describe.each(releaseNotesToLint)(
592+
'%s',
531593
(yamlRelPath, yamlAbsPath) => {
532594
let dictionary
533595

@@ -538,14 +600,10 @@ describe('lint-files', () => {
538600

539601
it('matches the schema', () => {
540602
const { errors } = revalidator.validate(dictionary, ghesReleaseNotesSchema)
541-
const errorMessage = errors.map(error => `- [${error.property}]: ${error.attribute}, ${error.message}`).join('\n')
603+
const errorMessage = errors.map(error => `- [${error.property}]: ${error.actual}, ${error.message}`).join('\n')
542604
expect(errors.length, errorMessage).toBe(0)
543605
})
544606
}
545607
)
546608
}
547609
})
548-
549-
function formatLinkError (message, links) {
550-
return `${message}\n - ${links.join('\n - ')}`
551-
}

0 commit comments

Comments
 (0)