Migrate 6 files from JavaScript to TypeScript (#57885)
This commit is contained in:
@@ -1,7 +1,26 @@
|
|||||||
import { describe, expect, test } from 'vitest'
|
import { describe, expect, test } from 'vitest'
|
||||||
|
|
||||||
|
interface TocItem {
|
||||||
|
title: string
|
||||||
|
intro: string
|
||||||
|
fullPath?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SpotlightItem {
|
||||||
|
article: string
|
||||||
|
image: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ProcessedSpotlightItem {
|
||||||
|
article: string
|
||||||
|
title: string
|
||||||
|
description: string
|
||||||
|
url: string
|
||||||
|
image: string
|
||||||
|
}
|
||||||
|
|
||||||
// Mock data to simulate tocItems and spotlight configurations
|
// Mock data to simulate tocItems and spotlight configurations
|
||||||
const mockTocItems = [
|
const mockTocItems: TocItem[] = [
|
||||||
{
|
{
|
||||||
title: 'Test Debug Article',
|
title: 'Test Debug Article',
|
||||||
intro: 'A test article for debugging functionality.',
|
intro: 'A test article for debugging functionality.',
|
||||||
@@ -20,19 +39,22 @@ const mockTocItems = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
// Helper function to simulate the spotlight processing logic from CategoryLanding
|
// Helper function to simulate the spotlight processing logic from CategoryLanding
|
||||||
function processSpotlight(spotlight, tocItems) {
|
function processSpotlight(
|
||||||
const findArticleData = (articlePath) => {
|
spotlight: SpotlightItem[] | undefined,
|
||||||
const cleanPath = articlePath.startsWith('/') ? articlePath.slice(1) : articlePath
|
tocItems: TocItem[],
|
||||||
|
): ProcessedSpotlightItem[] {
|
||||||
|
const findArticleData = (articlePath: string): TocItem | undefined => {
|
||||||
|
const cleanPath: string = articlePath.startsWith('/') ? articlePath.slice(1) : articlePath
|
||||||
return tocItems.find(
|
return tocItems.find(
|
||||||
(item) =>
|
(item: TocItem) =>
|
||||||
item.fullPath?.endsWith(cleanPath) ||
|
item.fullPath?.endsWith(cleanPath) ||
|
||||||
item.fullPath?.includes(cleanPath.split('/').pop() || ''),
|
item.fullPath?.includes(cleanPath.split('/').pop() || ''),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
spotlight?.map((spotlightItem) => {
|
spotlight?.map((spotlightItem: SpotlightItem): ProcessedSpotlightItem => {
|
||||||
const articleData = findArticleData(spotlightItem.article)
|
const articleData: TocItem | undefined = findArticleData(spotlightItem.article)
|
||||||
return {
|
return {
|
||||||
article: spotlightItem.article,
|
article: spotlightItem.article,
|
||||||
title: articleData?.title || 'Unknown Article',
|
title: articleData?.title || 'Unknown Article',
|
||||||
@@ -46,7 +68,7 @@ function processSpotlight(spotlight, tocItems) {
|
|||||||
|
|
||||||
describe('spotlight processing logic', () => {
|
describe('spotlight processing logic', () => {
|
||||||
test('processes spotlight object items correctly', () => {
|
test('processes spotlight object items correctly', () => {
|
||||||
const spotlight = [
|
const spotlight: SpotlightItem[] = [
|
||||||
{
|
{
|
||||||
article: '/debugging-errors/test-debug-article',
|
article: '/debugging-errors/test-debug-article',
|
||||||
image: '/assets/images/test-debugging.png',
|
image: '/assets/images/test-debugging.png',
|
||||||
@@ -57,7 +79,7 @@ describe('spotlight processing logic', () => {
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const result = processSpotlight(spotlight, mockTocItems)
|
const result: ProcessedSpotlightItem[] = processSpotlight(spotlight, mockTocItems)
|
||||||
|
|
||||||
expect(result).toHaveLength(2)
|
expect(result).toHaveLength(2)
|
||||||
expect(result[0]).toEqual({
|
expect(result[0]).toEqual({
|
||||||
@@ -77,7 +99,7 @@ describe('spotlight processing logic', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('processes multiple spotlight items with different images', () => {
|
test('processes multiple spotlight items with different images', () => {
|
||||||
const spotlight = [
|
const spotlight: SpotlightItem[] = [
|
||||||
{
|
{
|
||||||
article: '/debugging-errors/test-debug-article',
|
article: '/debugging-errors/test-debug-article',
|
||||||
image: '/assets/images/debugging.png',
|
image: '/assets/images/debugging.png',
|
||||||
@@ -92,7 +114,7 @@ describe('spotlight processing logic', () => {
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const result = processSpotlight(spotlight, mockTocItems)
|
const result: ProcessedSpotlightItem[] = processSpotlight(spotlight, mockTocItems)
|
||||||
|
|
||||||
expect(result).toHaveLength(3)
|
expect(result).toHaveLength(3)
|
||||||
expect(result[0].image).toBe('/assets/images/debugging.png')
|
expect(result[0].image).toBe('/assets/images/debugging.png')
|
||||||
@@ -102,13 +124,13 @@ describe('spotlight processing logic', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('finds articles by filename when full path does not match', () => {
|
test('finds articles by filename when full path does not match', () => {
|
||||||
const spotlight = [
|
const spotlight: SpotlightItem[] = [
|
||||||
{
|
{
|
||||||
article: 'test-debug-article',
|
article: 'test-debug-article',
|
||||||
image: '/assets/images/debug.png',
|
image: '/assets/images/debug.png',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
const result = processSpotlight(spotlight, mockTocItems)
|
const result: ProcessedSpotlightItem[] = processSpotlight(spotlight, mockTocItems)
|
||||||
|
|
||||||
expect(result[0].title).toBe('Test Debug Article')
|
expect(result[0].title).toBe('Test Debug Article')
|
||||||
expect(result[0].url).toBe('/en/category/debugging-errors/test-debug-article')
|
expect(result[0].url).toBe('/en/category/debugging-errors/test-debug-article')
|
||||||
@@ -116,7 +138,7 @@ describe('spotlight processing logic', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('handles articles not found in tocItems', () => {
|
test('handles articles not found in tocItems', () => {
|
||||||
const spotlight = [
|
const spotlight: SpotlightItem[] = [
|
||||||
{
|
{
|
||||||
article: '/completely/nonexistent/path',
|
article: '/completely/nonexistent/path',
|
||||||
image: '/assets/images/missing1.png',
|
image: '/assets/images/missing1.png',
|
||||||
@@ -127,7 +149,7 @@ describe('spotlight processing logic', () => {
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const result = processSpotlight(spotlight, mockTocItems)
|
const result: ProcessedSpotlightItem[] = processSpotlight(spotlight, mockTocItems)
|
||||||
|
|
||||||
expect(result).toHaveLength(2)
|
expect(result).toHaveLength(2)
|
||||||
expect(result[0]).toEqual({
|
expect(result[0]).toEqual({
|
||||||
@@ -147,13 +169,13 @@ describe('spotlight processing logic', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('handles empty spotlight array', () => {
|
test('handles empty spotlight array', () => {
|
||||||
const spotlight = []
|
const spotlight: SpotlightItem[] = []
|
||||||
const result = processSpotlight(spotlight, mockTocItems)
|
const result: ProcessedSpotlightItem[] = processSpotlight(spotlight, mockTocItems)
|
||||||
expect(result).toEqual([])
|
expect(result).toEqual([])
|
||||||
})
|
})
|
||||||
|
|
||||||
test('handles undefined spotlight', () => {
|
test('handles undefined spotlight', () => {
|
||||||
const result = processSpotlight(undefined, mockTocItems)
|
const result: ProcessedSpotlightItem[] = processSpotlight(undefined, mockTocItems)
|
||||||
expect(result).toEqual([])
|
expect(result).toEqual([])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -19,7 +19,7 @@ export default async function whatsNewChangelog(
|
|||||||
const changelogVersions = getApplicableVersions(req.context.page.changelog.versions)
|
const changelogVersions = getApplicableVersions(req.context.page.changelog.versions)
|
||||||
|
|
||||||
// If the current version is not included, do not display a changelog.
|
// If the current version is not included, do not display a changelog.
|
||||||
if (!changelogVersions.includes(req.context.currentVersion)) {
|
if (!req.context.currentVersion || !changelogVersions.includes(req.context.currentVersion)) {
|
||||||
return next()
|
return next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ export default async function processLearningTracks(
|
|||||||
const trackVersions = getApplicableVersions(track.versions)
|
const trackVersions = getApplicableVersions(track.versions)
|
||||||
|
|
||||||
// If the current version is not included, do not display the track.
|
// If the current version is not included, do not display the track.
|
||||||
if (!trackVersions.includes(context.currentVersion)) {
|
if (!context.currentVersion || !trackVersions.includes(context.currentVersion)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,16 +6,33 @@ import {
|
|||||||
getLocalizedGroupNames,
|
getLocalizedGroupNames,
|
||||||
} from '@/products/lib/get-product-groups'
|
} from '@/products/lib/get-product-groups'
|
||||||
|
|
||||||
|
// Mock data interface for tests - uses required name to match library expectations
|
||||||
|
interface MockProductGroupData {
|
||||||
|
name: string
|
||||||
|
octicon?: string
|
||||||
|
children: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mock data for testing edge cases with optional fields
|
||||||
|
interface PartialProductGroupData {
|
||||||
|
name?: string
|
||||||
|
octicon?: string
|
||||||
|
children: string[]
|
||||||
|
}
|
||||||
|
|
||||||
describe('get-product-groups helper functions', () => {
|
describe('get-product-groups helper functions', () => {
|
||||||
describe('createOcticonToNameMap', () => {
|
describe('createOcticonToNameMap', () => {
|
||||||
test('creates correct mapping from childGroups', () => {
|
test('creates correct mapping from childGroups', () => {
|
||||||
const mockChildGroups = [
|
const mockChildGroups: MockProductGroupData[] = [
|
||||||
{ name: 'Get started', octicon: 'RocketIcon', children: ['get-started'] },
|
{ name: 'Get started', octicon: 'RocketIcon', children: ['get-started'] },
|
||||||
{ name: 'GitHub Copilot', octicon: 'CopilotIcon', children: ['copilot'] },
|
{ name: 'GitHub Copilot', octicon: 'CopilotIcon', children: ['copilot'] },
|
||||||
{ name: 'Security', octicon: 'ShieldLockIcon', children: ['code-security'] },
|
{ name: 'Security', octicon: 'ShieldLockIcon', children: ['code-security'] },
|
||||||
]
|
]
|
||||||
|
|
||||||
const octiconToName = createOcticonToNameMap(mockChildGroups)
|
// Using any to cast mock data structure to match library's expected ProductGroupData type
|
||||||
|
const octiconToName: { [key: string]: string } = createOcticonToNameMap(
|
||||||
|
mockChildGroups as any,
|
||||||
|
)
|
||||||
|
|
||||||
expect(octiconToName['RocketIcon']).toBe('Get started')
|
expect(octiconToName['RocketIcon']).toBe('Get started')
|
||||||
expect(octiconToName['CopilotIcon']).toBe('GitHub Copilot')
|
expect(octiconToName['CopilotIcon']).toBe('GitHub Copilot')
|
||||||
@@ -24,14 +41,17 @@ describe('get-product-groups helper functions', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('handles missing octicon or name gracefully', () => {
|
test('handles missing octicon or name gracefully', () => {
|
||||||
const mockChildGroups = [
|
const mockChildGroups: PartialProductGroupData[] = [
|
||||||
{ name: 'Valid Group', octicon: 'RocketIcon', children: [] },
|
{ name: 'Valid Group', octicon: 'RocketIcon', children: [] },
|
||||||
{ octicon: 'MissingNameIcon', children: [] }, // missing name
|
{ octicon: 'MissingNameIcon', children: [] }, // missing name
|
||||||
{ name: 'Missing Octicon', children: [] }, // missing octicon
|
{ name: 'Missing Octicon', children: [] }, // missing octicon
|
||||||
{ name: '', octicon: 'EmptyNameIcon', children: [] }, // empty name
|
{ name: '', octicon: 'EmptyNameIcon', children: [] }, // empty name
|
||||||
]
|
]
|
||||||
|
|
||||||
const octiconToName = createOcticonToNameMap(mockChildGroups)
|
// Using any to test edge cases with partial/missing fields that wouldn't normally pass strict typing
|
||||||
|
const octiconToName: { [key: string]: string } = createOcticonToNameMap(
|
||||||
|
mockChildGroups as any,
|
||||||
|
)
|
||||||
|
|
||||||
expect(octiconToName['RocketIcon']).toBe('Valid Group')
|
expect(octiconToName['RocketIcon']).toBe('Valid Group')
|
||||||
expect(octiconToName['MissingNameIcon']).toBeUndefined()
|
expect(octiconToName['MissingNameIcon']).toBeUndefined()
|
||||||
@@ -42,19 +62,23 @@ describe('get-product-groups helper functions', () => {
|
|||||||
|
|
||||||
describe('mapEnglishToLocalizedNames', () => {
|
describe('mapEnglishToLocalizedNames', () => {
|
||||||
test('maps English names to localized names using octicon as key', () => {
|
test('maps English names to localized names using octicon as key', () => {
|
||||||
const englishGroups = [
|
const englishGroups: MockProductGroupData[] = [
|
||||||
{ name: 'Get started', octicon: 'RocketIcon', children: [] },
|
{ name: 'Get started', octicon: 'RocketIcon', children: [] },
|
||||||
{ name: 'Security', octicon: 'ShieldLockIcon', children: [] },
|
{ name: 'Security', octicon: 'ShieldLockIcon', children: [] },
|
||||||
{ name: 'GitHub Copilot', octicon: 'CopilotIcon', children: [] },
|
{ name: 'GitHub Copilot', octicon: 'CopilotIcon', children: [] },
|
||||||
]
|
]
|
||||||
|
|
||||||
const localizedByOcticon = {
|
const localizedByOcticon: { [key: string]: string } = {
|
||||||
RocketIcon: 'Empezar',
|
RocketIcon: 'Empezar',
|
||||||
ShieldLockIcon: 'Seguridad',
|
ShieldLockIcon: 'Seguridad',
|
||||||
CopilotIcon: 'GitHub Copilot', // Some names stay the same
|
CopilotIcon: 'GitHub Copilot', // Some names stay the same
|
||||||
}
|
}
|
||||||
|
|
||||||
const nameMap = mapEnglishToLocalizedNames(englishGroups, localizedByOcticon)
|
// Using any to cast mock data structure to match library's expected ProductGroupData type
|
||||||
|
const nameMap: { [key: string]: string } = mapEnglishToLocalizedNames(
|
||||||
|
englishGroups as any,
|
||||||
|
localizedByOcticon,
|
||||||
|
)
|
||||||
|
|
||||||
expect(nameMap['Get started']).toBe('Empezar')
|
expect(nameMap['Get started']).toBe('Empezar')
|
||||||
expect(nameMap['Security']).toBe('Seguridad')
|
expect(nameMap['Security']).toBe('Seguridad')
|
||||||
@@ -63,18 +87,22 @@ describe('get-product-groups helper functions', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('handles missing translations gracefully', () => {
|
test('handles missing translations gracefully', () => {
|
||||||
const englishGroups = [
|
const englishGroups: MockProductGroupData[] = [
|
||||||
{ name: 'Get started', octicon: 'RocketIcon', children: [] },
|
{ name: 'Get started', octicon: 'RocketIcon', children: [] },
|
||||||
{ name: 'Missing Translation', octicon: 'MissingIcon', children: [] },
|
{ name: 'Missing Translation', octicon: 'MissingIcon', children: [] },
|
||||||
{ name: 'No Octicon', children: [] },
|
{ name: 'No Octicon', children: [] },
|
||||||
]
|
]
|
||||||
|
|
||||||
const localizedByOcticon = {
|
const localizedByOcticon: { [key: string]: string } = {
|
||||||
RocketIcon: 'Empezar',
|
RocketIcon: 'Empezar',
|
||||||
// MissingIcon is not in the localized map
|
// MissingIcon is not in the localized map
|
||||||
}
|
}
|
||||||
|
|
||||||
const nameMap = mapEnglishToLocalizedNames(englishGroups, localizedByOcticon)
|
// Using any to cast mock data structure to match library's expected ProductGroupData type
|
||||||
|
const nameMap: { [key: string]: string } = mapEnglishToLocalizedNames(
|
||||||
|
englishGroups as any,
|
||||||
|
localizedByOcticon,
|
||||||
|
)
|
||||||
|
|
||||||
expect(nameMap['Get started']).toBe('Empezar')
|
expect(nameMap['Get started']).toBe('Empezar')
|
||||||
expect(nameMap['Missing Translation']).toBeUndefined()
|
expect(nameMap['Missing Translation']).toBeUndefined()
|
||||||
@@ -84,18 +112,22 @@ describe('get-product-groups helper functions', () => {
|
|||||||
|
|
||||||
test('handles different ordering between English and localized groups', () => {
|
test('handles different ordering between English and localized groups', () => {
|
||||||
// English groups in one order
|
// English groups in one order
|
||||||
const englishGroups = [
|
const englishGroups: MockProductGroupData[] = [
|
||||||
{ name: 'Get started', octicon: 'RocketIcon', children: [] },
|
{ name: 'Get started', octicon: 'RocketIcon', children: [] },
|
||||||
{ name: 'Security', octicon: 'ShieldLockIcon', children: [] },
|
{ name: 'Security', octicon: 'ShieldLockIcon', children: [] },
|
||||||
]
|
]
|
||||||
|
|
||||||
// Localized groups in different order (but mapped by octicon)
|
// Localized groups in different order (but mapped by octicon)
|
||||||
const localizedByOcticon = {
|
const localizedByOcticon: { [key: string]: string } = {
|
||||||
ShieldLockIcon: 'Seguridad', // Security comes first in localized
|
ShieldLockIcon: 'Seguridad', // Security comes first in localized
|
||||||
RocketIcon: 'Empezar', // Get started comes second
|
RocketIcon: 'Empezar', // Get started comes second
|
||||||
}
|
}
|
||||||
|
|
||||||
const nameMap = mapEnglishToLocalizedNames(englishGroups, localizedByOcticon)
|
// Using any to cast mock data structure to match library's expected ProductGroupData type
|
||||||
|
const nameMap: { [key: string]: string } = mapEnglishToLocalizedNames(
|
||||||
|
englishGroups as any,
|
||||||
|
localizedByOcticon,
|
||||||
|
)
|
||||||
|
|
||||||
// Should correctly map regardless of order
|
// Should correctly map regardless of order
|
||||||
expect(nameMap['Get started']).toBe('Empezar')
|
expect(nameMap['Get started']).toBe('Empezar')
|
||||||
@@ -105,17 +137,20 @@ describe('get-product-groups helper functions', () => {
|
|||||||
|
|
||||||
describe('getLocalizedGroupNames integration', () => {
|
describe('getLocalizedGroupNames integration', () => {
|
||||||
test('returns empty object for English language', async () => {
|
test('returns empty object for English language', async () => {
|
||||||
const result = await getLocalizedGroupNames('en')
|
const result: { [key: string]: string } = await getLocalizedGroupNames('en')
|
||||||
expect(result).toEqual({})
|
expect(result).toEqual({})
|
||||||
})
|
})
|
||||||
|
|
||||||
test('returns empty object when no translation root available', () => {
|
test('returns empty object when no translation root available', () => {
|
||||||
// Test the fallback when translation root is not found
|
// Test the fallback when translation root is not found
|
||||||
const lang = 'unknown-lang'
|
const lang = 'unknown-lang'
|
||||||
const languages = { en: { dir: '/en' }, es: { dir: '/es' } }
|
const languages: { [key: string]: { dir: string } } = {
|
||||||
|
en: { dir: '/en' },
|
||||||
|
es: { dir: '/es' },
|
||||||
|
}
|
||||||
|
|
||||||
const translationRoot = languages[lang]?.dir
|
const translationRoot: string | undefined = languages[lang]?.dir
|
||||||
const result = translationRoot
|
const result: { [key: string]: string } = translationRoot
|
||||||
? {
|
? {
|
||||||
/* would proceed */
|
/* would proceed */
|
||||||
}
|
}
|
||||||
@@ -126,7 +161,7 @@ describe('get-product-groups helper functions', () => {
|
|||||||
|
|
||||||
test('handles file read errors gracefully', () => {
|
test('handles file read errors gracefully', () => {
|
||||||
// Test the try/catch behavior when file read fails
|
// Test the try/catch behavior when file read fails
|
||||||
let result
|
let result: { [key: string]: string }
|
||||||
try {
|
try {
|
||||||
// Simulate file read error
|
// Simulate file read error
|
||||||
throw new Error('File not found')
|
throw new Error('File not found')
|
||||||
@@ -141,28 +176,35 @@ describe('get-product-groups helper functions', () => {
|
|||||||
describe('full translation pipeline', () => {
|
describe('full translation pipeline', () => {
|
||||||
test('complete flow from English groups to localized names', () => {
|
test('complete flow from English groups to localized names', () => {
|
||||||
// Simulate the complete flow
|
// Simulate the complete flow
|
||||||
const englishChildGroups = [
|
const englishChildGroups: MockProductGroupData[] = [
|
||||||
{ name: 'Get started', octicon: 'RocketIcon', children: ['get-started'] },
|
{ name: 'Get started', octicon: 'RocketIcon', children: ['get-started'] },
|
||||||
{ name: 'Security', octicon: 'ShieldLockIcon', children: ['code-security'] },
|
{ name: 'Security', octicon: 'ShieldLockIcon', children: ['code-security'] },
|
||||||
{ name: 'GitHub Copilot', octicon: 'CopilotIcon', children: ['copilot'] },
|
{ name: 'GitHub Copilot', octicon: 'CopilotIcon', children: ['copilot'] },
|
||||||
]
|
]
|
||||||
|
|
||||||
// Simulate what would come from a Spanish localized file
|
// Simulate what would come from a Spanish localized file
|
||||||
const mockLocalizedChildGroups = [
|
const mockLocalizedChildGroups: MockProductGroupData[] = [
|
||||||
{ name: 'Empezar', octicon: 'RocketIcon', children: ['get-started'] },
|
{ name: 'Empezar', octicon: 'RocketIcon', children: ['get-started'] },
|
||||||
{ name: 'Seguridad', octicon: 'ShieldLockIcon', children: ['code-security'] },
|
{ name: 'Seguridad', octicon: 'ShieldLockIcon', children: ['code-security'] },
|
||||||
{ name: 'GitHub Copilot', octicon: 'CopilotIcon', children: ['copilot'] },
|
{ name: 'GitHub Copilot', octicon: 'CopilotIcon', children: ['copilot'] },
|
||||||
]
|
]
|
||||||
|
|
||||||
// Step 1: Create octicon -> localized name mapping
|
// Step 1: Create octicon -> localized name mapping
|
||||||
const localizedByOcticon = createOcticonToNameMap(mockLocalizedChildGroups)
|
// Using any to cast mock data structure to match library's expected ProductGroupData type
|
||||||
|
const localizedByOcticon: { [key: string]: string } = createOcticonToNameMap(
|
||||||
|
mockLocalizedChildGroups as any,
|
||||||
|
)
|
||||||
|
|
||||||
// Step 2: Map English names to localized names
|
// Step 2: Map English names to localized names
|
||||||
const localizedNames = mapEnglishToLocalizedNames(englishChildGroups, localizedByOcticon)
|
// Using any to cast mock data structure to match library's expected ProductGroupData type
|
||||||
|
const localizedNames: { [key: string]: string } = mapEnglishToLocalizedNames(
|
||||||
|
englishChildGroups as any,
|
||||||
|
localizedByOcticon,
|
||||||
|
)
|
||||||
|
|
||||||
// Step 3: Use in final mapping
|
// Step 3: Use in final mapping
|
||||||
const finalResult = englishChildGroups.map((group) => {
|
const finalResult = englishChildGroups.map((group: MockProductGroupData) => {
|
||||||
const localizedName = localizedNames[group.name] || group.name
|
const localizedName: string = localizedNames[group.name] || group.name
|
||||||
return {
|
return {
|
||||||
name: localizedName,
|
name: localizedName,
|
||||||
octicon: group.octicon,
|
octicon: group.octicon,
|
||||||
@@ -31,7 +31,7 @@ export default async function secretScanning(
|
|||||||
const { currentVersion } = req.context
|
const { currentVersion } = req.context
|
||||||
|
|
||||||
req.context.secretScanningData = secretScanningData.filter((entry) =>
|
req.context.secretScanningData = secretScanningData.filter((entry) =>
|
||||||
getApplicableVersions(entry.versions).includes(currentVersion),
|
currentVersion ? getApplicableVersions(entry.versions).includes(currentVersion) : false,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Some entries might use Liquid syntax, so we need
|
// Some entries might use Liquid syntax, so we need
|
||||||
|
|||||||
@@ -5,7 +5,20 @@ const releasePattern = '[a-z0-9-.]+'
|
|||||||
const delimiter = '@'
|
const delimiter = '@'
|
||||||
const versionPattern = `${planPattern}${delimiter}${releasePattern}`
|
const versionPattern = `${planPattern}${delimiter}${releasePattern}`
|
||||||
|
|
||||||
export default {
|
interface VersionSchema {
|
||||||
|
type: string
|
||||||
|
additionalProperties: boolean
|
||||||
|
required: string[]
|
||||||
|
properties: {
|
||||||
|
[key: string]: {
|
||||||
|
description: string
|
||||||
|
type: string
|
||||||
|
pattern?: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const schema: VersionSchema = {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
additionalProperties: false,
|
additionalProperties: false,
|
||||||
required: [
|
required: [
|
||||||
@@ -106,3 +119,5 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default schema
|
||||||
@@ -3,7 +3,26 @@ import semver from 'semver'
|
|||||||
|
|
||||||
import versionSatisfiesRange from './version-satisfies-range'
|
import versionSatisfiesRange from './version-satisfies-range'
|
||||||
|
|
||||||
const rawDates = JSON.parse(fs.readFileSync('src/ghes-releases/lib/enterprise-dates.json', 'utf8'))
|
interface VersionDateData {
|
||||||
|
releaseDate: string
|
||||||
|
releaseCandidateDate?: string
|
||||||
|
generalAvailabilityDate?: string
|
||||||
|
deprecationDate: string
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EnhancedVersionDateData extends VersionDateData {
|
||||||
|
displayCandidateDate?: string | null
|
||||||
|
displayReleaseDate?: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RawDatesData {
|
||||||
|
[version: string]: VersionDateData
|
||||||
|
}
|
||||||
|
|
||||||
|
const rawDates: RawDatesData = JSON.parse(
|
||||||
|
fs.readFileSync('src/ghes-releases/lib/enterprise-dates.json', 'utf8'),
|
||||||
|
)
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// STATICALLY DEFINED VALUES
|
// STATICALLY DEFINED VALUES
|
||||||
@@ -91,7 +110,7 @@ export const latestStable = releaseCandidate ? supported[1] : latest
|
|||||||
export const oldestSupported = supported[supported.length - 1]
|
export const oldestSupported = supported[supported.length - 1]
|
||||||
|
|
||||||
// Enhanced dates object with computed display values for templates
|
// Enhanced dates object with computed display values for templates
|
||||||
export const dates = Object.fromEntries(
|
export const dates: Record<string, EnhancedVersionDateData> = Object.fromEntries(
|
||||||
Object.entries(rawDates).map(([version, versionData]) => [
|
Object.entries(rawDates).map(([version, versionData]) => [
|
||||||
version,
|
version,
|
||||||
{
|
{
|
||||||
@@ -100,11 +119,13 @@ export const dates = Object.fromEntries(
|
|||||||
displayReleaseDate: processDateForDisplay(versionData.generalAvailabilityDate),
|
displayReleaseDate: processDateForDisplay(versionData.generalAvailabilityDate),
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
)
|
) as Record<string, EnhancedVersionDateData>
|
||||||
|
|
||||||
// Deprecation tracking
|
// Deprecation tracking
|
||||||
export const nextDeprecationDate = dates[oldestSupported].deprecationDate
|
export const nextDeprecationDate = dates[oldestSupported].deprecationDate
|
||||||
export const isOldestReleaseDeprecated = new Date() > new Date(nextDeprecationDate)
|
export const isOldestReleaseDeprecated = nextDeprecationDate
|
||||||
|
? new Date() > new Date(nextDeprecationDate)
|
||||||
|
: false
|
||||||
|
|
||||||
// Filtered version arrays for different use cases
|
// Filtered version arrays for different use cases
|
||||||
export const deprecatedOnNewSite = deprecated.filter((version) =>
|
export const deprecatedOnNewSite = deprecated.filter((version) =>
|
||||||
@@ -133,7 +154,7 @@ export const deprecatedReleasesOnDeveloperSite = deprecated.filter((version) =>
|
|||||||
* @param {string|null} date - ISO date string
|
* @param {string|null} date - ISO date string
|
||||||
* @returns {string|null} - Date string if in the past, null if future or invalid
|
* @returns {string|null} - Date string if in the past, null if future or invalid
|
||||||
*/
|
*/
|
||||||
function processDateForDisplay(date) {
|
function processDateForDisplay(date: string | undefined): string | null {
|
||||||
if (!date) return null
|
if (!date) return null
|
||||||
const currentTimestamp = Math.floor(Date.now() / 1000)
|
const currentTimestamp = Math.floor(Date.now() / 1000)
|
||||||
const dateTimestamp = Math.floor(new Date(date).getTime() / 1000)
|
const dateTimestamp = Math.floor(new Date(date).getTime() / 1000)
|
||||||
@@ -146,24 +167,24 @@ function processDateForDisplay(date) {
|
|||||||
* @param {string} v2 - Next version
|
* @param {string} v2 - Next version
|
||||||
* @throws {Error} If version sequence is invalid
|
* @throws {Error} If version sequence is invalid
|
||||||
*/
|
*/
|
||||||
function isValidNext(v1, v2) {
|
function isValidNext(v1: string, v2: string): void {
|
||||||
const semverV1 = semver.coerce(v1).raw
|
const semverV1 = semver.coerce(v1)!.raw
|
||||||
const semverV2 = semver.coerce(v2).raw
|
const semverV2 = semver.coerce(v2)!.raw
|
||||||
const isValid =
|
const isValid =
|
||||||
semverV2 === semver.inc(semverV1, 'minor') || semverV2 === semver.inc(semverV1, 'major')
|
semverV2 === semver.inc(semverV1, 'minor') || semverV2 === semver.inc(semverV1, 'major')
|
||||||
if (!isValid)
|
if (!isValid)
|
||||||
throw new Error(`The version "${v2}" is not one version ahead of "${v1}" as expected`)
|
throw new Error(`The version "${v2}" is not one version ahead of "${v1}" as expected`)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const findReleaseNumberIndex = (releaseNum) => {
|
export const findReleaseNumberIndex = (releaseNum: string): number => {
|
||||||
return all.findIndex((i) => i === releaseNum)
|
return all.findIndex((i) => i === releaseNum)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getNextReleaseNumber = (releaseNum) => {
|
export const getNextReleaseNumber = (releaseNum: string): string => {
|
||||||
return all[findReleaseNumberIndex(releaseNum) - 1]
|
return all[findReleaseNumberIndex(releaseNum) - 1]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getPreviousReleaseNumber = (releaseNum) => {
|
export const getPreviousReleaseNumber = (releaseNum: string): string => {
|
||||||
return all[findReleaseNumberIndex(releaseNum) + 1]
|
return all[findReleaseNumberIndex(releaseNum) + 1]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,6 +201,7 @@ export default {
|
|||||||
nextNext,
|
nextNext,
|
||||||
supported,
|
supported,
|
||||||
deprecated,
|
deprecated,
|
||||||
|
deprecatedWithFunctionalRedirects,
|
||||||
legacyAssetVersions,
|
legacyAssetVersions,
|
||||||
all,
|
all,
|
||||||
latest,
|
latest,
|
||||||
@@ -193,11 +215,13 @@ export default {
|
|||||||
firstVersionDeprecatedOnNewSite,
|
firstVersionDeprecatedOnNewSite,
|
||||||
lastVersionWithoutArchivedRedirectsFile,
|
lastVersionWithoutArchivedRedirectsFile,
|
||||||
lastReleaseWithLegacyFormat,
|
lastReleaseWithLegacyFormat,
|
||||||
|
firstReleaseStoredInBlobStorage,
|
||||||
deprecatedReleasesWithLegacyFormat,
|
deprecatedReleasesWithLegacyFormat,
|
||||||
deprecatedReleasesWithNewFormat,
|
deprecatedReleasesWithNewFormat,
|
||||||
deprecatedReleasesOnDeveloperSite,
|
deprecatedReleasesOnDeveloperSite,
|
||||||
firstReleaseNote,
|
firstReleaseNote,
|
||||||
firstRestoredAdminGuides,
|
firstRestoredAdminGuides,
|
||||||
|
findReleaseNumberIndex,
|
||||||
getNextReleaseNumber,
|
getNextReleaseNumber,
|
||||||
getPreviousReleaseNumber,
|
getPreviousReleaseNumber,
|
||||||
}
|
}
|
||||||
@@ -3,21 +3,36 @@ import { allVersions } from './all-versions'
|
|||||||
import versionSatisfiesRange from './version-satisfies-range'
|
import versionSatisfiesRange from './version-satisfies-range'
|
||||||
import { next, nextNext } from './enterprise-server-releases'
|
import { next, nextNext } from './enterprise-server-releases'
|
||||||
import { getDeepDataByLanguage } from '@/data-directory/lib/get-data'
|
import { getDeepDataByLanguage } from '@/data-directory/lib/get-data'
|
||||||
|
import type { Version } from '@/types'
|
||||||
|
|
||||||
let featureData = null
|
interface VersionsObject {
|
||||||
|
[key: string]: string | string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GetApplicableVersionsOptions {
|
||||||
|
doNotThrow?: boolean
|
||||||
|
includeNextVersion?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
// Using any for feature data as it's dynamically loaded from YAML files
|
||||||
|
let featureData: any = null
|
||||||
|
|
||||||
const allVersionKeys = Object.keys(allVersions)
|
const allVersionKeys = Object.keys(allVersions)
|
||||||
|
|
||||||
// return an array of versions that an article's product versions encompasses
|
// return an array of versions that an article's product versions encompasses
|
||||||
function getApplicableVersions(versionsObj, filepath, opts = {}) {
|
function getApplicableVersions(
|
||||||
|
versionsObj: VersionsObject | string | undefined,
|
||||||
|
filepath?: string,
|
||||||
|
opts: GetApplicableVersionsOptions = {},
|
||||||
|
): string[] {
|
||||||
if (typeof versionsObj === 'undefined') {
|
if (typeof versionsObj === 'undefined') {
|
||||||
throw new Error(`No \`versions\` frontmatter found in ${filepath}`)
|
throw new Error(`No \`versions\` frontmatter found in ${filepath || 'undefined'}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Catch an old frontmatter value that was used to indicate an article was available in all versions.
|
// Catch an old frontmatter value that was used to indicate an article was available in all versions.
|
||||||
if (versionsObj === '*') {
|
if (versionsObj === '*') {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`${filepath} contains the invalid versions frontmatter: *. Please explicitly list out all the versions that apply to this article.`,
|
`${filepath || 'undefined'} contains the invalid versions frontmatter: *. Please explicitly list out all the versions that apply to this article.`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,9 +50,12 @@ function getApplicableVersions(versionsObj, filepath, opts = {}) {
|
|||||||
// fpt: '*'
|
// fpt: '*'
|
||||||
// ghes: '>=2.23'
|
// ghes: '>=2.23'
|
||||||
// where the feature is bringing the ghes versions into the mix.
|
// where the feature is bringing the ghes versions into the mix.
|
||||||
const featureVersionsObj = reduce(
|
const featureVersionsObj: VersionsObject =
|
||||||
|
typeof versionsObj === 'string'
|
||||||
|
? {}
|
||||||
|
: reduce(
|
||||||
versionsObj,
|
versionsObj,
|
||||||
(result, value, key) => {
|
(result: any, value, key) => {
|
||||||
if (key === 'feature') {
|
if (key === 'feature') {
|
||||||
if (typeof value === 'string') {
|
if (typeof value === 'string') {
|
||||||
Object.assign(result, { ...featureData[value]?.versions })
|
Object.assign(result, { ...featureData[value]?.versions })
|
||||||
@@ -55,14 +73,16 @@ function getApplicableVersions(versionsObj, filepath, opts = {}) {
|
|||||||
|
|
||||||
// Get available versions for feature and standard versions.
|
// Get available versions for feature and standard versions.
|
||||||
const foundFeatureVersions = evaluateVersions(featureVersionsObj)
|
const foundFeatureVersions = evaluateVersions(featureVersionsObj)
|
||||||
const foundStandardVersions = evaluateVersions(versionsObj)
|
const foundStandardVersions = typeof versionsObj === 'string' ? [] : evaluateVersions(versionsObj)
|
||||||
|
|
||||||
// Combine them!
|
// Combine them!
|
||||||
const applicableVersions = Array.from(new Set(foundStandardVersions.concat(foundFeatureVersions)))
|
const applicableVersions: string[] = Array.from(
|
||||||
|
new Set(foundStandardVersions.concat(foundFeatureVersions)),
|
||||||
|
)
|
||||||
|
|
||||||
if (!applicableVersions.length && !opts.doNotThrow) {
|
if (!applicableVersions.length && !opts.doNotThrow) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`${filepath} is not available in any currently supported version. Make sure the \`versions\` property includes at least one supported version.`,
|
`${filepath || 'undefined'} is not available in any currently supported version. Make sure the \`versions\` property includes at least one supported version.`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,35 +94,38 @@ function getApplicableVersions(versionsObj, filepath, opts = {}) {
|
|||||||
// Strip out not-yet-supported versions if the option to include them is not provided.
|
// Strip out not-yet-supported versions if the option to include them is not provided.
|
||||||
if (!opts.includeNextVersion) {
|
if (!opts.includeNextVersion) {
|
||||||
sortedVersions = sortedVersions.filter(
|
sortedVersions = sortedVersions.filter(
|
||||||
(v) => !(v.endsWith(`@${next}`) || v.endsWith(`@${nextNext}`)),
|
(v: string) => !(v.endsWith(`@${next}`) || v.endsWith(`@${nextNext}`)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return sortedVersions
|
return sortedVersions
|
||||||
}
|
}
|
||||||
|
|
||||||
function evaluateVersions(versionsObj) {
|
function evaluateVersions(versionsObj: VersionsObject): string[] {
|
||||||
// get an array like: [ 'free-pro-team@latest', 'enterprise-server@2.21', 'enterprise-cloud@latest' ]
|
// get an array like: [ 'free-pro-team@latest', 'enterprise-server@2.21', 'enterprise-cloud@latest' ]
|
||||||
const versions = []
|
const versions: string[] = []
|
||||||
|
|
||||||
// where versions obj is something like:
|
// where versions obj is something like:
|
||||||
// fpt: '*'
|
// fpt: '*'
|
||||||
// ghes: '>=2.19'
|
// ghes: '>=2.19'
|
||||||
// ghec: '*'
|
// ghec: '*'
|
||||||
// ^ where each key corresponds to a plan's short name (defined in lib/all-versions.js)
|
// ^ where each key corresponds to a plan's short name (defined in lib/all-versions.js)
|
||||||
Object.entries(versionsObj).forEach(([plan, planValue]) => {
|
Object.entries(versionsObj).forEach(([plan, planValue]: [string, string | string[]]) => {
|
||||||
|
// Skip non-string plan values for semantic comparison
|
||||||
|
if (typeof planValue !== 'string') return
|
||||||
|
|
||||||
// For each available plan (e.g., `ghes`), get the matching versions from allVersions.
|
// For each available plan (e.g., `ghes`), get the matching versions from allVersions.
|
||||||
// This will be an array of one or more version objects.
|
// This will be an array of one or more version objects.
|
||||||
const matchingVersionObjs = Object.values(allVersions).filter(
|
const matchingVersionObjs: Version[] = Object.values(allVersions).filter(
|
||||||
(relevantVersionObj) =>
|
(relevantVersionObj: Version) =>
|
||||||
relevantVersionObj.plan === plan || relevantVersionObj.shortName === plan,
|
relevantVersionObj.plan === plan || relevantVersionObj.shortName === plan,
|
||||||
)
|
)
|
||||||
|
|
||||||
// For each matching version found above, compare it to the provided planValue.
|
// For each matching version found above, compare it to the provided planValue.
|
||||||
// E.g., compare `enterprise-server@2.19` to `ghes: >=2.19`.
|
// E.g., compare `enterprise-server@2.19` to `ghes: >=2.19`.
|
||||||
matchingVersionObjs.forEach((relevantVersionObj) => {
|
matchingVersionObjs.forEach((relevantVersionObj: Version) => {
|
||||||
// If the version doesn't require any semantic comparison, we can assume it applies.
|
// If the version doesn't require any semantic comparison, we can assume it applies.
|
||||||
if (!(relevantVersionObj.hasNumberedReleases || relevantVersionObj.internalLatestRelease)) {
|
if (!relevantVersionObj.hasNumberedReleases) {
|
||||||
versions.push(relevantVersionObj.version)
|
versions.push(relevantVersionObj.version)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -117,11 +140,9 @@ function evaluateVersions(versionsObj) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Determine which release to use for semantic comparison.
|
// Determine which release to use for semantic comparison.
|
||||||
const releaseToCompare = relevantVersionObj.hasNumberedReleases
|
const releaseToCompare: string = relevantVersionObj.currentRelease
|
||||||
? relevantVersionObj.currentRelease
|
|
||||||
: relevantVersionObj.internalLatestRelease
|
|
||||||
|
|
||||||
if (versionSatisfiesRange(releaseToCompare, planValue)) {
|
if (releaseToCompare && versionSatisfiesRange(releaseToCompare, planValue)) {
|
||||||
versions.push(relevantVersionObj.version)
|
versions.push(relevantVersionObj.version)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -10,7 +10,7 @@ import { deprecated, oldestSupported } from '@/versions/lib/enterprise-server-re
|
|||||||
const allVersionKeys = Object.values(allVersions)
|
const allVersionKeys = Object.values(allVersions)
|
||||||
const dryRun = ['-d', '--dry-run'].includes(process.argv[2])
|
const dryRun = ['-d', '--dry-run'].includes(process.argv[2])
|
||||||
|
|
||||||
const walkFiles = (pathToWalk, ext) => {
|
const walkFiles = (pathToWalk: string, ext: string): string[] => {
|
||||||
return walk(path.posix.join(process.cwd(), pathToWalk), {
|
return walk(path.posix.join(process.cwd(), pathToWalk), {
|
||||||
includeBasePath: true,
|
includeBasePath: true,
|
||||||
directories: false,
|
directories: false,
|
||||||
@@ -20,7 +20,24 @@ const walkFiles = (pathToWalk, ext) => {
|
|||||||
const markdownFiles = walkFiles('content', '.md').concat(walkFiles('data', '.md'))
|
const markdownFiles = walkFiles('content', '.md').concat(walkFiles('data', '.md'))
|
||||||
const yamlFiles = walkFiles('data', '.yml')
|
const yamlFiles = walkFiles('data', '.yml')
|
||||||
|
|
||||||
const operatorsMap = {
|
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 = {
|
||||||
// old: new
|
// old: new
|
||||||
'==': '=',
|
'==': '=',
|
||||||
ver_gt: '>',
|
ver_gt: '>',
|
||||||
@@ -50,9 +67,10 @@ async function main() {
|
|||||||
const newContent = makeLiquidReplacements(contentReplacements, content)
|
const newContent = makeLiquidReplacements(contentReplacements, content)
|
||||||
|
|
||||||
// B. UPDATE FRONTMATTER VERSIONS PROPERTY
|
// B. UPDATE FRONTMATTER VERSIONS PROPERTY
|
||||||
const { data } = frontmatter(newContent)
|
const { data } = frontmatter(newContent) as { data: VersionData }
|
||||||
if (data.versions && typeof data.versions !== 'string') {
|
if (data.versions && typeof data.versions !== 'string') {
|
||||||
Object.entries(data.versions).forEach(([plan, value]) => {
|
const versions = data.versions as Record<string, string>
|
||||||
|
Object.entries(versions).forEach(([plan, value]) => {
|
||||||
// Update legacy versioning while we're here
|
// Update legacy versioning while we're here
|
||||||
const valueToUse = value
|
const valueToUse = value
|
||||||
.replace('2.23', '3.0')
|
.replace('2.23', '3.0')
|
||||||
@@ -68,15 +86,16 @@ async function main() {
|
|||||||
console.error(`can't find supported version for ${plan}`)
|
console.error(`can't find supported version for ${plan}`)
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
}
|
}
|
||||||
delete data.versions[plan]
|
delete versions[plan]
|
||||||
data.versions[versionObj.shortName] = valueToUse
|
versions[versionObj.shortName] = valueToUse
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dryRun) {
|
if (dryRun) {
|
||||||
console.log(contentReplacements)
|
console.log(contentReplacements)
|
||||||
} else {
|
} else {
|
||||||
fs.writeFileSync(file, frontmatter.stringify(newContent, data, { lineWidth: 10000 }))
|
// Using any for frontmatter.stringify options as gray-matter types don't include lineWidth
|
||||||
|
fs.writeFileSync(file, frontmatter.stringify(newContent, data, { lineWidth: 10000 } as any))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,14 +128,15 @@ main().then(
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Convenience function to help with readability by removing this large but unneded property.
|
// Convenience function to help with readability by removing this large but unneded property.
|
||||||
function removeInputProps(arrayOfObjects) {
|
// Using any for token objects as liquidjs doesn't provide TypeScript types
|
||||||
return arrayOfObjects.map((obj) => {
|
function removeInputProps(arrayOfObjects: any[]): any[] {
|
||||||
|
return arrayOfObjects.map((obj: any) => {
|
||||||
delete obj.input || delete obj.token.input
|
delete obj.input || delete obj.token.input
|
||||||
return obj
|
return obj
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeLiquidReplacements(replacementsObj, text) {
|
function makeLiquidReplacements(replacementsObj: ReplacementsMap, text: string): string {
|
||||||
let newText = text
|
let newText = text
|
||||||
Object.entries(replacementsObj).forEach(([oldCond, newCond]) => {
|
Object.entries(replacementsObj).forEach(([oldCond, newCond]) => {
|
||||||
const oldCondRegex = new RegExp(`({%-?)\\s*?${escapeRegExp(oldCond)}\\s*?(-?%})`, 'g')
|
const oldCondRegex = new RegExp(`({%-?)\\s*?${escapeRegExp(oldCond)}\\s*?(-?%})`, 'g')
|
||||||
@@ -139,8 +159,8 @@ function makeLiquidReplacements(replacementsObj, text) {
|
|||||||
// if currentVersion ver_gt "myVersion@myRelease -> ifversion myVersionShort > myRelease
|
// if currentVersion ver_gt "myVersion@myRelease -> ifversion myVersionShort > myRelease
|
||||||
// if currentVersion ver_lt "myVersion@myRelease -> ifversion myVersionShort < myRelease
|
// if currentVersion ver_lt "myVersion@myRelease -> ifversion myVersionShort < myRelease
|
||||||
// if enterpriseServerVersions contains currentVersion -> ifversion ghes
|
// if enterpriseServerVersions contains currentVersion -> ifversion ghes
|
||||||
function getLiquidReplacements(content, file) {
|
function getLiquidReplacements(content: string, file: string): ReplacementsMap {
|
||||||
const replacements = {}
|
const replacements: ReplacementsMap = {}
|
||||||
|
|
||||||
const tokenizer = new Tokenizer(content)
|
const tokenizer = new Tokenizer(content)
|
||||||
const tokens = removeInputProps(tokenizer.readTopLevelTokens())
|
const tokens = removeInputProps(tokenizer.readTopLevelTokens())
|
||||||
@@ -157,7 +177,7 @@ function getLiquidReplacements(content, file) {
|
|||||||
token
|
token
|
||||||
.replace(/(if|elsif) /, '')
|
.replace(/(if|elsif) /, '')
|
||||||
.split(/ (or|and) /)
|
.split(/ (or|and) /)
|
||||||
.forEach((op) => {
|
.forEach((op: any) => {
|
||||||
if (op === 'or' || op === 'and') {
|
if (op === 'or' || op === 'and') {
|
||||||
newToken.push(op)
|
newToken.push(op)
|
||||||
return
|
return
|
||||||
@@ -193,7 +213,7 @@ function getLiquidReplacements(content, file) {
|
|||||||
|
|
||||||
// Handle numbered releases!
|
// Handle numbered releases!
|
||||||
if (versionObj.hasNumberedReleases) {
|
if (versionObj.hasNumberedReleases) {
|
||||||
const newOperator = operatorsMap[operator]
|
const newOperator: string | undefined = operatorsMap[operator]
|
||||||
if (!newOperator) {
|
if (!newOperator) {
|
||||||
console.error(
|
console.error(
|
||||||
`Couldn't find an operator that corresponds to ${operator} in "${token} in "${file}`,
|
`Couldn't find an operator that corresponds to ${operator} in "${token} in "${file}`,
|
||||||
Reference in New Issue
Block a user