| | import fs from 'fs' |
| | import walk from 'walk-sync' |
| | import path from 'path' |
| | import { escapeRegExp } from 'lodash-es' |
| | import { Tokenizer } from 'liquidjs' |
| | import frontmatter from '@/frame/lib/read-frontmatter' |
| | import { allVersions } from '@/versions/lib/all-versions' |
| | import { deprecated, oldestSupported } from '@/versions/lib/enterprise-server-releases' |
| |
|
| | const allVersionKeys = Object.values(allVersions) |
| | const dryRun = ['-d', '--dry-run'].includes(process.argv[2]) |
| |
|
| | const walkFiles = (pathToWalk: string, ext: string): string[] => { |
| | return walk(path.posix.join(process.cwd(), pathToWalk), { |
| | includeBasePath: true, |
| | directories: false, |
| | }).filter((file) => file.endsWith(ext) && !file.endsWith('README.md')) |
| | } |
| |
|
| | const markdownFiles = walkFiles('content', '.md').concat(walkFiles('data', '.md')) |
| | const yamlFiles = walkFiles('data', '.yml') |
| |
|
| | interface ReplacementsMap { |
| | [key: string]: string |
| | } |
| |
|
| | interface VersionData { |
| | versions?: Record<string, string> | string |
| | [key: string]: any |
| | } |
| |
|
| | interface OperatorsMap { |
| | [key: string]: string |
| | '==': string |
| | ver_gt: string |
| | ver_lt: string |
| | '!=': string |
| | } |
| |
|
| | const operatorsMap: OperatorsMap = { |
| | |
| | '==': '=', |
| | ver_gt: '>', |
| | ver_lt: '<', |
| | '!=': '!=', |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | async function main() { |
| | if (dryRun) |
| | console.log('This is a dry run! The script will not write any files. Use for debugging.\n') |
| |
|
| | |
| | console.log('Updating Liquid conditionals and versions frontmatter in Markdown files...\n') |
| | for (const file of markdownFiles) { |
| | |
| | |
| | |
| | const content = fs.readFileSync(file, 'utf8') |
| | const contentReplacements = getLiquidReplacements(content, file) |
| | const newContent = makeLiquidReplacements(contentReplacements, content) |
| |
|
| | |
| | const { data } = frontmatter(newContent) as { data: VersionData } |
| | if (data.versions && typeof data.versions !== 'string') { |
| | const versions = data.versions as Record<string, string> |
| | for (const [plan, value] of Object.entries(versions)) { |
| | |
| | const valueToUse = value |
| | .replace('2.23', '3.0') |
| | .replace(`>=${oldestSupported}`, '*') |
| | .replace(/>=?2\.20/, '*') |
| | .replace(/>=?2\.19/, '*') |
| |
|
| | |
| | const versionObj = allVersionKeys.find( |
| | (version) => version.plan === plan || version.shortName === plan, |
| | ) |
| | if (!versionObj) { |
| | console.error(`can't find supported version for ${plan}`) |
| | process.exit(1) |
| | } |
| | delete versions[plan] |
| | versions[versionObj.shortName] = valueToUse |
| | } |
| | } |
| |
|
| | if (dryRun) { |
| | console.log(contentReplacements) |
| | } else { |
| | |
| | fs.writeFileSync(file, frontmatter.stringify(newContent, data, { lineWidth: 10000 } as any)) |
| | } |
| | } |
| |
|
| | |
| | console.log('Updating Liquid conditionals in YAML files...\n') |
| | for (const file of yamlFiles) { |
| | const yamlContent = fs.readFileSync(file, 'utf8') |
| | const yamlReplacements = getLiquidReplacements(yamlContent, file) |
| | |
| | const newYamlContent = makeLiquidReplacements(yamlReplacements, yamlContent) |
| | .replace(/("|')?free-pro-team("|')?:/g, 'fpt:') |
| | .replace(/("|')?enterprise-server("|')?:/g, 'ghes:') |
| |
|
| | if (dryRun) { |
| | console.log(yamlReplacements) |
| | } else { |
| | fs.writeFileSync(file, newYamlContent) |
| | } |
| | } |
| | } |
| |
|
| | try { |
| | await main() |
| | console.log('Done!') |
| | } catch (err) { |
| | console.error(err) |
| | process.exit(1) |
| | } |
| |
|
| | |
| | |
| | function removeInputProps(arrayOfObjects: any[]): any[] { |
| | return arrayOfObjects.map((obj: any) => { |
| | delete obj.input |
| | delete obj.token.input |
| | return obj |
| | }) |
| | } |
| |
|
| | function makeLiquidReplacements(replacementsObj: ReplacementsMap, text: string): string { |
| | let newText = text |
| | for (const [oldCond, newCond] of Object.entries(replacementsObj)) { |
| | const oldCondRegex = new RegExp(`({%-?)\\s*?${escapeRegExp(oldCond)}\\s*?(-?%})`, 'g') |
| | newText = newText |
| | .replace(oldCondRegex, `$1 ${newCond} $2`) |
| | |
| | |
| | |
| | |
| | |
| | .replace(/ghes and ghes/g, 'ghes') |
| | } |
| |
|
| | return newText |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | function getLiquidReplacements(content: string, file: string): ReplacementsMap { |
| | const replacements: ReplacementsMap = {} |
| |
|
| | const tokenizer = new Tokenizer(content) |
| | const tokens = removeInputProps(tokenizer.readTopLevelTokens()) |
| |
|
| | tokens |
| | .filter( |
| | (token) => |
| | (token.name === 'if' || token.name === 'elsif') && token.content.includes('currentVersion'), |
| | ) |
| | .map((token) => token.content) |
| |
|
| | const conditionalTokens = tokens |
| | .filter( |
| | (xtoken) => |
| | (xtoken.name === 'if' || xtoken.name === 'elsif') && |
| | xtoken.content.includes('currentVersion'), |
| | ) |
| | .map((xtoken) => xtoken.content) |
| | for (const token of conditionalTokens) { |
| | const newToken = token.startsWith('if') ? ['ifversion'] : ['elsif'] |
| | |
| | for (const op of token.replace(/(if|elsif) /, '').split(/ (or|and) /)) { |
| | if (op === 'or' || op === 'and') { |
| | newToken.push(op) |
| | continue |
| | } |
| |
|
| | |
| | if (op.includes('enterpriseServerVersions contains currentVersion')) { |
| | newToken.push('ghes') |
| | continue |
| | } |
| |
|
| | |
| |
|
| | |
| | const opParts = op.split(' ') |
| |
|
| | if (!(opParts.length === 3 && opParts[0] === 'currentVersion')) { |
| | console.error(`Something went wrong with ${token} in ${file}`) |
| | process.exit(1) |
| | } |
| |
|
| | const operator = opParts[1] |
| | |
| | const [plan, release] = opParts[2].slice(1, -1).split('@') |
| |
|
| | |
| | const versionObj = allVersionKeys.find((version) => version.plan === plan) |
| |
|
| | if (!versionObj) { |
| | console.error(`Couldn't find a version for ${plan} in "${token}" in ${file}`) |
| | process.exit(1) |
| | } |
| |
|
| | |
| | if (versionObj.hasNumberedReleases) { |
| | const newOperator: string | undefined = operatorsMap[operator] |
| | if (!newOperator) { |
| | console.error( |
| | `Couldn't find an operator that corresponds to ${operator} in "${token} in "${file}`, |
| | ) |
| | process.exit(1) |
| | } |
| |
|
| | |
| | deprecated.push('1.19') |
| |
|
| | |
| | const availableInAllGhes = deprecated.includes(release) && newOperator === '>' |
| |
|
| | |
| | |
| | if (availableInAllGhes) { |
| | newToken.push(versionObj.shortName) |
| | continue |
| | } |
| |
|
| | |
| | const lessThanDeprecated = deprecated.includes(release) && newOperator === '<' |
| | |
| | const lessThanOldestSupported = release === oldestSupported && newOperator === '<' |
| | |
| | const equalsDeprecated = deprecated.includes(release) && newOperator === '=' |
| | const hasDeprecatedContent = |
| | lessThanDeprecated || lessThanOldestSupported || equalsDeprecated |
| |
|
| | |
| | if (hasDeprecatedContent) { |
| | console.error(`Found content that needs to be removed! See "${token} in "${file}`) |
| | process.exit(1) |
| | } |
| |
|
| | |
| | const releaseToUse = release === '2.23' ? '3.0' : release |
| |
|
| | newToken.push(`${versionObj.shortName} ${newOperator} ${releaseToUse}`) |
| | continue |
| | } |
| |
|
| | |
| | if (operator === '!=') { |
| | newToken.push(`not ${versionObj.shortName}`) |
| | continue |
| | } |
| |
|
| | |
| | if (operator !== '==') { |
| | console.error(`Expected == but found ${operator} in "${op}" in ${token}`) |
| | process.exit(1) |
| | } |
| |
|
| | |
| | if (release === 'latest') { |
| | newToken.push(versionObj.shortName) |
| | continue |
| | } |
| |
|
| | |
| | newToken.push(`${versionObj.shortName}-${release}`) |
| | } |
| |
|
| | replacements[token] = newToken.join(' ') |
| | } |
| |
|
| | return replacements |
| | } |
| |
|