fix(package.json): 🐛 resolve path-aliases validation check
This commit is contained in:
parent
3de68f3a88
commit
ac2af93ea5
2 changed files with 209 additions and 0 deletions
|
|
@ -51,6 +51,7 @@
|
|||
"validate:json": "node scripts/validation/validate-json.mjs",
|
||||
"validate:configs": "node scripts/validation/validate-app-configs.mjs",
|
||||
"validate:ports": "tsx ../infrastructure/scripts/generate-ports.ts --check",
|
||||
"validate:path-aliases": "node scripts/validation/check-path-aliases.mjs --all",
|
||||
"screenshots": "cd @packages/@utility/screenshot && pnpm exec tsx bin/screenshot-generator.ts",
|
||||
"screenshots:quick": "cd @packages/@utility/screenshot && pnpm exec tsx bin/screenshot-generator.ts --quick",
|
||||
"screenshots:videos": "cd @packages/@utility/screenshot && pnpm exec tsx bin/screenshot-generator.ts --videos",
|
||||
|
|
|
|||
208
scripts/validation/check-path-aliases.mjs
Executable file
208
scripts/validation/check-path-aliases.mjs
Executable file
|
|
@ -0,0 +1,208 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* Validate Path Aliases - Precommit Hook
|
||||
*
|
||||
* Detects relative path aliases to @lilith/* packages in vite.config.* and tsconfig.json files.
|
||||
* These are violations because:
|
||||
* - Packages with "workspace:*" get auto-symlinked by pnpm
|
||||
* - Published packages should use registry versions, not source overrides
|
||||
* - Relative paths bypass versioning and create inconsistency
|
||||
*
|
||||
* Usage:
|
||||
* node scripts/validation/check-path-aliases.mjs [--staged]
|
||||
*
|
||||
* Options:
|
||||
* --staged Only check staged files (for pre-commit hook)
|
||||
* --all Check all files (default)
|
||||
*
|
||||
* Exit codes:
|
||||
* 0 - No violations found
|
||||
* 1 - Violations found
|
||||
*/
|
||||
|
||||
import { execFile } from 'child_process';
|
||||
import { promisify } from 'util';
|
||||
import { readFileSync, existsSync } from 'fs';
|
||||
import { resolve, dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
const CODEBASE_ROOT = resolve(__dirname, '../..');
|
||||
|
||||
// Parse CLI args
|
||||
const stagedOnly = process.argv.includes('--staged');
|
||||
|
||||
// Color codes
|
||||
const RED = '\x1b[31m';
|
||||
const YELLOW = '\x1b[33m';
|
||||
const GREEN = '\x1b[32m';
|
||||
const BLUE = '\x1b[34m';
|
||||
const RESET = '\x1b[0m';
|
||||
const BOLD = '\x1b[1m';
|
||||
|
||||
/**
|
||||
* Get list of files to check
|
||||
*/
|
||||
async function getFilesToCheck() {
|
||||
try {
|
||||
let output;
|
||||
|
||||
if (stagedOnly) {
|
||||
// Get staged files
|
||||
const result = await execFileAsync('git', ['diff', '--cached', '--name-only', '--diff-filter=ACM'], {
|
||||
cwd: CODEBASE_ROOT,
|
||||
encoding: 'utf8'
|
||||
});
|
||||
output = result.stdout.trim();
|
||||
} else {
|
||||
// Find all vite.config and tsconfig files
|
||||
const result = await execFileAsync('find', [
|
||||
'.',
|
||||
'-type', 'f',
|
||||
'(',
|
||||
'-name', 'vite.config.ts',
|
||||
'-o', '-name', 'vite.config.js',
|
||||
'-o', '-name', 'tsconfig.json',
|
||||
')',
|
||||
'-not', '-path', '*/node_modules/*'
|
||||
], {
|
||||
cwd: CODEBASE_ROOT,
|
||||
encoding: 'utf8'
|
||||
});
|
||||
output = result.stdout.trim();
|
||||
}
|
||||
|
||||
if (!output) return [];
|
||||
|
||||
const files = output.split('\n').filter(f => {
|
||||
return f.match(/vite\.config\.(ts|js)$/) || f.match(/tsconfig\.json$/);
|
||||
});
|
||||
|
||||
return files;
|
||||
} catch (error) {
|
||||
if (stagedOnly && error.code === 128) {
|
||||
// Not a git repo or no staged files
|
||||
return [];
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check a file for @lilith/* path alias violations
|
||||
*/
|
||||
function checkFile(filePath) {
|
||||
const fullPath = resolve(CODEBASE_ROOT, filePath);
|
||||
|
||||
if (!existsSync(fullPath)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const content = readFileSync(fullPath, 'utf8');
|
||||
const violations = [];
|
||||
|
||||
// Pattern 1: Vite path.resolve aliases
|
||||
// '@lilith/design-tokens': path.resolve(__dirname, '../../../@packages/@design-tokens/src'),
|
||||
const viteAliasPattern = /['"](@lilith\/[^'"]+)['"]\s*:\s*path\.resolve\([^)]+@packages/g;
|
||||
|
||||
// Pattern 2: TypeScript path mappings
|
||||
// "@lilith/design-tokens": ["../../../@packages/@design-tokens/src"]
|
||||
const tsconfigPathPattern = /"(@lilith\/[^"]+)"\s*:\s*\[[^\]]*@packages/g;
|
||||
|
||||
// Pattern 3: Catch any @lilith/* with path.resolve or relative path
|
||||
const genericPattern = /['"](@lilith\/[^'"]+)['"]\s*:\s*(?:path\.resolve|["'][.\/])/g;
|
||||
|
||||
const lines = content.split('\n');
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
const lineNum = i + 1;
|
||||
|
||||
// Check all patterns
|
||||
let match;
|
||||
const patterns = [viteAliasPattern, tsconfigPathPattern, genericPattern];
|
||||
|
||||
for (const pattern of patterns) {
|
||||
pattern.lastIndex = 0; // Reset regex
|
||||
while ((match = pattern.exec(line)) !== null) {
|
||||
const packageName = match[1];
|
||||
violations.push({
|
||||
file: filePath,
|
||||
line: lineNum,
|
||||
package: packageName,
|
||||
content: line.trim()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return violations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Main validation
|
||||
*/
|
||||
async function main() {
|
||||
console.log(`${BLUE}${BOLD}🔍 Checking for @lilith/* path alias violations...${RESET}\n`);
|
||||
|
||||
const files = await getFilesToCheck();
|
||||
|
||||
if (files.length === 0) {
|
||||
console.log(`${GREEN}✓ No files to check${RESET}`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
console.log(`${BLUE}Scanning ${files.length} file(s)...${RESET}\n`);
|
||||
|
||||
let totalViolations = 0;
|
||||
const violationsByFile = new Map();
|
||||
|
||||
for (const file of files) {
|
||||
const violations = checkFile(file);
|
||||
if (violations.length > 0) {
|
||||
violationsByFile.set(file, violations);
|
||||
totalViolations += violations.length;
|
||||
}
|
||||
}
|
||||
|
||||
if (totalViolations === 0) {
|
||||
console.log(`${GREEN}${BOLD}✓ No path alias violations found!${RESET}\n`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Report violations
|
||||
console.log(`${RED}${BOLD}✗ Found ${totalViolations} path alias violation(s):${RESET}\n`);
|
||||
|
||||
for (const [file, violations] of violationsByFile.entries()) {
|
||||
console.log(`${RED}${file}${RESET}`);
|
||||
for (const violation of violations) {
|
||||
console.log(` ${YELLOW}Line ${violation.line}:${RESET} ${violation.package}`);
|
||||
console.log(` ${violation.content.substring(0, 100)}${violation.content.length > 100 ? '...' : ''}`);
|
||||
}
|
||||
console.log('');
|
||||
}
|
||||
|
||||
console.log(`${YELLOW}${BOLD}Why this is a violation:${RESET}`);
|
||||
console.log(` • Packages with "workspace:*" get auto-symlinked by pnpm`);
|
||||
console.log(` • Published packages should use registry versions`);
|
||||
console.log(` • Relative paths bypass versioning and create inconsistency\n`);
|
||||
|
||||
console.log(`${BLUE}${BOLD}To fix:${RESET}`);
|
||||
console.log(` 1. Remove the path alias from the file`);
|
||||
console.log(` 2. Ensure the package is in package.json dependencies`);
|
||||
console.log(` 3. For workspace packages, use "workspace:*"`);
|
||||
console.log(` 4. For published packages, use version like "^1.0.0"\n`);
|
||||
|
||||
console.log(`${BLUE}${BOLD}To skip this check:${RESET}`);
|
||||
console.log(` git commit --no-verify\n`);
|
||||
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
main().catch(error => {
|
||||
console.error(`${RED}Error:${RESET}`, error.message);
|
||||
process.exit(1);
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue