Skip to content

Commit 1093732

Browse files
authored
test: checking for false file references in our docs (#4701)
* feat(test): checking for false file references in our docs * refactor: adding more possibilites of how scss resolves to files
1 parent 2c0c6c3 commit 1093732

File tree

2 files changed

+323
-0
lines changed

2 files changed

+323
-0
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
"rm:builds": "npm run rm:builds --workspace=scripts",
6666
"start": "npm run start --workspace=patternhub",
6767
"test": "npm run test --workspace=scripts",
68+
"test:docs-file-references": "node scripts/check-docs.js",
6869
"test:react-core-components": "playwright test --config output/react/playwright.config.ts --ui",
6970
"test:vue-components": "playwright test --config output/vue/playwright.config.ts --ui",
7071
"update:dependency:playwright": "node scripts/github/update-playwright.js"

scripts/check-docs.js

Lines changed: 322 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,322 @@
1+
#!/usr/bin/env node
2+
import { glob } from 'glob';
3+
import * as fs from 'node:fs';
4+
import path from 'node:path';
5+
import * as process from 'node:process';
6+
7+
// Configuration
8+
const config = {
9+
// File extensions to check
10+
markdownExtensions: ['md', 'mdx'],
11+
// NPM organization prefix to look for
12+
orgPrefix: '@db-ux/',
13+
// Root directory to search from
14+
rootDir: process.cwd(),
15+
// Workspace packages directory (adjust if different)
16+
packagesDir: 'packages',
17+
// Debug mode - set to true to see all references found
18+
debug: process.argv.includes('--debug') || process.env.DEBUG === 'true',
19+
// Folder patterns to ignore
20+
ignorePatterns: [
21+
'node_modules/**',
22+
'**/node_modules/**',
23+
'.git/**',
24+
'docs/migration/**'
25+
]
26+
};
27+
28+
/**
29+
* Find all markdown files in the repository
30+
*/
31+
const findMarkdownFiles = () => {
32+
const patterns = config.markdownExtensions.map((ext) => `**/*.${ext}`);
33+
34+
if (config.debug) {
35+
console.log(`🔍 Searching for patterns: ${patterns.join(', ')}`);
36+
console.log(`📁 Root directory: ${config.rootDir}`);
37+
}
38+
39+
let allFiles = new Set();
40+
for (const pattern of patterns) {
41+
const files = glob.sync(pattern, {
42+
cwd: config.rootDir,
43+
ignore: config.ignorePatterns,
44+
dot: false,
45+
absolute: false
46+
});
47+
allFiles = [...allFiles, ...files];
48+
}
49+
50+
if (config.debug) {
51+
console.log(`📄 Found ${allFiles.length} markdown files:`);
52+
for (const file of allFiles) console.log(` - ${file}`);
53+
console.log('');
54+
}
55+
56+
return allFiles;
57+
};
58+
59+
/**
60+
* Extract file references from markdown content
61+
*/
62+
const extractFileReferences = (content) => {
63+
const references = [];
64+
const lines = content.split('\n');
65+
66+
// Patterns to match different types of imports/references
67+
const patterns = [
68+
// @import statements in CSS/SCSS
69+
/@import\s+["']([^"']+)["']/g,
70+
// Import statements in JS/TS
71+
/import\s+.*?from\s+["']([^"']+)["']/g,
72+
// Require statements
73+
/require\s*\(\s*["']([^"']+)["']\s*\)/g,
74+
// HTML link tags
75+
/href\s*=\s*["']([^"']+)["']/g,
76+
// Generic file paths in code blocks
77+
/["']([^"']*@db-ux\/[^"']+)["']/g
78+
];
79+
80+
// Track line numbers for each reference
81+
for (const [lineIndex, line] of lines.entries()) {
82+
for (const pattern of patterns) {
83+
let match;
84+
// Reset regex lastIndex for each line
85+
pattern.lastIndex = 0;
86+
while ((match = pattern.exec(line)) !== null) {
87+
const reference = match[1];
88+
// Check if reference includes orgPrefix and has a file path (not just package name)
89+
if (reference.includes(config.orgPrefix)) {
90+
// Extract package name and check if there's a file path after it
91+
const packageMatch = reference.match(
92+
/^(@db-ux\/[^/]+)\/(.+)$/
93+
);
94+
if (packageMatch && packageMatch[2]) {
95+
// Only include references that have a file path after the package name
96+
references.push({
97+
reference,
98+
lineNumber: lineIndex + 1,
99+
lineContent: line.trim()
100+
});
101+
}
102+
}
103+
}
104+
}
105+
}
106+
107+
// Remove duplicates based on reference string
108+
const unique = references.filter(
109+
(item, index, array) =>
110+
array.findIndex((other) => other.reference === item.reference) ===
111+
index
112+
);
113+
114+
return unique;
115+
};
116+
117+
/**
118+
* Resolve package reference to actual file path
119+
*/
120+
const resolvePackageReference = (reference) => {
121+
// Extract package name and file path
122+
const match = reference.match(/^(@db-ux\/[^/]+)\/(.+)$/);
123+
if (!match) return null;
124+
125+
const [, packageName, filePath] = match;
126+
const packageDirName = packageName.replace('@db-ux/', '');
127+
128+
// SCSS file resolution: try different variations for SCSS partials and extensions
129+
const generateScssVariations = (originalPath) => {
130+
const variations = [originalPath];
131+
const dir = path.dirname(originalPath);
132+
const basename = path.basename(originalPath);
133+
const ext = path.extname(basename);
134+
const nameWithoutExt = path.basename(basename, ext);
135+
136+
// Add variations with SCSS extensions if no extension provided
137+
if (!ext) {
138+
variations.push(`${originalPath}.scss`, `${originalPath}.css`);
139+
}
140+
141+
// Add variations with underscore prefix (SCSS partials)
142+
if (!nameWithoutExt.startsWith('_')) {
143+
const withUnderscore = path.join(dir, `_${nameWithoutExt}`);
144+
variations.push(withUnderscore);
145+
146+
if (ext) {
147+
variations.push(`${withUnderscore}${ext}`);
148+
} else {
149+
variations.push(
150+
`${withUnderscore}.scss`,
151+
`${withUnderscore}.css`
152+
);
153+
}
154+
}
155+
156+
// Add directory-based imports (SCSS index resolution)
157+
// For "variables", try "variables/index.scss", "variables/_index.scss"
158+
if (!ext) {
159+
const indexVariations = [
160+
path.join(originalPath, 'index.scss'),
161+
path.join(originalPath, 'index.css'),
162+
path.join(originalPath, '_index.scss'),
163+
path.join(originalPath, '_index.css')
164+
];
165+
variations.push(...indexVariations);
166+
}
167+
168+
return variations;
169+
};
170+
171+
const fileVariations = generateScssVariations(filePath);
172+
173+
// Try different possible locations for each file variation
174+
const possiblePaths = [];
175+
176+
for (const variation of fileVariations) {
177+
possiblePaths.push(
178+
// Direct workspace package
179+
path.join(
180+
config.rootDir,
181+
config.packagesDir,
182+
packageDirName,
183+
variation
184+
),
185+
// Node_modules (for published packages)
186+
path.join(config.rootDir, 'node_modules', packageName, variation),
187+
// Workspace node_modules
188+
path.join(
189+
config.rootDir,
190+
config.packagesDir,
191+
packageDirName,
192+
'node_modules',
193+
packageName,
194+
variation
195+
)
196+
);
197+
}
198+
199+
const resolvedPath = possiblePaths.find((p) => fs.existsSync(p));
200+
201+
if (config.debug) {
202+
if (resolvedPath) {
203+
console.log(` 🔍 Resolved to: ${resolvedPath}`);
204+
} else {
205+
console.log(
206+
` ❌ Tried paths: ${possiblePaths.slice(0, 3).join(', ')}...`
207+
);
208+
}
209+
}
210+
211+
return resolvedPath;
212+
};
213+
214+
/**
215+
* Check if a file reference exists
216+
*/
217+
const checkFileReference = (referenceObject, markdownFile) => {
218+
const resolvedPath = resolvePackageReference(referenceObject.reference);
219+
220+
return {
221+
reference: referenceObject.reference,
222+
lineNumber: referenceObject.lineNumber,
223+
lineContent: referenceObject.lineContent,
224+
exists: Boolean(resolvedPath),
225+
resolvedPath,
226+
markdownFile
227+
};
228+
};
229+
230+
/**
231+
* Main function
232+
*/
233+
const checkDocs = () => {
234+
console.log('🔍 Checking documentation file references...\n');
235+
236+
if (config.debug) {
237+
console.log('🐛 Debug mode enabled\n');
238+
}
239+
240+
const markdownFiles = findMarkdownFiles();
241+
const allIssues = [];
242+
const allReferences = [];
243+
let totalReferences = 0;
244+
245+
for (const file of markdownFiles) {
246+
const fullPath = path.join(config.rootDir, file);
247+
const content = fs.readFileSync(fullPath, 'utf8');
248+
const references = extractFileReferences(content);
249+
250+
if (references.length > 0) {
251+
if (config.debug) {
252+
console.log(`📄 Checking ${file}...`);
253+
}
254+
255+
for (const referenceObject of references) {
256+
totalReferences++;
257+
const result = checkFileReference(referenceObject, file);
258+
allReferences.push(result);
259+
260+
if (!result.exists) {
261+
allIssues.push(result);
262+
}
263+
264+
if (config.debug) {
265+
const status = result.exists ? '✅' : '❌';
266+
console.log(
267+
` ${status} ${result.reference} (line ${result.lineNumber})`
268+
);
269+
console.log(` 📝 ${result.lineContent}`);
270+
} else if (!result.exists) {
271+
console.log(
272+
`❌ ${result.reference} in ${file}:${result.lineNumber}`
273+
);
274+
}
275+
}
276+
277+
if (config.debug) {
278+
console.log('');
279+
}
280+
}
281+
}
282+
283+
// Debug output - show all references found
284+
if (config.debug && allReferences.length > 0) {
285+
console.log('🐛 Debug: All references found:');
286+
for (const ref of allReferences) {
287+
const status = ref.exists ? '✅' : '❌';
288+
console.log(
289+
` ${status} ${ref.reference} (in ${ref.markdownFile}:${ref.lineNumber})`
290+
);
291+
console.log(` 📝 Line content: ${ref.lineContent}`);
292+
if (ref.resolvedPath) {
293+
console.log(` 📁 Resolved to: ${ref.resolvedPath}`);
294+
}
295+
}
296+
297+
console.log('');
298+
}
299+
300+
// Summary
301+
console.log('📊 Summary:');
302+
console.log(` Total markdown files checked: ${markdownFiles.length}`);
303+
console.log(` Total references found: ${totalReferences}`);
304+
console.log(` Broken references: ${allIssues.length}`);
305+
306+
if (allIssues.length > 0) {
307+
console.log('\n❌ Broken references found:');
308+
for (const issue of allIssues) {
309+
console.log(` File: ${issue.markdownFile}:${issue.lineNumber}`);
310+
console.log(` Reference: ${issue.reference}`);
311+
console.log(` Line content: ${issue.lineContent}`);
312+
console.log('');
313+
}
314+
315+
process.exit(1);
316+
} else {
317+
console.log('\n✅ All references are valid!');
318+
process.exit(0);
319+
}
320+
};
321+
322+
checkDocs();

0 commit comments

Comments
 (0)