Improvements in tools to help merge crowdin PRs (#18409)
- add `script/test-render-translation.js` to render all translated content to catch malformed liquid that would cause render errors - improve test output for `script/fix-translation-errors.js` and `tests/content/lint-files.js` - make it so `script/reset-translated-file.js` can handle files that have been renamed
This commit is contained in:
@@ -4,11 +4,11 @@ const isBrowser = process.env.BROWSER
|
|||||||
const isActions = Boolean(process.env.GITHUB_ACTIONS)
|
const isActions = Boolean(process.env.GITHUB_ACTIONS)
|
||||||
const testTranslation = Boolean(process.env.TEST_TRANSLATION)
|
const testTranslation = Boolean(process.env.TEST_TRANSLATION)
|
||||||
|
|
||||||
const reporters = ['default']
|
let reporters = ['default']
|
||||||
|
|
||||||
if (testTranslation) {
|
if (testTranslation) {
|
||||||
// only use custom reporter if we are linting translations
|
// only use custom reporter if we are linting translations
|
||||||
reporters.push('<rootDir>/tests/helpers/lint-translation-reporter.js')
|
reporters = ['<rootDir>/tests/helpers/lint-translation-reporter.js']
|
||||||
} else if (isActions) {
|
} else if (isActions) {
|
||||||
reporters.push('jest-github-actions-reporter')
|
reporters.push('jest-github-actions-reporter')
|
||||||
}
|
}
|
||||||
|
|||||||
63
package-lock.json
generated
63
package-lock.json
generated
@@ -5537,7 +5537,7 @@
|
|||||||
},
|
},
|
||||||
"agentkeepalive": {
|
"agentkeepalive": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"resolved": "http://registry.npmjs.org/agentkeepalive/-/agentkeepalive-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-2.2.0.tgz",
|
||||||
"integrity": "sha1-xdG9SxKQCPEWPyNvhuX66iAm4u8="
|
"integrity": "sha1-xdG9SxKQCPEWPyNvhuX66iAm4u8="
|
||||||
},
|
},
|
||||||
"aggregate-error": {
|
"aggregate-error": {
|
||||||
@@ -5958,6 +5958,16 @@
|
|||||||
"has-ansi": "^2.0.0",
|
"has-ansi": "^2.0.0",
|
||||||
"strip-ansi": "^3.0.0",
|
"strip-ansi": "^3.0.0",
|
||||||
"supports-color": "^2.0.0"
|
"supports-color": "^2.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"strip-ansi": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
|
||||||
|
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
|
||||||
|
"requires": {
|
||||||
|
"ansi-regex": "^2.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"js-tokens": {
|
"js-tokens": {
|
||||||
@@ -7083,7 +7093,7 @@
|
|||||||
},
|
},
|
||||||
"brfs": {
|
"brfs": {
|
||||||
"version": "1.6.1",
|
"version": "1.6.1",
|
||||||
"resolved": "http://registry.npmjs.org/brfs/-/brfs-1.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/brfs/-/brfs-1.6.1.tgz",
|
||||||
"integrity": "sha512-OfZpABRQQf+Xsmju8XE9bDjs+uU4vLREGolP7bDgcpsI17QREyZ4Bl+2KLxxx1kCgA0fAIhKQBaBYh+PEcCqYQ==",
|
"integrity": "sha512-OfZpABRQQf+Xsmju8XE9bDjs+uU4vLREGolP7bDgcpsI17QREyZ4Bl+2KLxxx1kCgA0fAIhKQBaBYh+PEcCqYQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"quote-stream": "^1.0.1",
|
"quote-stream": "^1.0.1",
|
||||||
@@ -11689,6 +11699,23 @@
|
|||||||
"has-ansi": "^2.0.0",
|
"has-ansi": "^2.0.0",
|
||||||
"strip-ansi": "^3.0.0",
|
"strip-ansi": "^3.0.0",
|
||||||
"supports-color": "^2.0.0"
|
"supports-color": "^2.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-regex": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
|
||||||
|
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"strip-ansi": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
|
||||||
|
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"ansi-regex": "^2.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"is-fullwidth-code-point": {
|
"is-fullwidth-code-point": {
|
||||||
@@ -18386,7 +18413,7 @@
|
|||||||
},
|
},
|
||||||
"magic-string": {
|
"magic-string": {
|
||||||
"version": "0.22.5",
|
"version": "0.22.5",
|
||||||
"resolved": "http://registry.npmjs.org/magic-string/-/magic-string-0.22.5.tgz",
|
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.22.5.tgz",
|
||||||
"integrity": "sha512-oreip9rJZkzvA8Qzk9HFs8fZGF/u7H/gtrE8EN6RjKJ9kh2HlC+yQ2QezifqTZfGyiuAV0dRv5a+y/8gBb1m9w==",
|
"integrity": "sha512-oreip9rJZkzvA8Qzk9HFs8fZGF/u7H/gtrE8EN6RjKJ9kh2HlC+yQ2QezifqTZfGyiuAV0dRv5a+y/8gBb1m9w==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"vlq": "^0.2.2"
|
"vlq": "^0.2.2"
|
||||||
@@ -19934,6 +19961,17 @@
|
|||||||
"has-ansi": "^2.0.0",
|
"has-ansi": "^2.0.0",
|
||||||
"strip-ansi": "^3.0.0",
|
"strip-ansi": "^3.0.0",
|
||||||
"supports-color": "^2.0.0"
|
"supports-color": "^2.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"strip-ansi": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
|
||||||
|
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"ansi-regex": "^2.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"commander": {
|
"commander": {
|
||||||
@@ -22957,11 +22995,20 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"strip-ansi": {
|
"strip-ansi": {
|
||||||
"version": "3.0.1",
|
"version": "6.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
|
||||||
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
|
"integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
|
||||||
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"ansi-regex": "^2.0.0"
|
"ansi-regex": "^5.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-regex": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"strip-bom": {
|
"strip-bom": {
|
||||||
@@ -23413,7 +23460,7 @@
|
|||||||
},
|
},
|
||||||
"through": {
|
"through": {
|
||||||
"version": "2.3.8",
|
"version": "2.3.8",
|
||||||
"resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz",
|
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
|
||||||
"integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU="
|
"integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU="
|
||||||
},
|
},
|
||||||
"through2": {
|
"through2": {
|
||||||
|
|||||||
@@ -158,6 +158,7 @@
|
|||||||
"replace": "^1.2.0",
|
"replace": "^1.2.0",
|
||||||
"robots-parser": "^2.1.1",
|
"robots-parser": "^2.1.1",
|
||||||
"start-server-and-test": "^1.12.0",
|
"start-server-and-test": "^1.12.0",
|
||||||
|
"strip-ansi": "^6.0.0",
|
||||||
"supertest": "^4.0.2",
|
"supertest": "^4.0.2",
|
||||||
"url-template": "^2.0.8",
|
"url-template": "^2.0.8",
|
||||||
"webpack-dev-middleware": "^3.7.2",
|
"webpack-dev-middleware": "^3.7.2",
|
||||||
|
|||||||
@@ -71,13 +71,14 @@ changedFilesRelPaths.forEach(async (relPath) => {
|
|||||||
if (!engResult) return
|
if (!engResult) return
|
||||||
const { data: engData } = engResult
|
const { data: engData } = engResult
|
||||||
|
|
||||||
console.log(chalk.red('fixing errors in ') + chalk.bold(relPath))
|
console.log(chalk.bold(relPath))
|
||||||
|
|
||||||
const newData = data
|
const newData = data
|
||||||
|
|
||||||
fixableErrors.forEach(({ property }) => {
|
fixableErrors.forEach(({ property, message }) => {
|
||||||
const correctValue = get(engData, property)
|
const correctValue = get(engData, property)
|
||||||
console.log(` [${property}]: ${get(data, property)} -> ${correctValue}`)
|
console.log(chalk.red(` error message: [${property}] ${message}`))
|
||||||
|
console.log(` fix property [${property}]: ${get(data, property)} -> ${correctValue}`)
|
||||||
set(newData, property, correctValue)
|
set(newData, property, correctValue)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -13,20 +13,8 @@
|
|||||||
//
|
//
|
||||||
// Examples:
|
// Examples:
|
||||||
//
|
//
|
||||||
// reset a single translated file using a relative path:
|
|
||||||
// $ script/reset-translated-file.js translations/es-XL/content/actions/index.md
|
// $ script/reset-translated-file.js translations/es-XL/content/actions/index.md
|
||||||
//
|
//
|
||||||
// reset a single translated file using a full path:
|
|
||||||
// $ script/reset-translated-file.js /Users/z/git/github/docs-internal/translations/es-XL/content/actions/index.md
|
|
||||||
//
|
|
||||||
// reset all language variants of a single English file (using a relative path):
|
|
||||||
// $ script/reset-translated-file.js content/actions/index.md
|
|
||||||
// $ script/reset-translated-file.js data/ui.yml
|
|
||||||
//
|
|
||||||
// reset all language variants of a single English file (using a full path):
|
|
||||||
// $ script/reset-translated-file.js /Users/z/git/github/docs-internal/content/desktop/index.md
|
|
||||||
// $ script/reset-translated-file.js /Users/z/git/github/docs-internal/data/ui.yml
|
|
||||||
//
|
|
||||||
// [end-readme]
|
// [end-readme]
|
||||||
|
|
||||||
const program = require('commander')
|
const program = require('commander')
|
||||||
@@ -34,42 +22,47 @@ const { execSync } = require('child_process')
|
|||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const languages = require('../lib/languages')
|
const chalk = require('chalk')
|
||||||
|
|
||||||
program
|
program
|
||||||
.description('reset translated files')
|
.description('reset translated files')
|
||||||
.option('-m, --use-main', 'Reset file to the translated file from `main` branch instead of from English source.')
|
.option('-m, --prefer-main', 'Reset file to the translated file, try using the file from `main` branch first, if not found (usually due to renaming), fall back to English source.')
|
||||||
.parse(process.argv)
|
.parse(process.argv)
|
||||||
|
|
||||||
|
const resetToEnglishSource = (translationFilePath) => {
|
||||||
|
assert(translationFilePath.startsWith('translations/'), 'path argument must be in the format `translations/<lang>/path/to/file`')
|
||||||
|
assert(fs.existsSync(translationFilePath), `file does not exist: ${translationFilePath}`)
|
||||||
|
|
||||||
|
const relativePath = translationFilePath.split(path.sep).slice(2).join(path.sep)
|
||||||
|
const englishFile = path.join(process.cwd(), relativePath)
|
||||||
|
assert(fs.existsSync(englishFile), `file does not exist: ${englishFile}`)
|
||||||
|
|
||||||
|
// replace file with English source
|
||||||
|
const englishContent = fs.readFileSync(englishFile, 'utf8')
|
||||||
|
fs.writeFileSync(translationFilePath, englishContent)
|
||||||
|
console.log('-> reverted to English: %s', path.relative(process.cwd(), translationFilePath))
|
||||||
|
}
|
||||||
|
|
||||||
const [pathArg] = program.args
|
const [pathArg] = program.args
|
||||||
assert(pathArg, 'first arg must be a target filename')
|
assert(pathArg, 'first arg must be a target filename')
|
||||||
let languageCode
|
|
||||||
|
|
||||||
// Is the arg a fully-qualified path?
|
// Is the arg a fully-qualified path?
|
||||||
let relativePath = fs.existsSync(pathArg)
|
const relativePath = fs.existsSync(pathArg)
|
||||||
? path.relative(process.cwd(), pathArg)
|
? path.relative(process.cwd(), pathArg)
|
||||||
: pathArg
|
: pathArg
|
||||||
|
|
||||||
if (program.useMain) {
|
if (program.preferMain) {
|
||||||
execSync(`git checkout main -- ${relativePath}`)
|
try {
|
||||||
console.log('reverted to file from main branch: %s', relativePath)
|
execSync(`git checkout main -- ${relativePath}`, { stdio: 'pipe' })
|
||||||
} else {
|
console.log('-> reverted to file from main branch: %s', relativePath)
|
||||||
// extract relative path and language code if pathArg is in the format `translations/<lang>/path/to/file`
|
} catch (e) {
|
||||||
if (relativePath.startsWith('translations/')) {
|
if (e.message.includes('pathspec')) {
|
||||||
languageCode = Object.values(languages).find(language => relativePath.startsWith(language.dir) && language.code !== 'en').code
|
console.warn(chalk.red(`cannot find ${relativePath} in main branch (likely because it was renamed); falling back to English source file.`))
|
||||||
relativePath = relativePath.split(path.sep).slice(2).join(path.sep)
|
resetToEnglishSource(relativePath)
|
||||||
|
} else {
|
||||||
|
console.warn(e.message)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
const englishFile = path.join(process.cwd(), relativePath)
|
} else {
|
||||||
assert(fs.existsSync(englishFile), `file does not exist: ${englishFile}`)
|
resetToEnglishSource(relativePath)
|
||||||
const englishContent = fs.readFileSync(englishFile, 'utf8')
|
|
||||||
|
|
||||||
Object.values(languages).forEach(({ code }) => {
|
|
||||||
if (code === 'en') return
|
|
||||||
if (languageCode && languageCode !== code) return
|
|
||||||
|
|
||||||
const translatedFile = path.join(process.cwd(), languages[code].dir, relativePath)
|
|
||||||
fs.writeFileSync(translatedFile, englishContent)
|
|
||||||
console.log('reverted to English: %s', path.relative(process.cwd(), translatedFile))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|||||||
57
script/test-render-translation.js
Normal file
57
script/test-render-translation.js
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
const renderContent = require('../lib/render-content')
|
||||||
|
const loadSiteData = require('../lib/site-data')
|
||||||
|
const { loadPages } = require('../lib/pages')
|
||||||
|
const languages = require('../lib/languages')
|
||||||
|
const path = require('path')
|
||||||
|
const { execSync } = require('child_process')
|
||||||
|
const fs = require('fs')
|
||||||
|
const frontmatter = require('../lib/frontmatter')
|
||||||
|
const chalk = require('chalk')
|
||||||
|
|
||||||
|
const main = async () => {
|
||||||
|
const siteData = loadSiteData()
|
||||||
|
const pages = await loadPages()
|
||||||
|
const contextByLanguage = {}
|
||||||
|
for (const lang in languages) {
|
||||||
|
const langObj = languages[lang]
|
||||||
|
const [crowdinLangCode] = langObj.dir === '' ? 'en' : langObj.dir.split('/').slice(1)
|
||||||
|
if (!crowdinLangCode) continue
|
||||||
|
contextByLanguage[crowdinLangCode] = {
|
||||||
|
site: siteData[langObj.code].site,
|
||||||
|
currentLanguage: langObj.code,
|
||||||
|
currentVersion: 'free-pro-team@latest'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const rootDir = path.join(__dirname, '..')
|
||||||
|
|
||||||
|
const changedFilesRelPaths = execSync('git diff --name-only origin/main | egrep "^translations/.*/.+.md$"', { maxBuffer: 1024 * 1024 * 100 })
|
||||||
|
.toString()
|
||||||
|
.split('\n')
|
||||||
|
.filter(path => path !== '' && !path.endsWith('README.md'))
|
||||||
|
.sort()
|
||||||
|
|
||||||
|
console.log(`Found ${changedFilesRelPaths.length} translated files.`)
|
||||||
|
|
||||||
|
changedFilesRelPaths.forEach(async (relPath) => {
|
||||||
|
const fullPath = path.join(rootDir, relPath)
|
||||||
|
const lang = relPath.split('/')[1]
|
||||||
|
const context = {
|
||||||
|
...contextByLanguage[lang],
|
||||||
|
pages,
|
||||||
|
page: pages.find(page => page.fullPath === fullPath),
|
||||||
|
redirects: {}
|
||||||
|
}
|
||||||
|
if (!context.page && !relPath.includes('data/reusables')) return
|
||||||
|
const fileContents = await fs.promises.readFile(fullPath, 'utf8')
|
||||||
|
const { content } = frontmatter(fileContents)
|
||||||
|
try {
|
||||||
|
await renderContent.liquid.parseAndRender(content, context)
|
||||||
|
} catch (err) {
|
||||||
|
console.log(chalk.bold(relPath))
|
||||||
|
console.log(chalk.red(` error message: ${err.message}`))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
main()
|
||||||
@@ -202,7 +202,7 @@ if (!process.env.TEST_TRANSLATION) {
|
|||||||
console.log('testing translations.')
|
console.log('testing translations.')
|
||||||
|
|
||||||
// get all translated markdown or yaml files by comparing files changed to main branch
|
// get all translated markdown or yaml files by comparing files changed to main branch
|
||||||
const changedFilesRelPaths = execSync('git diff --name-only origin/main | egrep "^translations/.*/.+.(yml|md)$"').toString().split('\n')
|
const changedFilesRelPaths = execSync('git diff --name-only origin/main | egrep "^translations/.*/.+.(yml|md)$"', { maxBuffer: 1024 * 1024 * 100 }).toString().split('\n')
|
||||||
console.log(`Found ${changedFilesRelPaths.length} translated files.`)
|
console.log(`Found ${changedFilesRelPaths.length} translated files.`)
|
||||||
|
|
||||||
const { mdRelPaths = [], ymlRelPaths = [], releaseNotesRelPaths = [] } = groupBy(changedFilesRelPaths, (path) => {
|
const { mdRelPaths = [], ymlRelPaths = [], releaseNotesRelPaths = [] } = groupBy(changedFilesRelPaths, (path) => {
|
||||||
@@ -244,6 +244,7 @@ function getContent (content) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe('lint markdown content', () => {
|
describe('lint markdown content', () => {
|
||||||
|
if (mdToLint.length < 1) return
|
||||||
describe.each(mdToLint)(
|
describe.each(mdToLint)(
|
||||||
'%s',
|
'%s',
|
||||||
(markdownRelPath, markdownAbsPath) => {
|
(markdownRelPath, markdownAbsPath) => {
|
||||||
@@ -292,36 +293,6 @@ describe('lint markdown content', () => {
|
|||||||
expect(matches.length, errorMessage).toBe(0)
|
expect(matches.length, errorMessage).toBe(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('URLs must not contain a hard-coded language code', async () => {
|
|
||||||
const matches = links.filter(link => {
|
|
||||||
return /\/(?:${languageCodes.join('|')})\//.test(link)
|
|
||||||
})
|
|
||||||
|
|
||||||
const errorMessage = formatLinkError(languageLinkErrorText, matches)
|
|
||||||
expect(matches.length, errorMessage).toBe(0)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('URLs must not contain a hard-coded version number', async () => {
|
|
||||||
const initialMatches = (content.match(versionLinkRegEx) || [])
|
|
||||||
|
|
||||||
// Filter out some very specific false positive matches
|
|
||||||
const matches = initialMatches.filter(match => {
|
|
||||||
if (markdownRelPath === 'content/admin/enterprise-management/migrating-from-github-enterprise-1110x-to-2123.md') {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
|
|
||||||
const errorMessage = formatLinkError(versionLinkErrorText, matches)
|
|
||||||
expect(matches.length, errorMessage).toBe(0)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('URLs must not contain a hard-coded domain name', async () => {
|
|
||||||
const matches = (content.match(domainLinkRegex) || [])
|
|
||||||
const errorMessage = formatLinkError(domainLinkErrorText, matches)
|
|
||||||
expect(matches.length, errorMessage).toBe(0)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('must not leak Early Access doc URLs', async () => {
|
test('must not leak Early Access doc URLs', async () => {
|
||||||
// Only execute for docs that are NOT Early Access
|
// Only execute for docs that are NOT Early Access
|
||||||
if (!isEarlyAccess) {
|
if (!isEarlyAccess) {
|
||||||
@@ -348,6 +319,7 @@ describe('lint markdown content', () => {
|
|||||||
expect(matches.length, errorMessage).toBe(0)
|
expect(matches.length, errorMessage).toBe(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (!process.env.TEST_TRANSLATION) {
|
||||||
test('does not use old site.data variable syntax', async () => {
|
test('does not use old site.data variable syntax', async () => {
|
||||||
const matches = (content.match(oldVariableRegex) || [])
|
const matches = (content.match(oldVariableRegex) || [])
|
||||||
const matchesWithExample = matches.map(match => {
|
const matchesWithExample = matches.map(match => {
|
||||||
@@ -380,6 +352,37 @@ describe('lint markdown content', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('URLs must not contain a hard-coded language code', async () => {
|
||||||
|
const matches = links.filter(link => {
|
||||||
|
return /\/(?:${languageCodes.join('|')})\//.test(link)
|
||||||
|
})
|
||||||
|
|
||||||
|
const errorMessage = formatLinkError(languageLinkErrorText, matches)
|
||||||
|
expect(matches.length, errorMessage).toBe(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('URLs must not contain a hard-coded version number', async () => {
|
||||||
|
const initialMatches = (content.match(versionLinkRegEx) || [])
|
||||||
|
|
||||||
|
// Filter out some very specific false positive matches
|
||||||
|
const matches = initialMatches.filter(match => {
|
||||||
|
if (markdownRelPath === 'content/admin/enterprise-management/migrating-from-github-enterprise-1110x-to-2123.md') {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
const errorMessage = formatLinkError(versionLinkErrorText, matches)
|
||||||
|
expect(matches.length, errorMessage).toBe(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('URLs must not contain a hard-coded domain name', async () => {
|
||||||
|
const matches = (content.match(domainLinkRegex) || [])
|
||||||
|
const errorMessage = formatLinkError(domainLinkErrorText, matches)
|
||||||
|
expect(matches.length, errorMessage).toBe(0)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
test('contains valid Liquid', async () => {
|
test('contains valid Liquid', async () => {
|
||||||
// If Liquid can't parse the file, it'll throw an error.
|
// If Liquid can't parse the file, it'll throw an error.
|
||||||
// For example, the following is invalid and will fail this test:
|
// For example, the following is invalid and will fail this test:
|
||||||
@@ -411,6 +414,7 @@ describe('lint markdown content', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('lint yaml content', () => {
|
describe('lint yaml content', () => {
|
||||||
|
if (ymlToLint.length < 1) return
|
||||||
describe.each(ymlToLint)(
|
describe.each(ymlToLint)(
|
||||||
'%s',
|
'%s',
|
||||||
(yamlRelPath, yamlAbsPath) => {
|
(yamlRelPath, yamlAbsPath) => {
|
||||||
@@ -439,54 +443,6 @@ describe('lint yaml content', () => {
|
|||||||
expect(matches.length, errorMessage).toBe(0)
|
expect(matches.length, errorMessage).toBe(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('URLs must not contain a hard-coded language code', async () => {
|
|
||||||
const matches = []
|
|
||||||
|
|
||||||
for (const [key, content] of Object.entries(dictionary)) {
|
|
||||||
const contentStr = getContent(content)
|
|
||||||
if (!contentStr) continue
|
|
||||||
const valMatches = (contentStr.match(languageLinkRegex) || [])
|
|
||||||
if (valMatches.length > 0) {
|
|
||||||
matches.push(...valMatches.map((match) => `Key "${key}": ${match}`))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const errorMessage = formatLinkError(languageLinkErrorText, matches)
|
|
||||||
expect(matches.length, errorMessage).toBe(0)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('URLs must not contain a hard-coded version number', async () => {
|
|
||||||
const matches = []
|
|
||||||
|
|
||||||
for (const [key, content] of Object.entries(dictionary)) {
|
|
||||||
const contentStr = getContent(content)
|
|
||||||
if (!contentStr) continue
|
|
||||||
const valMatches = (contentStr.match(versionLinkRegEx) || [])
|
|
||||||
if (valMatches.length > 0) {
|
|
||||||
matches.push(...valMatches.map((match) => `Key "${key}": ${match}`))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const errorMessage = formatLinkError(versionLinkErrorText, matches)
|
|
||||||
expect(matches.length, errorMessage).toBe(0)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('URLs must not contain a hard-coded domain name', async () => {
|
|
||||||
const matches = []
|
|
||||||
|
|
||||||
for (const [key, content] of Object.entries(dictionary)) {
|
|
||||||
const contentStr = getContent(content)
|
|
||||||
if (!contentStr) continue
|
|
||||||
const valMatches = (contentStr.match(domainLinkRegex) || [])
|
|
||||||
if (valMatches.length > 0) {
|
|
||||||
matches.push(...valMatches.map((match) => `Key "${key}": ${match}`))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const errorMessage = formatLinkError(domainLinkErrorText, matches)
|
|
||||||
expect(matches.length, errorMessage).toBe(0)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('must not leak Early Access doc URLs', async () => {
|
test('must not leak Early Access doc URLs', async () => {
|
||||||
// Only execute for docs that are NOT Early Access
|
// Only execute for docs that are NOT Early Access
|
||||||
if (!isEarlyAccess) {
|
if (!isEarlyAccess) {
|
||||||
@@ -543,6 +499,55 @@ describe('lint yaml content', () => {
|
|||||||
expect(matches.length, errorMessage).toBe(0)
|
expect(matches.length, errorMessage).toBe(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (!process.env.TEST_TRANSLATION) {
|
||||||
|
test('URLs must not contain a hard-coded language code', async () => {
|
||||||
|
const matches = []
|
||||||
|
|
||||||
|
for (const [key, content] of Object.entries(dictionary)) {
|
||||||
|
const contentStr = getContent(content)
|
||||||
|
if (!contentStr) continue
|
||||||
|
const valMatches = (contentStr.match(languageLinkRegex) || [])
|
||||||
|
if (valMatches.length > 0) {
|
||||||
|
matches.push(...valMatches.map((match) => `Key "${key}": ${match}`))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const errorMessage = formatLinkError(languageLinkErrorText, matches)
|
||||||
|
expect(matches.length, errorMessage).toBe(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('URLs must not contain a hard-coded version number', async () => {
|
||||||
|
const matches = []
|
||||||
|
|
||||||
|
for (const [key, content] of Object.entries(dictionary)) {
|
||||||
|
const contentStr = getContent(content)
|
||||||
|
if (!contentStr) continue
|
||||||
|
const valMatches = (contentStr.match(versionLinkRegEx) || [])
|
||||||
|
if (valMatches.length > 0) {
|
||||||
|
matches.push(...valMatches.map((match) => `Key "${key}": ${match}`))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const errorMessage = formatLinkError(versionLinkErrorText, matches)
|
||||||
|
expect(matches.length, errorMessage).toBe(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('URLs must not contain a hard-coded domain name', async () => {
|
||||||
|
const matches = []
|
||||||
|
|
||||||
|
for (const [key, content] of Object.entries(dictionary)) {
|
||||||
|
const contentStr = getContent(content)
|
||||||
|
if (!contentStr) continue
|
||||||
|
const valMatches = (contentStr.match(domainLinkRegex) || [])
|
||||||
|
if (valMatches.length > 0) {
|
||||||
|
matches.push(...valMatches.map((match) => `Key "${key}": ${match}`))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const errorMessage = formatLinkError(domainLinkErrorText, matches)
|
||||||
|
expect(matches.length, errorMessage).toBe(0)
|
||||||
|
})
|
||||||
|
|
||||||
test('does not use old site.data variable syntax', async () => {
|
test('does not use old site.data variable syntax', async () => {
|
||||||
const matches = []
|
const matches = []
|
||||||
|
|
||||||
@@ -595,11 +600,12 @@ describe('lint yaml content', () => {
|
|||||||
expect(matches.length, errorMessage).toBe(0)
|
expect(matches.length, errorMessage).toBe(0)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('lint release notes', () => {
|
describe('lint release notes', () => {
|
||||||
if (releaseNotesToLint.length > 0) {
|
if (releaseNotesToLint.length < 1) return
|
||||||
describe.each(releaseNotesToLint)(
|
describe.each(releaseNotesToLint)(
|
||||||
'%s',
|
'%s',
|
||||||
(yamlRelPath, yamlAbsPath) => {
|
(yamlRelPath, yamlAbsPath) => {
|
||||||
@@ -615,7 +621,31 @@ describe('lint release notes', () => {
|
|||||||
const errorMessage = errors.map(error => `- [${error.property}]: ${error.actual}, ${error.message}`).join('\n')
|
const errorMessage = errors.map(error => `- [${error.property}]: ${error.actual}, ${error.message}`).join('\n')
|
||||||
expect(errors.length, errorMessage).toBe(0)
|
expect(errors.length, errorMessage).toBe(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('contains valid liquid', () => {
|
||||||
|
const { intro, sections } = dictionary
|
||||||
|
let toLint = { intro }
|
||||||
|
for (const key in sections) {
|
||||||
|
const section = sections[key]
|
||||||
|
const label = `sections.${key}`
|
||||||
|
section.forEach((part) => {
|
||||||
|
if (Array.isArray(part)) {
|
||||||
|
toLint = { ...toLint, ...{ [label]: section.join('\n') } }
|
||||||
|
} else {
|
||||||
|
for (const prop in section) {
|
||||||
|
toLint = { ...toLint, ...{ [`${label}.${prop}`]: section[prop] } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const key in toLint) {
|
||||||
|
if (!toLint[key]) continue
|
||||||
|
expect(() => renderContent.liquid.parse(toLint[key]), `${key} contains invalid liquid`)
|
||||||
|
.not
|
||||||
|
.toThrow()
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
const chalk = require('chalk')
|
const chalk = require('chalk')
|
||||||
|
const stripAnsi = require('strip-ansi')
|
||||||
const { groupBy } = require('lodash')
|
const { groupBy } = require('lodash')
|
||||||
|
|
||||||
// we don't want to print all the stack traces
|
// we don't want to print all the stack traces
|
||||||
const stackTrackRegExp = /^\s+at\s.+/i
|
const stackTraceRegExp = /^\s+at\s.+/img
|
||||||
|
|
||||||
class TranslationReporter {
|
class TranslationReporter {
|
||||||
constructor (globalConfig, options) {
|
constructor (globalConfig, options) {
|
||||||
@@ -18,7 +19,9 @@ class TranslationReporter {
|
|||||||
return {
|
return {
|
||||||
fileName: ancestorTitles[1],
|
fileName: ancestorTitles[1],
|
||||||
failedTests: title,
|
failedTests: title,
|
||||||
failureMessage: failureMessages.map((message) => message.split('\n').filter(line => !stackTrackRegExp.test(line)).join('\n'))
|
failureMessage: failureMessages.map((message) => {
|
||||||
|
return message.split('\n').filter(line => !stackTraceRegExp.test(stripAnsi(line))).join('\n')
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return [...fails, ...formattedFails]
|
return [...fails, ...formattedFails]
|
||||||
@@ -34,9 +37,6 @@ class TranslationReporter {
|
|||||||
})
|
})
|
||||||
console.groupEnd()
|
console.groupEnd()
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(chalk.bold('\nthese files should not be included: '))
|
|
||||||
console.dir(Object.keys(failuresByFile), { maxArrayLength: null })
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user