1
0
mirror of synced 2025-12-19 18:10:59 -05:00

Convert additional data-directory files to TypeScript (#56396)

This commit is contained in:
Kevin Heis
2025-07-09 11:04:14 -07:00
committed by GitHub
parent e5e13f8954
commit 8872a88693
9 changed files with 53 additions and 30 deletions

View File

@@ -1,7 +1,7 @@
import yaml from 'js-yaml' import yaml from 'js-yaml'
import fs from 'fs/promises' import fs from 'fs/promises'
import dataSchemas from '#src/data-directory/lib/data-schemas/index.js' import dataSchemas from '#src/data-directory/lib/data-schemas/index.ts'
import ajv from '#src/tests/lib/validate-json-schema.js' import ajv from '#src/tests/lib/validate-json-schema.js'
// AJV already has a built-in way to extract out properties // AJV already has a built-in way to extract out properties

View File

@@ -1,4 +0,0 @@
import { FeatureData } from '@/types.js'
declare function dataDirectory(dir: string, opts?: Object): FeatureData
export default dataDirectory

View File

@@ -4,45 +4,65 @@ import path from 'path'
import walk from 'walk-sync' import walk from 'walk-sync'
import yaml from 'js-yaml' import yaml from 'js-yaml'
import { isRegExp, setWith } from 'lodash-es' import { isRegExp, setWith } from 'lodash-es'
import filenameToKey from './filename-to-key.js' import filenameToKey from './filename-to-key'
import matter from 'gray-matter' import matter from 'gray-matter'
export default function dataDirectory(dir, opts = {}) { interface DataDirectoryOptions {
const defaultOpts = { preprocess?: (content: string) => string
preprocess: (content) => { ignorePatterns?: RegExp[]
extensions?: string[]
}
interface DataDirectoryResult {
[key: string]: any
}
export default function dataDirectory(
dir: string,
opts: DataDirectoryOptions = {},
): DataDirectoryResult {
const defaultOpts: Required<DataDirectoryOptions> = {
preprocess: (content: string) => {
return content return content
}, },
ignorePatterns: [/README\.md$/i], ignorePatterns: [/README\.md$/i],
extensions: ['.json', '.md', '.markdown', '.yml'], extensions: ['.json', '.md', '.markdown', '.yml'],
} }
opts = Object.assign({}, defaultOpts, opts) const mergedOpts = Object.assign({}, defaultOpts, opts)
// validate input // validate input
assert(Array.isArray(opts.ignorePatterns)) assert(Array.isArray(mergedOpts.ignorePatterns))
assert(opts.ignorePatterns.every(isRegExp)) assert(mergedOpts.ignorePatterns.every(isRegExp))
assert(Array.isArray(opts.extensions)) assert(Array.isArray(mergedOpts.extensions))
assert(opts.extensions.length) assert(mergedOpts.extensions.length)
// start with an empty data object // start with an empty data object
const data = {} const data: DataDirectoryResult = {}
// find YAML and Markdown files in the given directory, recursively // find YAML and Markdown files in the given directory, recursively
const filenames = walk(dir, { includeBasePath: true }).filter((filename) => { const filenames = walk(dir, { includeBasePath: true }).filter((filename: string) => {
// ignore files that match any of ignorePatterns regexes // ignore files that match any of ignorePatterns regexes
if (opts.ignorePatterns.some((pattern) => pattern.test(filename))) return false if (mergedOpts.ignorePatterns.some((pattern) => pattern.test(filename))) return false
// ignore files that don't have a whitelisted file extension // ignore files that don't have a whitelisted file extension
return opts.extensions.includes(path.extname(filename).toLowerCase()) return mergedOpts.extensions.includes(path.extname(filename).toLowerCase())
}) })
const files = filenames.map((filename) => [filename, fs.readFileSync(filename, 'utf8')]) const files: [string, string][] = filenames.map((filename: string) => [
filename,
fs.readFileSync(filename, 'utf8'),
])
files.forEach(([filename, fileContent]) => { files.forEach(([filename, fileContent]) => {
// derive `foo.bar.baz` object key from `foo/bar/baz.yml` filename // derive `foo.bar.baz` object key from `foo/bar/baz.yml` filename
const key = filenameToKey(path.relative(dir, filename)) const key = filenameToKey(path.relative(dir, filename))
const extension = path.extname(filename).toLowerCase() const extension = path.extname(filename).toLowerCase()
if (opts.preprocess) fileContent = opts.preprocess(fileContent) let processedContent = fileContent
if (mergedOpts.preprocess) {
processedContent = mergedOpts.preprocess(fileContent)
}
// Add this file's data to the global data object. // Add this file's data to the global data object.
// Note we want to use `setWith` instead of `set` so we can customize the type during path creation. // Note we want to use `setWith` instead of `set` so we can customize the type during path creation.
@@ -51,17 +71,17 @@ export default function dataDirectory(dir, opts = {}) {
// See https://lodash.com/docs#set for an explanation. // See https://lodash.com/docs#set for an explanation.
switch (extension) { switch (extension) {
case '.json': case '.json':
setWith(data, key, JSON.parse(fileContent), Object) setWith(data, key, JSON.parse(processedContent), Object)
break break
case '.yml': case '.yml':
setWith(data, key, yaml.load(fileContent, { filename }), Object) setWith(data, key, yaml.load(processedContent, { filename }), Object)
break break
case '.md': case '.md':
case '.markdown': case '.markdown':
// Use `matter` to drop frontmatter, since localized reusable Markdown files // Use `matter` to drop frontmatter, since localized reusable Markdown files
// can potentially have frontmatter, but we want to prevent the frontmatter // can potentially have frontmatter, but we want to prevent the frontmatter
// from being rendered. // from being rendered.
setWith(data, key, matter(fileContent).content, Object) setWith(data, key, matter(processedContent).content, Object)
break break
} }
}) })

View File

@@ -1,4 +1,8 @@
export default { interface DataSchemas {
[key: string]: string
}
const dataSchemas: DataSchemas = {
'data/features': '#src/data-directory/lib/data-schemas/features.js', 'data/features': '#src/data-directory/lib/data-schemas/features.js',
'data/variables': '#src/data-directory/lib/data-schemas/variables.js', 'data/variables': '#src/data-directory/lib/data-schemas/variables.js',
'data/learning-tracks': '#src/data-directory/lib/data-schemas/learning-tracks.js', 'data/learning-tracks': '#src/data-directory/lib/data-schemas/learning-tracks.js',
@@ -7,3 +11,5 @@ export default {
'data/glossaries/candidates.yml': '#src/data-directory/lib/data-schemas/glossaries-candidates.js', 'data/glossaries/candidates.yml': '#src/data-directory/lib/data-schemas/glossaries-candidates.js',
'data/glossaries/external.yml': '#src/data-directory/lib/data-schemas/glossaries-external.js', 'data/glossaries/external.yml': '#src/data-directory/lib/data-schemas/glossaries-external.js',
} }
export default dataSchemas

View File

@@ -1,5 +1,6 @@
import path from 'path' import path from 'path'
import { escapeRegExp } from 'lodash-es' import { escapeRegExp } from 'lodash-es'
/* eslint-disable prefer-regex-literals */ /* eslint-disable prefer-regex-literals */
// slash at the beginning of a filename // slash at the beginning of a filename
@@ -14,8 +15,8 @@ const windowsPathSeparator = new RegExp('/', 'g')
const windowsDoubleSlashSeparator = new RegExp('\\\\', 'g') const windowsDoubleSlashSeparator = new RegExp('\\\\', 'g')
// derive `foo.bar.baz` object key from `foo/bar/baz.yml` filename // derive `foo.bar.baz` object key from `foo/bar/baz.yml` filename
export default function filenameToKey(filename) { export default function filenameToKey(filename: string): string {
const extension = new RegExp(`${path.extname(filename)}$`) const extension = new RegExp(`${escapeRegExp(path.extname(filename))}$`)
const key = filename const key = filename
.replace(extension, '') .replace(extension, '')
.replace(leadingPathSeparator, '') .replace(leadingPathSeparator, '')

View File

@@ -7,7 +7,7 @@ import { beforeAll, describe, expect, test } from 'vitest'
import { getJsonValidator, validateJson } from '#src/tests/lib/validate-json-schema.js' import { getJsonValidator, validateJson } from '#src/tests/lib/validate-json-schema.js'
import { formatAjvErrors } from '#src/tests/helpers/schemas.js' import { formatAjvErrors } from '#src/tests/helpers/schemas.js'
import dataSchemas from '#src/data-directory/lib/data-schemas/index.js' import dataSchemas from '#src/data-directory/lib/data-schemas/index.ts'
const schemaPaths = Object.keys(dataSchemas) const schemaPaths = Object.keys(dataSchemas)
const singleFilesSchemas = schemaPaths.filter((schemaPath) => extname(schemaPath)) const singleFilesSchemas = schemaPaths.filter((schemaPath) => extname(schemaPath))

View File

@@ -1,6 +1,6 @@
import { describe, expect, test } from 'vitest' import { describe, expect, test } from 'vitest'
import filenameToKey from '#src/data-directory/lib/filename-to-key.js' import filenameToKey from '#src/data-directory/lib/filename-to-key.ts'
describe('filename-to-key', () => { describe('filename-to-key', () => {
test('converts filenames to object keys', () => { test('converts filenames to object keys', () => {

View File

@@ -3,7 +3,7 @@ import path from 'path'
import { describe, expect, test } from 'vitest' import { describe, expect, test } from 'vitest'
import dataDirectory from '#src/data-directory/lib/data-directory.js' import dataDirectory from '#src/data-directory/lib/data-directory.ts'
const __dirname = path.dirname(fileURLToPath(import.meta.url)) const __dirname = path.dirname(fileURLToPath(import.meta.url))
const fixturesDir = path.join(__dirname, 'fixtures') const fixturesDir = path.join(__dirname, 'fixtures')

View File

@@ -1,7 +1,7 @@
import semver from 'semver' import semver from 'semver'
import { supported } from '@/versions/lib/enterprise-server-releases.js' import { supported } from '@/versions/lib/enterprise-server-releases.js'
import getDataDirectory from '@/data-directory/lib/data-directory.js' import getDataDirectory from '@/data-directory/lib/data-directory'
import { FeatureData, FrontmatterVersions } from '@/types.js' import { FeatureData, FrontmatterVersions } from '@/types.js'
// Return true if lowestSupportedVersion > semVerRange // Return true if lowestSupportedVersion > semVerRange