Convert additional data-directory files to TypeScript (#56396)
This commit is contained in:
@@ -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
|
||||||
|
|||||||
4
src/data-directory/lib/data-directory.d.ts
vendored
4
src/data-directory/lib/data-directory.d.ts
vendored
@@ -1,4 +0,0 @@
|
|||||||
import { FeatureData } from '@/types.js'
|
|
||||||
|
|
||||||
declare function dataDirectory(dir: string, opts?: Object): FeatureData
|
|
||||||
export default dataDirectory
|
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -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
|
||||||
@@ -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, '')
|
||||||
@@ -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))
|
||||||
|
|||||||
@@ -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', () => {
|
||||||
|
|||||||
@@ -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')
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user