зеркало из https://github.com/github/docs.git
235 строки
6.9 KiB
JavaScript
Executable File
235 строки
6.9 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
|
|
import fs from 'fs'
|
|
import path from 'path'
|
|
import program from 'commander'
|
|
import walk from 'walk-sync'
|
|
import { execSync } from 'child_process'
|
|
|
|
import frontmatter from '../lib/read-frontmatter.js'
|
|
|
|
const scriptName = new URL(import.meta.url).pathname.split('/').pop()
|
|
|
|
const contentDir = path.resolve('content')
|
|
const reusablesDir = path.resolve('data/reusables')
|
|
const dataDir = path.resolve('data')
|
|
|
|
const currentBranch = execSync('git branch --show-current').toString().trim()
|
|
const plan = 'ghae'
|
|
|
|
// [start-readme]
|
|
//
|
|
// Find and replace lightweight feature flags for GitHub AE content.
|
|
//
|
|
// [end-readme]
|
|
|
|
program
|
|
.description(
|
|
'Toggle issue-based, feature-flagged versioning for GitHub AE content like\n' +
|
|
'ghae-next or ghae-issue-1234, then commit the results.\n\n' +
|
|
'Documentation: https://git.io/JCtUO\n\n' +
|
|
'Examples:\n' +
|
|
` ${scriptName} -n\n` +
|
|
` ${scriptName} -f 'issue-1234, issue-5678'`
|
|
)
|
|
.option('-n, --toggle-next', "toggle 'next' flags for M2 release")
|
|
.option('-s, --show-flags', 'show list of existing flags')
|
|
.option("-f, --toggle-flags '<flag-1>[,flag-2,...]'", 'toggle comma-separated list of flags')
|
|
.parse(process.argv)
|
|
|
|
const options = program.opts()
|
|
|
|
let optionsCount = 0
|
|
options.toggleNext && optionsCount++
|
|
options.showFlags && optionsCount++
|
|
options.toggleFlags && optionsCount++
|
|
|
|
// Show help with no options; error when more than one.
|
|
|
|
if (optionsCount === 0) {
|
|
program.help()
|
|
} else if (optionsCount > 1) {
|
|
console.log(`Error: you specified ${optionsCount} options (accepts 1)`)
|
|
process.exit(1)
|
|
}
|
|
|
|
// Store flags that user wants to toggle.
|
|
|
|
let flagsToToggle = []
|
|
let flagCount = 0
|
|
if (options.toggleNext) {
|
|
flagsToToggle = ['next']
|
|
} else if (options.toggleFlags) {
|
|
flagsToToggle = options.toggleFlags.split(',').map((item) => item.trim())
|
|
flagCount = flagsToToggle.length
|
|
}
|
|
|
|
if (options.toggleNext || options.toggleFlags) {
|
|
// Refuse to proceed if repository has uncommitted changes.
|
|
|
|
const localChangesCount = execSync(
|
|
`git status ${contentDir} ${reusablesDir} ${dataDir} --porcelain=v1 2>/dev/null | wc -l`
|
|
).toString()
|
|
|
|
if (localChangesCount > 0) {
|
|
console.log("Error: refusing to proceed due to uncommitted changes (review 'git status')")
|
|
process.exit(1)
|
|
}
|
|
}
|
|
|
|
// Gather the files.
|
|
|
|
const markdownFiles = walk(contentDir, { includeBasePath: true, directories: false })
|
|
.concat(walk(reusablesDir, { includeBasePath: true, directories: false }))
|
|
.filter((file) => file.endsWith('.md') && !/readme\.md/i.test(file))
|
|
|
|
const yamlFiles = walk(dataDir, {
|
|
includeBasePath: true,
|
|
directories: false,
|
|
ignore: ['graphql'],
|
|
}).filter((file) => file.endsWith('.yml'))
|
|
|
|
const allFiles = [...markdownFiles, ...yamlFiles]
|
|
|
|
// --------------------------------------------------------------- --show-flags
|
|
|
|
// Create a dictionary to populate with keys as flag names that reference
|
|
// arrays of paths to files that contain the flag in YAML front matter or
|
|
// Liquid conditionals.
|
|
|
|
const allFlags = {}
|
|
|
|
console.log(`Parsing all flags for ${plan} plan on ${currentBranch} branch...`)
|
|
|
|
allFiles.forEach((file) => {
|
|
const fileContent = fs.readFileSync(file, 'utf8')
|
|
const { data } = frontmatter(fileContent)
|
|
|
|
// Process YAML front matter and data files.
|
|
|
|
if (data.versions && data.versions[plan] && data.versions[plan] !== '*') {
|
|
if (!allFlags[data.versions[plan]]) {
|
|
allFlags[data.versions[plan]] = []
|
|
}
|
|
|
|
allFlags[data.versions[plan]].push(file)
|
|
}
|
|
|
|
// Process Liquid conditionals in Markdown source.
|
|
|
|
const liquidShowRegExp = new RegExp(`\\s${plan}-([^\\s]+)`, 'g')
|
|
const deduplicatedMatches = [...new Set(fileContent.match(liquidShowRegExp))]
|
|
|
|
if (deduplicatedMatches.length > 0) {
|
|
const matches = deduplicatedMatches.map((match) => match.trim().replace(plan + '-', ''))
|
|
|
|
matches.forEach((match) => {
|
|
if (!allFlags[match]) {
|
|
allFlags[match] = []
|
|
}
|
|
|
|
allFlags[match].push(file)
|
|
})
|
|
}
|
|
})
|
|
|
|
// Output flags and lists of files that contain the flags.
|
|
|
|
if (options.showFlags) {
|
|
let flag, files
|
|
for ([flag, files] of Object.entries(allFlags)) {
|
|
if (flag === 'next') {
|
|
console.log(
|
|
`\n🚩 \x1b[7m ${plan}-${flag} \x1b[0m \x1b[1m\x1b[34m\x1b[4mhttps://github.com/github/docs-content/issues/3950\x1b[0m`
|
|
)
|
|
} else if (flag.match(/^issue-[0-9]+$/)) {
|
|
console.log(
|
|
`\n🚩 \x1b[7m ${plan}-${flag} \x1b[0m \x1b[1m\x1b[34m\x1b[4m${flag.replace(
|
|
'issue-',
|
|
'https://github.com/github/docs-content/issues/'
|
|
)}\x1b[0m`
|
|
)
|
|
} else {
|
|
console.log(`\n🚩 \x1b[43m ${plan}-${flag} \x1b[0m`)
|
|
}
|
|
|
|
files.forEach((file) => {
|
|
console.log(` ${file}`)
|
|
})
|
|
}
|
|
|
|
// -------------------------------------------- --toggle-flags or --toggle-next
|
|
} else if (options.toggleFlags || options.toggleNext) {
|
|
let commitCount = 0
|
|
|
|
console.log(`Toggling flag${flagCount > 1 ? 's' : ''} (${flagsToToggle.join(', ')})...`)
|
|
|
|
flagsToToggle.forEach((flag) => {
|
|
allFiles.forEach((file) => {
|
|
const fileContent = fs.readFileSync(file, 'utf8')
|
|
const { data } = frontmatter(fileContent)
|
|
|
|
const liquidReplacementRegExp = new RegExp(`${plan}-${flag}`, 'g')
|
|
|
|
// Update versions in Liquid conditionals.
|
|
|
|
const newContent = fileContent.replace(liquidReplacementRegExp, plan)
|
|
|
|
if (data.versions && data.versions[plan] === flag) {
|
|
// Update versions in YAML files and content files with YAML front
|
|
// matter.
|
|
|
|
data.versions[plan] = '*'
|
|
}
|
|
|
|
fs.writeFileSync(file, frontmatter.stringify(newContent, data, { lineWidth: 10000 }))
|
|
})
|
|
|
|
let filesUpdatedForFlag = 0
|
|
|
|
if (!(flag in allFlags)) {
|
|
console.log(`Warning: ${flag} does not exist in source`)
|
|
}
|
|
|
|
if (allFlags[flag]) {
|
|
console.log(`Toggled ${flag}. Committing changes...`)
|
|
allFlags[flag].forEach((fileToAdd) => {
|
|
execSync(`git add ${fileToAdd}`)
|
|
filesUpdatedForFlag++
|
|
})
|
|
|
|
if (filesUpdatedForFlag === allFlags[flag].length) {
|
|
let commitCommand = `git commit -m 'Toggle ${plan}-${flag} flag'`
|
|
|
|
if (flag.match(/^issue-[0-9]+$/)) {
|
|
commitCommand = `${commitCommand} -m 'For ${flag.replace(
|
|
'issue-',
|
|
'github/docs-content#'
|
|
)}'`
|
|
}
|
|
|
|
execSync(commitCommand)
|
|
commitCount++
|
|
}
|
|
}
|
|
|
|
// Check out any file that had syntax adjusted, but didn't contain one
|
|
// or more feature flags to toggle.
|
|
|
|
execSync(`git checkout --quiet ${contentDir} ${reusablesDir} ${dataDir}`)
|
|
})
|
|
|
|
console.log('Done!')
|
|
|
|
if (commitCount > 0) {
|
|
console.log(' - Review commits:')
|
|
console.log(` git log -n ${commitCount}`)
|
|
console.log(' - Review changes in diffs:')
|
|
console.log(` git show -n ${commitCount}`)
|
|
console.log(' - Undo changes:')
|
|
console.log(` git reset HEAD~${commitCount} && git checkout content data`)
|
|
console.log(' - Push changes:')
|
|
console.log(' git push')
|
|
}
|
|
}
|