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.
- package/.validate-structurerc.example.json +45 -0
- package/CHANGELOG.md +42 -0
- package/CONTRIBUTING.md +142 -0
- package/LICENSE +21 -0
- package/PROJECT_SUMMARY.md +252 -0
- package/QUICK_START.md +164 -0
- package/README.md +330 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +121 -0
- package/dist/cli.js.map +1 -0
- package/dist/config/loader.d.ts +4 -0
- package/dist/config/loader.d.ts.map +1 -0
- package/dist/config/loader.js +71 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/core/validator.d.ts +16 -0
- package/dist/core/validator.d.ts.map +1 -0
- package/dist/core/validator.js +231 -0
- package/dist/core/validator.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -0
- package/dist/presets/index.d.ts +5 -0
- package/dist/presets/index.d.ts.map +1 -0
- package/dist/presets/index.js +17 -0
- package/dist/presets/index.js.map +1 -0
- package/dist/presets/react.d.ts +3 -0
- package/dist/presets/react.d.ts.map +1 -0
- package/dist/presets/react.js +94 -0
- package/dist/presets/react.js.map +1 -0
- package/dist/reporters/consoleReporter.d.ts +9 -0
- package/dist/reporters/consoleReporter.d.ts.map +1 -0
- package/dist/reporters/consoleReporter.js +98 -0
- package/dist/reporters/consoleReporter.js.map +1 -0
- package/dist/types/index.d.ts +59 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +4 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/fileScanner.d.ts +20 -0
- package/dist/utils/fileScanner.d.ts.map +1 -0
- package/dist/utils/fileScanner.js +166 -0
- package/dist/utils/fileScanner.js.map +1 -0
- package/dist/utils/naming.d.ts +4 -0
- package/dist/utils/naming.d.ts.map +1 -0
- package/dist/utils/naming.js +75 -0
- package/dist/utils/naming.js.map +1 -0
- package/jest.config.js +17 -0
- package/package.json +48 -0
- package/src/cli.ts +106 -0
- package/src/config/loader.ts +79 -0
- package/src/core/validator.ts +242 -0
- package/src/index.ts +6 -0
- package/src/presets/index.ts +16 -0
- package/src/presets/react.ts +93 -0
- package/src/reporters/consoleReporter.ts +116 -0
- package/src/types/index.ts +67 -0
- package/src/types/micromatch.d.ts +41 -0
- package/src/utils/__tests__/naming.test.ts +107 -0
- package/src/utils/fileScanner.ts +162 -0
- package/src/utils/naming.ts +99 -0
- 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
|