Dynamically register data/tables schemas (#57905)
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
# Data-driven tables
|
# YAML-powered tables
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@ Every data-driven table needs **three files** that work together:
|
|||||||
|-----------|----------|---------|
|
|-----------|----------|---------|
|
||||||
| **Data file** | `data/tables/` | Stores the table content in YAML format |
|
| **Data file** | `data/tables/` | Stores the table content in YAML format |
|
||||||
| **Content file** | `content/` | Displays the table using Liquid templating |
|
| **Content file** | `content/` | Displays the table using Liquid templating |
|
||||||
| **Schema file** | `src/data-directory/lib/data-schemas/` | Validates the YAML structure |
|
| **Schema file** | `src/data-directory/lib/data-schemas/tables/` | Validates the YAML structure |
|
||||||
|
|
||||||
**Estimated time**: 30-60 minutes for a new table
|
**Estimated time**: 30-60 minutes for a new table
|
||||||
|
|
||||||
@@ -49,7 +49,7 @@ Create a new `.yml` file in `data/tables/` with a descriptive name.
|
|||||||
Create a YAML structure that will allow me to generate a table that looks like:
|
Create a YAML structure that will allow me to generate a table that looks like:
|
||||||
[describe your table headers, rows, and columns OR attach an example]
|
[describe your table headers, rows, and columns OR attach an example]
|
||||||
|
|
||||||
See src/secret-scanning/data/public-docs.yml for an example.
|
See data/tables/supported-code-languages.yml for an example.
|
||||||
```
|
```
|
||||||
|
|
||||||
### Step 2: Create the content display
|
### Step 2: Create the content display
|
||||||
@@ -62,7 +62,7 @@ Create a Markdown table that is dynamically rendered using Liquid code.
|
|||||||
Pull data from data/tables/TABLE_NAME.yml.
|
Pull data from data/tables/TABLE_NAME.yml.
|
||||||
The table should look like: [describe your desired output OR attach an example]
|
The table should look like: [describe your desired output OR attach an example]
|
||||||
|
|
||||||
See content/code-security/secret-scanning/introduction/supported-secret-scanning-patterns.md for an example.
|
See content/get-started/learning-about-github/github-language-support.md for an example.
|
||||||
Liquid docs: https://shopify.github.io/liquid
|
Liquid docs: https://shopify.github.io/liquid
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -70,14 +70,14 @@ Liquid docs: https://shopify.github.io/liquid
|
|||||||
|
|
||||||
### Step 3: Create the schema file
|
### Step 3: Create the schema file
|
||||||
|
|
||||||
Create a `.ts` file in `src/data-directory/lib/data-schemas/` with the same name as your YAML file.
|
Create a `.ts` file in `src/data-directory/lib/data-schemas/tables/` with the same name as your YAML file.
|
||||||
|
|
||||||
**Copilot prompt template:**
|
**Copilot prompt template:**
|
||||||
```
|
```
|
||||||
Create a TypeScript schema following prior art under data-schemas that enforces
|
Create a TypeScript schema following prior art under data-schemas that enforces
|
||||||
the structure of the data/TABLE_NAME.yml file.
|
the structure of the data/TABLE_NAME.yml file.
|
||||||
|
|
||||||
See src/data-directory/lib/data-schemas/learning-tracks.ts for an example.
|
See src/data-directory/lib/data-schemas/tables/supported-code-languages.ts for an example.
|
||||||
```
|
```
|
||||||
|
|
||||||
## Testing and validation
|
## Testing and validation
|
||||||
|
|||||||
@@ -1,8 +1,37 @@
|
|||||||
|
import fs from 'fs'
|
||||||
|
import path from 'path'
|
||||||
|
import { fileURLToPath } from 'url'
|
||||||
|
|
||||||
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||||
|
|
||||||
interface DataSchemas {
|
interface DataSchemas {
|
||||||
[key: string]: string
|
[key: string]: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const dataSchemas: DataSchemas = {
|
// Auto-discover table schemas from data/tables/ directory
|
||||||
|
function loadTableSchemas(): DataSchemas {
|
||||||
|
const tablesDir = path.join(process.cwd(), 'data/tables')
|
||||||
|
const schemasDir = path.join(__dirname, 'tables')
|
||||||
|
const tableSchemas: DataSchemas = {}
|
||||||
|
|
||||||
|
if (fs.existsSync(tablesDir)) {
|
||||||
|
const yamlFiles = fs.readdirSync(tablesDir).filter((file) => file.endsWith('.yml'))
|
||||||
|
|
||||||
|
for (const yamlFile of yamlFiles) {
|
||||||
|
const name = path.basename(yamlFile, '.yml')
|
||||||
|
const schemaPath = path.join(schemasDir, `${name}.ts`)
|
||||||
|
|
||||||
|
if (fs.existsSync(schemaPath)) {
|
||||||
|
tableSchemas[`data/tables/${yamlFile}`] = `@/data-directory/lib/data-schemas/tables/${name}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tableSchemas
|
||||||
|
}
|
||||||
|
|
||||||
|
// Manual schema registrations for non-table data
|
||||||
|
const manualSchemas: DataSchemas = {
|
||||||
'data/features': '@/data-directory/lib/data-schemas/features.js',
|
'data/features': '@/data-directory/lib/data-schemas/features.js',
|
||||||
'data/variables': '@/data-directory/lib/data-schemas/variables',
|
'data/variables': '@/data-directory/lib/data-schemas/variables',
|
||||||
'data/learning-tracks': '@/data-directory/lib/data-schemas/learning-tracks.js',
|
'data/learning-tracks': '@/data-directory/lib/data-schemas/learning-tracks.js',
|
||||||
@@ -10,8 +39,12 @@ const dataSchemas: DataSchemas = {
|
|||||||
'data/code-languages.yml': '@/data-directory/lib/data-schemas/code-languages',
|
'data/code-languages.yml': '@/data-directory/lib/data-schemas/code-languages',
|
||||||
'data/glossaries/candidates.yml': '@/data-directory/lib/data-schemas/glossaries-candidates.js',
|
'data/glossaries/candidates.yml': '@/data-directory/lib/data-schemas/glossaries-candidates.js',
|
||||||
'data/glossaries/external.yml': '@/data-directory/lib/data-schemas/glossaries-external.js',
|
'data/glossaries/external.yml': '@/data-directory/lib/data-schemas/glossaries-external.js',
|
||||||
'data/tables/supported-code-languages.yml':
|
}
|
||||||
'@/data-directory/lib/data-schemas/supported-code-languages.js',
|
|
||||||
|
// Combine manual registrations with auto-discovered table schemas
|
||||||
|
const dataSchemas: DataSchemas = {
|
||||||
|
...manualSchemas,
|
||||||
|
...loadTableSchemas(),
|
||||||
}
|
}
|
||||||
|
|
||||||
export default dataSchemas
|
export default dataSchemas
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import yaml from 'js-yaml'
|
import yaml from 'js-yaml'
|
||||||
import { readFileSync } from 'fs'
|
import { readFileSync, existsSync, readdirSync } from 'fs'
|
||||||
import { extname, basename } from 'path'
|
import { extname, basename, join } from 'path'
|
||||||
|
|
||||||
import walk from 'walk-sync'
|
import walk from 'walk-sync'
|
||||||
import { beforeAll, describe, expect, test } from 'vitest'
|
import { beforeAll, describe, expect, test } from 'vitest'
|
||||||
@@ -49,3 +49,24 @@ describe('single data files', () => {
|
|||||||
expect(isValid, formattedErrors).toBe(true)
|
expect(isValid, formattedErrors).toBe(true)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('YAML-powered tables', () => {
|
||||||
|
test('all table files have corresponding schemas', () => {
|
||||||
|
const tablesDir = join(process.cwd(), 'data/tables')
|
||||||
|
const schemasDir = join(__dirname, '../lib/data-schemas/tables')
|
||||||
|
|
||||||
|
if (existsSync(tablesDir)) {
|
||||||
|
const yamlFiles = readdirSync(tablesDir).filter((file) => file.endsWith('.yml'))
|
||||||
|
|
||||||
|
for (const yamlFile of yamlFiles) {
|
||||||
|
const name = basename(yamlFile, '.yml')
|
||||||
|
const schemaPath = join(schemasDir, `${name}.ts`)
|
||||||
|
expect(existsSync(schemaPath)).toBe(true)
|
||||||
|
|
||||||
|
// Also verify it's registered in the dataSchemas
|
||||||
|
const dataKey = `data/tables/${yamlFile}`
|
||||||
|
expect(dataSchemas[dataKey]).toBeDefined()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user