project-structure-lint 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/.validate-structurerc.example.json +45 -0
  2. package/CHANGELOG.md +42 -0
  3. package/CONTRIBUTING.md +142 -0
  4. package/LICENSE +21 -0
  5. package/PROJECT_SUMMARY.md +252 -0
  6. package/QUICK_START.md +164 -0
  7. package/README.md +330 -0
  8. package/dist/cli.d.ts +3 -0
  9. package/dist/cli.d.ts.map +1 -0
  10. package/dist/cli.js +121 -0
  11. package/dist/cli.js.map +1 -0
  12. package/dist/config/loader.d.ts +4 -0
  13. package/dist/config/loader.d.ts.map +1 -0
  14. package/dist/config/loader.js +71 -0
  15. package/dist/config/loader.js.map +1 -0
  16. package/dist/core/validator.d.ts +16 -0
  17. package/dist/core/validator.d.ts.map +1 -0
  18. package/dist/core/validator.js +231 -0
  19. package/dist/core/validator.js.map +1 -0
  20. package/dist/index.d.ts +6 -0
  21. package/dist/index.d.ts.map +1 -0
  22. package/dist/index.js +29 -0
  23. package/dist/index.js.map +1 -0
  24. package/dist/presets/index.d.ts +5 -0
  25. package/dist/presets/index.d.ts.map +1 -0
  26. package/dist/presets/index.js +17 -0
  27. package/dist/presets/index.js.map +1 -0
  28. package/dist/presets/react.d.ts +3 -0
  29. package/dist/presets/react.d.ts.map +1 -0
  30. package/dist/presets/react.js +94 -0
  31. package/dist/presets/react.js.map +1 -0
  32. package/dist/reporters/consoleReporter.d.ts +9 -0
  33. package/dist/reporters/consoleReporter.d.ts.map +1 -0
  34. package/dist/reporters/consoleReporter.js +98 -0
  35. package/dist/reporters/consoleReporter.js.map +1 -0
  36. package/dist/types/index.d.ts +59 -0
  37. package/dist/types/index.d.ts.map +1 -0
  38. package/dist/types/index.js +4 -0
  39. package/dist/types/index.js.map +1 -0
  40. package/dist/utils/fileScanner.d.ts +20 -0
  41. package/dist/utils/fileScanner.d.ts.map +1 -0
  42. package/dist/utils/fileScanner.js +166 -0
  43. package/dist/utils/fileScanner.js.map +1 -0
  44. package/dist/utils/naming.d.ts +4 -0
  45. package/dist/utils/naming.d.ts.map +1 -0
  46. package/dist/utils/naming.js +75 -0
  47. package/dist/utils/naming.js.map +1 -0
  48. package/jest.config.js +17 -0
  49. package/package.json +48 -0
  50. package/src/cli.ts +106 -0
  51. package/src/config/loader.ts +79 -0
  52. package/src/core/validator.ts +242 -0
  53. package/src/index.ts +6 -0
  54. package/src/presets/index.ts +16 -0
  55. package/src/presets/react.ts +93 -0
  56. package/src/reporters/consoleReporter.ts +116 -0
  57. package/src/types/index.ts +67 -0
  58. package/src/types/micromatch.d.ts +41 -0
  59. package/src/utils/__tests__/naming.test.ts +107 -0
  60. package/src/utils/fileScanner.ts +162 -0
  61. package/src/utils/naming.ts +99 -0
  62. package/tsconfig.json +20 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fileScanner.d.ts","sourceRoot":"","sources":["../../src/utils/fileScanner.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,cAAc,CAAW;gBAErB,OAAO,EAAE,MAAM,EAAE,cAAc,GAAE,MAAM,EAAO;IAKpD,aAAa,CAAC,GAAG,GAAE,MAAqB,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAsCjE,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAmB/D,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IA8B/D,uBAAuB,CAAC,aAAa,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE;IA6B1D,OAAO,CAAC,YAAY;IAQpB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAKrC,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;CAQvC"}
