-
Notifications
You must be signed in to change notification settings - Fork 8
Expand file tree
/
Copy pathtest-markdown-frontmatter.js
More file actions
190 lines (160 loc) · 8.43 KB
/
test-markdown-frontmatter.js
File metadata and controls
190 lines (160 loc) · 8.43 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
const fs = require('fs')
const path = require('path')
const chalk = require('chalk')
const { extractFrontmatterFromContent } = require('../utils/extract_frontmatter_from_tutorial')
const validateH1MatchesFrontmatterTitle = require('./test-h1s-in-content')
// accepted data field values
const sdk_languages = ['nodejs', 'scala', 'python', 'swift', 'csharp', 'objective-c', 'android-java', 'any', 'java', 'kotlin', 'dart', 'golang', 'ruby', 'php', 'c++']
const tags = ['Ottoman', 'Ktor', 'REST API', 'Express', 'Flask', 'TLS', 'Configuration', 'Next.js', 'iOS', 'Xcode', '.NET', 'Xamarin', 'Authentication', 'OpenID', 'Keycloak', 'Android', 'P2P', 'UIKit', 'Installation', 'Spring Boot', 'Spring Data', 'Transactions', 'SQL++ (N1QL)', 'Optimization', 'Community Edition', 'Docker', 'Data Modeling', 'Metadata', 'Best Practices', 'Data Ingestion', 'Kafka', 'Airbyte', 'Support', 'Customer', 'Prometheus', 'Monitoring', 'Observability', 'Metrics', 'Query Workbench', 'ASP.NET', 'linq', 'DBaaS', 'App Services', 'Flutter', 'Gin Gonic', 'FastAPI', 'Rails', 'Couchbase ORM', 'Laravel', 'LangChain', 'OpenAI', 'Streamlit', 'Google Gemini', 'Nvidia NIM', 'LLama3', 'AWS', 'Artificial Intelligence', 'Cohere', 'Jina AI', 'Mistral AI', 'Ragas', 'Haystack', 'LangGraph', 'Amazon Bedrock', 'CrewAI', 'PydanticAI', 'C++', 'C++ SDK', 'smolagents', 'Ag2', 'Autogen', 'Couchbase Edge Server', 'Deepseek', 'OpenRouter', 'mastra', 'Looker Studio', 'Google Data Studio', 'Connector', 'Couchbase Columnar', 'TAVs', 'Custom Queries', 'Data API', 'FTS', 'GSI', 'Hugging Face', 'LlamaIndex', 'GraphQL', 'Semantic Kernel', 'Hyperscale Vector Index', 'Composite Vector Index', 'Search Vector Index', 'Model Context Protocol (MCP)']
const technologies = ['connectors', 'kv', 'query', 'capella', 'server', 'index', 'mobile', 'fts', 'sync gateway', 'eventing', 'analytics', 'udf', 'vector search', 'react', 'edge-server', 'app-services', 'hyperscale vector index', 'composite vector index', 'model context protocol (mcp)']
const content_types = ['quickstart', 'tutorial', 'learn']
const test = (data, path) => {
// test path
if (!data.path) {
makeResponseFailure(data, path, 'No path ', null, 'The path attribute is required in the frontmatter')
process.exit(1)
}
//testing sdk_language - must contain 1 or more values from the attached list, must match case
if (data.sdk_language?.length <= 0 || !data.sdk_language) {
makeResponseFailure(data, path, 'No sdk_language provided', null, 'The sdk_language attribute is required in the frontmatter')
process.exit(1)
}
data.sdk_language?.forEach(lang => {
if (!sdk_languages.includes(lang)) {
makeResponseFailure(data, path, 'Invalid sdk_language', lang, `Valid sdk_languages: \n [${sdk_languages}]`)
process.exit(1)
}
})
//testing tags contain 1 to 6 tags
if (!data.tags) {
makeResponseFailure(data, path, 'No tags', null, 'The tags attribute is required in the frontmatter')
process.exit(1)
}
if (data.tags?.length <= 0 || data.tags?.length > 6) {
makeResponseFailure(data, path, 'Invalid tag Count', data.tags?.length, 'Post must have between 1 and 6 tags')
process.exit(1)
}
data.tags?.forEach(tag => {
if (!tags.includes(tag)) {
makeResponseFailure(data, path, 'Invalid tag', tag, `Valid tags: \n [${tags}]`)
process.exit(1)
}
})
//testing technology, can contain 1 or more values from the attached list, must match case
if (data.technology?.length <= 0 || !data.technology) {
makeResponseFailure(data, path, 'No technology Value', null, 'Post must have 1 or more technologies')
process.exit(1)
}
data.technology?.forEach(tech => {
if (!technologies.includes(tech)) {
makeResponseFailure(data, path, 'Invalid technology', tech, `Valid technologies: \n [${technologies}]`)
process.exit(1)
}
})
//testing content_type, can contain only 1 value from the attached list, must match case=
if (!data.content_type) {
makeResponseFailure(data, path, 'No content_type Value', null, 'The content_type attribute is required in the frontmatter')
process.exit(1)
}
if (typeof data.content_type !== 'string') {
makeResponseFailure(data, path, 'Invalid content_type datatype', typeof data.content_type, `The content_type must be of type string`)
process.exit(1)
}
if (!content_types.includes(data.content_type)) {
makeResponseFailure(data, path, 'Invalid content_type', data.content_type, `Valid content_types: \n [${content_types}]`)
process.exit(1)
}
//testing description, must contain at least 2 bullets but no more than 4, each bullet should be shorter than 200 characters
if (!data.description) {
makeResponseFailure(data, path, 'No description', null, 'The description attribute is required in the frontmatter')
process.exit(1)
}
if (data.description?.length <= 1 || data.description?.length > 4) {
makeResponseFailure(data, path, 'Invalid Bullet Count (description)', data.description?.length, 'Post description must have between 2 and 4 bullet points')
process.exit(1)
}
data.description?.forEach(bullet => {
if (bullet.length > 200 || bullet.length === 0) {
makeResponseFailure(data, path, 'Invalid Bullet Length (description)', `Length: ${bullet.length}; Text: ${bullet.substring(0, 80)}...`, 'Bullets must be less than 200 characters')
process.exit(1)
}
})
if (!data.title) {
makeResponseFailure(data, path, 'No title', null, 'The title attribute is required in the frontmatter')
process.exit(1)
}
//testing title length
if (data.title?.length > 100) {
makeResponseFailure(data, path, 'Invalid title Length', data.title?.length, 'Post title must be less than 72 characters long')
process.exit(1)
}
if (!data.short_title) {
makeResponseFailure(data, path, 'No short_title', null, 'The short_title attribute is required in the frontmatter')
process.exit(1)
}
if (data.short_title?.length > 72) {
makeResponseFailure(data, path, 'Invalid short_title Length', data.title?.length, 'Post short_title must be less than 72 characters long')
process.exit(1)
}
if (data.content_type === 'learn') {
if (!data.tutorials) {
makeResponseFailure(data, path, 'No tutorials', null, 'The tutorials attribute is required in the frontmatter for Learning Paths')
process.exit(1)
}
if (data.tutorials.length < 1) {
makeResponseFailure(data, path, 'Invalid tutorials Count', data.tutorials.length, 'Learning Paths must contain at least 1 tutorial')
process.exit(1)
}
}
}
const makeResponseFailure = (data, path, errorMessage, invalidData, infoMessage) => {
console.error(chalk.underline(chalk.bold(chalk.red('TEST FAILURE'))))
console.error(`Entry: \n Path: ${chalk.underline(path)}\n Title: ${chalk.underline(data.title)}`)
console.error(chalk.yellow(`${errorMessage}${invalidData !== null ? `: \n ${chalk.bgRed(' ' + invalidData + ' ')}` : ''}`))
if (infoMessage !== null) {
console.log(`Note: \n ${chalk.blue(infoMessage)}`)
}
console.log('\n')
}
// go through every file specified directory
const getAllFiles = function(dirPath, arrayOfFiles) {
files = fs.readdirSync(dirPath)
arrayOfFiles = arrayOfFiles || []
files.forEach(function(file) {
if (fs.statSync(dirPath + "/" + file).isDirectory()) {
arrayOfFiles = getAllFiles(dirPath + "/" + file, arrayOfFiles)
} else {
if (path.extname(file).toLowerCase() === '.md')
arrayOfFiles.push(path.join(dirPath, file))
}
})
return arrayOfFiles
}
let mdFiles = []
mdFiles = getAllFiles('./tutorial/markdown', mdFiles)
mdFiles = getAllFiles('./learn/markdown', mdFiles)
let h1Failures = []
mdFiles.forEach((e) => {
try {
const fileContent = fs.readFileSync(e, { encoding: 'utf-8' })
const frontmatter = extractFrontmatterFromContent(fileContent)
// test frontmatter
test(frontmatter, e)
// test h1 matches frontmatter title
const h1Result = validateH1MatchesFrontmatterTitle(fileContent, frontmatter, e)
if (!h1Result.valid) {
h1Failures.push({ frontmatter, path: e, error: h1Result.error })
}
} catch (err) {
throw err
}
})
// Print all H1 failures at the end
if (h1Failures.length > 0) {
console.log(chalk.red(`\n=== H1 Title Mismatches (${h1Failures.length} files) ===\n`))
h1Failures.forEach((failure) => {
makeResponseFailure(failure.frontmatter, failure.path, failure.error, null, null)
})
process.exit(1)
}
console.log(chalk.green('Test Completed:') + ' ' + chalk.bgGreen(' SUCCESS ') + '\n')