@@ -0,0 +1,166 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.FileScanner = void 0;
40
+ const fs = __importStar(require("fs"));
41
+ const path = __importStar(require("path"));
42
+ const glob_1 = require("glob");
43
+ const micromatch_1 = __importDefault(require("micromatch"));
44
+ class FileScanner {
45
+ constructor(rootDir, ignorePatterns = []) {
46
+ this.rootDir = path.resolve(rootDir);
47
+ this.ignorePatterns = ignorePatterns;
48
+ }
49
+ async scanDirectory(dir = this.rootDir) {
50
+ const files = [];
51
+ try {
52
+ const entries = await fs.promises.readdir(dir, { withFileTypes: true });
53
+ for (const entry of entries) {
54
+ const fullPath = path.join(dir, entry.name);
55
+ const relativePath = path.relative(this.rootDir, fullPath);
56
+ // Skip ignored paths
57
+ if (this.shouldIgnore(relativePath)) {
58
+ continue;
59
+ }
60
+ const scannedFile = {
61
+ path: relativePath,
62
+ name: entry.name,
63
+ directory: path.relative(this.rootDir, dir),
64
+ extension: path.extname(entry.name),
65
+ isDirectory: entry.isDirectory()
66
+ };
67
+ files.push(scannedFile);
68
+ // Recursively scan subdirectories
69
+ if (entry.isDirectory()) {
70
+ const subFiles = await this.scanDirectory(fullPath);
71
+ files.push(...subFiles);
72
+ }
73
+ }
74
+ }
75
+ catch (error) {
76
+ console.error(`Error scanning directory ${dir}:`, error);
77
+ }
78
+ return files;
79
+ }
80
+ async findFiles(pattern, baseDir) {
81
+ const searchDir = baseDir ? path.join(this.rootDir, baseDir) : this.rootDir;
82
+ try {
83
+ const files = await (0, glob_1.glob)(pattern, {
84
+ cwd: searchDir,
85
+ ignore: this.ignorePatterns,
86
+ nodir: true
87
+ });
88
+ return files.map(file => baseDir ? path.join(baseDir, file) : file);
89
+ }
90
+ catch (error) {
91
+ console.error(`Error finding files with pattern ${pattern}:`, error);
92
+ return [];
93
+ }
94
+ }
95
+ async getDirectoryContents(dir) {
96
+ const fullPath = path.join(this.rootDir, dir);
97
+ const files = [];
98
+ try {
99
+ const entries = await fs.promises.readdir(fullPath, { withFileTypes: true });
100
+ for (const entry of entries) {
101
+ const relativePath = path.join(dir, entry.name);
102
+ if (this.shouldIgnore(relativePath)) {
103
+ continue;
104
+ }
105
+ files.push({
106
+ path: relativePath,
107
+ name: entry.name,
108
+ directory: dir,
109
+ extension: path.extname(entry.name),
110
+ isDirectory: entry.isDirectory()
111
+ });
112
+ }
113
+ }
114
+ catch (error) {
115
+ // Directory doesn't exist or can't be read
116
+ return [];
117
+ }
118
+ return files;
119
+ }
120
+ getComponentDirectories(componentDirs) {
121
+ const dirs = [];
122
+ for (const dir of componentDirs) {
123
+ const fullPath = path.join(this.rootDir, dir);
124
+ if (!fs.existsSync(fullPath)) {
125
+ continue;
126
+ }
127
+ try {
128
+ const entries = fs.readdirSync(fullPath, { withFileTypes: true });
129
+ for (const entry of entries) {
130
+ if (entry.isDirectory()) {
131
+ const relativePath = path.join(dir, entry.name);
132
+ if (!this.shouldIgnore(relativePath)) {
133
+ dirs.push(relativePath);
134
+ }
135
+ }
136
+ }
137
+ }
138
+ catch (error) {
139
+ console.error(`Error reading component directory ${fullPath}:`, error);
140
+ }
141
+ }
142
+ return dirs;
143
+ }
144
+ shouldIgnore(filePath) {
145
+ if (this.ignorePatterns.length === 0) {
146
+ return false;
147
+ }
148
+ return micromatch_1.default.isMatch(filePath, this.ignorePatterns);
149
+ }
150
+ fileExists(filePath) {
151
+ const fullPath = path.join(this.rootDir, filePath);
152
+ return fs.existsSync(fullPath);
153
+ }
154
+ isDirectory(filePath) {
155
+ const fullPath = path.join(this.rootDir, filePath);
156
+ try {
157
+ return fs.statSync(fullPath).isDirectory();
158
+ }
159
+ catch {
160
+ return false;
161
+ }
162
+ }
163
+ }
164
+ exports.FileScanner = FileScanner;
165
+ // Made with
166
+ //# sourceMappingURL=fileScanner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fileScanner.js","sourceRoot":"","sources":["../../src/utils/fileScanner.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import { NamingConvention } from '../types';
2
+ export declare function validateNamingConvention(filename: string, convention: NamingConvention): boolean;
3
+ export declare function getExpectedNaming(filename: string, convention: NamingConvention): string;
4
+ //# sourceMappingURL=naming.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"naming.d.ts","sourceRoot":"","sources":["../../src/utils/naming.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAE5C,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,gBAAgB,GAC3B,OAAO,CA2BT;AAED,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,gBAAgB,GAC3B,MAAM,CA6BR"}
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateNamingConvention = validateNamingConvention;
4
+ exports.getExpectedNaming = getExpectedNaming;
5
+ function validateNamingConvention(filename, convention) {
6
+ // Remove extension
7
+ const nameWithoutExt = filename.replace(/\.[^/.]+$/, '');
8
+ // Remove test/spec/stories suffixes but keep the base name
9
+ const cleanName = nameWithoutExt
10
+ .replace(/\.(test|spec|stories|types|d)$/, '');
11
+ switch (convention) {
12
+ case 'PascalCase':
13
+ return /^[A-Z][a-zA-Z0-9]*$/.test(cleanName);
14
+ case 'camelCase':
15
+ return /^[a-z][a-zA-Z0-9]*$/.test(cleanName);
16
+ case 'kebab-case':
17
+ return /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/.test(cleanName);
18
+ case 'snake_case':
19
+ return /^[a-z][a-z0-9]*(_[a-z0-9]+)*$/.test(cleanName);
20
+ case 'UPPER_CASE':
21
+ return /^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$/.test(cleanName);
22
+ default:
23
+ return true;
24
+ }
25
+ }
26
+ function getExpectedNaming(filename, convention) {
27
+ const nameWithoutExt = filename.replace(/\.[^/.]+$/, '');
28
+ const ext = filename.substring(nameWithoutExt.length);
29
+ let converted = nameWithoutExt;
30
+ switch (convention) {
31
+ case 'PascalCase':
32
+ converted = toPascalCase(nameWithoutExt);
33
+ break;
34
+ case 'camelCase':
35
+ converted = toCamelCase(nameWithoutExt);
36
+ break;
37
+ case 'kebab-case':
38
+ converted = toKebabCase(nameWithoutExt);
39
+ break;
40
+ case 'snake_case':
41
+ converted = toSnakeCase(nameWithoutExt);
42
+ break;
43
+ case 'UPPER_CASE':
44
+ converted = toUpperCase(nameWithoutExt);
45
+ break;
46
+ }
47
+ return converted + ext;
48
+ }
49
+ function toPascalCase(str) {
50
+ return str
51
+ .replace(/[-_\s]+(.)?/g, (_, c) => (c ? c.toUpperCase() : ''))
52
+ .replace(/^(.)/, (c) => c.toUpperCase());
53
+ }
54
+ function toCamelCase(str) {
55
+ return str
56
+ .replace(/[-_\s]+(.)?/g, (_, c) => (c ? c.toUpperCase() : ''))
57
+ .replace(/^(.)/, (c) => c.toLowerCase());
58
+ }
59
+ function toKebabCase(str) {
60
+ return str
61
+ .replace(/([a-z])([A-Z])/g, '$1-$2')
62
+ .replace(/[\s_]+/g, '-')
63
+ .toLowerCase();
64
+ }
65
+ function toSnakeCase(str) {
66
+ return str
67
+ .replace(/([a-z])([A-Z])/g, '$1_$2')
68
+ .replace(/[\s-]+/g, '_')
69
+ .toLowerCase();
70
+ }
71
+ function toUpperCase(str) {
72
+ return toSnakeCase(str).toUpperCase();
73
+ }
74
+ // Made with
75
+ //# sourceMappingURL=naming.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"naming.js","sourceRoot":"","sources":["../../src/utils/naming.ts"],"names":[],"mappings":"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"}
package/jest.config.js ADDED
@@ -0,0 +1,17 @@
1
+ module.exports = {
2
+ preset: 'ts-jest',
3
+ testEnvironment: 'node',
4
+ roots: ['<rootDir>/src'],
5
+ testMatch: ['**/__tests__/**/*.ts', '**/?(*.)+(spec|test).ts'],
6
+ collectCoverageFrom: [
7
+ 'src/**/*.ts',
8
+ '!src/**/*.d.ts',
9
+ '!src/**/__tests__/**',
10
+ '!src/cli.ts'
11
+ ],
12
+ coverageDirectory: 'coverage',
13
+ coverageReporters: ['text', 'lcov', 'html'],
14
+ moduleFileExtensions: ['ts', 'js', 'json'],
15
+ verbose: true
16
+ };
17
+
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "project-structure-lint",
3
+ "version": "1.0.1",
4
+ "description": "A CLI tool to validate project folder structure and file naming conventions with configurable presets",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "validate-structure": "./dist/cli.js"
8
+ },
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "dev": "tsc --watch",
12
+ "test": "jest",
13
+ "lint": "eslint src --ext .ts",
14
+ "prepublishOnly": "npm run build"
15
+ },
16
+ "keywords": [
17
+ "validation",
18
+ "folder-structure",
19
+ "naming-convention",
20
+ "linter",
21
+ "project-structure",
22
+ "react",
23
+ "cli"
24
+ ],
25
+ "author": "",
26
+ "license": "MIT",
27
+ "devDependencies": {
28
+ "@types/jest": "^29.5.11",
29
+ "@types/micromatch": "^4.0.10",
30
+ "@types/node": "^20.10.6",
31
+ "@typescript-eslint/eslint-plugin": "^6.17.0",
32
+ "@typescript-eslint/parser": "^6.17.0",
33
+ "eslint": "^8.56.0",
34
+ "jest": "^29.7.0",
35
+ "ts-jest": "^29.1.1",
36
+ "typescript": "^5.3.3"
37
+ },
38
+ "dependencies": {
39
+ "chalk": "^4.1.2",
40
+ "commander": "^11.1.0",
41
+ "cosmiconfig": "^9.0.0",
42
+ "glob": "^10.3.10",
43
+ "micromatch": "^4.0.5"
44
+ },
45
+ "engines": {
46
+ "node": ">=14.0.0"
47
+ }
48
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,106 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from 'commander';
4
+ import { loadConfig, validateConfig } from './config/loader';
5
+ import { ProjectValidator } from './core/validator';
6
+ import { ConsoleReporter } from './reporters/consoleReporter';
7
+ import { listPresets } from './presets';
8
+
9
+ const program = new Command();
10
+
11
+ program
12
+ .name('validate-structure')
13
+ .description('Validate project folder structure and file naming conventions')
14
+ .version('1.0.0');
15
+
16
+ program
17
+ .command('check')
18
+ .description('Validate the project structure')
19
+ .option('-c, --config <path>', 'Path to configuration file')
20
+ .option('-r, --root <path>', 'Root directory to validate (default: current directory)')
21
+ .action(async (options) => {
22
+ const reporter = new ConsoleReporter();
23
+
24
+ try {
25
+ // Load configuration
26
+ const config = await loadConfig(options.config);
27
+
28
+ // Validate configuration
29
+ const configErrors = validateConfig(config);
30
+ if (configErrors.length > 0) {
31
+ reporter.reportConfigErrors(configErrors);
32
+ process.exit(1);
33
+ }
34
+
35
+ // Run validation
36
+ const rootDir = options.root || process.cwd();
37
+ const validator = new ProjectValidator(config, rootDir);
38
+ const result = await validator.validate();
39
+
40
+ // Report results
41
+ reporter.report(result);
42
+
43
+ // Exit with appropriate code
44
+ process.exit(result.valid ? 0 : 1);
45
+ } catch (error) {
46
+ reporter.reportConfigError(
47
+ error instanceof Error ? error.message : 'Unknown error occurred'
48
+ );
49
+ process.exit(1);
50
+ }
51
+ });
52
+
53
+ program
54
+ .command('init')
55
+ .description('Initialize a configuration file')
56
+ .option('-p, --preset <name>', 'Preset to use (default: react)', 'react')
57
+ .action(async (options) => {
58
+ const fs = await import('fs');
59
+ const path = await import('path');
60
+
61
+ const configPath = path.join(process.cwd(), '.validate-structurerc.json');
62
+
63
+ if (fs.existsSync(configPath)) {
64
+ console.log('Configuration file already exists at:', configPath);
65
+ process.exit(1);
66
+ }
67
+
68
+ const presetName = options.preset;
69
+ const availablePresets = listPresets();
70
+
71
+ if (!availablePresets.includes(presetName)) {
72
+ console.log(`Preset "${presetName}" not found.`);
73
+ console.log('Available presets:', availablePresets.join(', '));
74
+ process.exit(1);
75
+ }
76
+
77
+ const config = {
78
+ preset: presetName,
79
+ // Users can customize these
80
+ rules: {
81
+ componentColocation: {
82
+ enabled: true
83
+ }
84
+ }
85
+ };
86
+
87
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
88
+ console.log(`Configuration file created at: ${configPath}`);
89
+ console.log(`Using preset: ${presetName}`);
90
+ });
91
+
92
+ program
93
+ .command('presets')
94
+ .description('List available presets')
95
+ .action(() => {
96
+ const presets = listPresets();
97
+ console.log('\nAvailable presets:');
98
+ presets.forEach(preset => {
99
+ console.log(` • ${preset}`);
100
+ });
101
+ console.log('');
102
+ });
103
+
104
+ program.parse();
105
+
106
+
@@ -0,0 +1,79 @@
1
+ import { cosmiconfig } from 'cosmiconfig';
2
+ import { ProjectConfig } from '../types';
3
+ import { getPreset } from '../presets';
4
+
5
+ const explorer = cosmiconfig('validate-structure');
6
+
7
+ export async function loadConfig(configPath?: string): Promise<ProjectConfig> {
8
+ let result;
9
+
10
+ if (configPath) {
11
+ result = await explorer.load(configPath);
12
+ } else {
13
+ result = await explorer.search();
14
+ }
15
+
16
+ if (!result || !result.config) {
17
+ // Return default React preset if no config found
18
+ const reactPreset = getPreset('react');
19
+ if (!reactPreset) {
20
+ throw new Error('Default React preset not found');
21
+ }
22
+ return reactPreset.config;
23
+ }
24
+
25
+ const config = result.config as ProjectConfig;
26
+
27
+ // If preset is specified, merge with preset config
28
+ if (config.preset) {
29
+ const preset = getPreset(config.preset);
30
+ if (!preset) {
31
+ throw new Error(`Preset "${config.preset}" not found`);
32
+ }
33
+
34
+ // Merge preset config with user config (user config takes precedence)
35
+ return mergeConfigs(preset.config, config);
36
+ }
37
+
38
+ return config;
39
+ }
40
+
41
+ function mergeConfigs(preset: ProjectConfig, user: ProjectConfig): ProjectConfig {
42
+ return {
43
+ ...preset,
44
+ ...user,
45
+ rules: {
46
+ ...preset.rules,
47
+ ...user.rules,
48
+ componentColocation: user.rules?.componentColocation
49
+ ? {
50
+ ...preset.rules?.componentColocation,
51
+ ...user.rules.componentColocation
52
+ }
53
+ : preset.rules?.componentColocation,
54
+ fileNaming: {
55
+ ...preset.rules?.fileNaming,
56
+ ...user.rules?.fileNaming
57
+ },
58
+ folderStructure: user.rules?.folderStructure || preset.rules?.folderStructure
59
+ },
60
+ ignore: [...(preset.ignore || []), ...(user.ignore || [])]
61
+ };
62
+ }
63
+
64
+ export function validateConfig(config: ProjectConfig): string[] {
65
+ const errors: string[] = [];
66
+
67
+ if (config.rules?.componentColocation?.enabled) {
68
+ if (!config.rules.componentColocation.componentDirs?.length) {
69
+ errors.push('componentColocation.componentDirs must be specified when enabled');
70
+ }
71
+ if (!config.rules.componentColocation.requiredFiles?.length) {
72
+ errors.push('componentColocation.requiredFiles must be specified when enabled');
73
+ }
74
+ }
75
+
76
+ return errors;
77
+ }
78
+
79
+ // Made with