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,162 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { glob } from 'glob';
4
+ import micromatch from 'micromatch';
5
+
6
+ export interface ScannedFile {
7
+ path: string;
8
+ name: string;
9
+ directory: string;
10
+ extension: string;
11
+ isDirectory: boolean;
12
+ }
13
+
14
+ export class FileScanner {
15
+ private rootDir: string;
16
+ private ignorePatterns: string[];
17
+
18
+ constructor(rootDir: string, ignorePatterns: string[] = []) {
19
+ this.rootDir = path.resolve(rootDir);
20
+ this.ignorePatterns = ignorePatterns;
21
+ }
22
+
23
+ async scanDirectory(dir: string = this.rootDir): Promise<ScannedFile[]> {
24
+ const files: ScannedFile[] = [];
25
+
26
+ try {
27
+ const entries = await fs.promises.readdir(dir, { withFileTypes: true });
28
+
29
+ for (const entry of entries) {
30
+ const fullPath = path.join(dir, entry.name);
31
+ const relativePath = path.relative(this.rootDir, fullPath);
32
+
33
+ // Skip ignored paths
34
+ if (this.shouldIgnore(relativePath)) {
35
+ continue;
36
+ }
37
+
38
+ const scannedFile: ScannedFile = {
39
+ path: relativePath,
40
+ name: entry.name,
41
+ directory: path.relative(this.rootDir, dir),
42
+ extension: path.extname(entry.name),
43
+ isDirectory: entry.isDirectory()
44
+ };
45
+
46
+ files.push(scannedFile);
47
+
48
+ // Recursively scan subdirectories
49
+ if (entry.isDirectory()) {
50
+ const subFiles = await this.scanDirectory(fullPath);
51
+ files.push(...subFiles);
52
+ }
53
+ }
54
+ } catch (error) {
55
+ console.error(`Error scanning directory ${dir}:`, error);
56
+ }
57
+
58
+ return files;
59
+ }
60
+
61
+ async findFiles(pattern: string, baseDir?: string): Promise<string[]> {
62
+ const searchDir = baseDir ? path.join(this.rootDir, baseDir) : this.rootDir;
63
+
64
+ try {
65
+ const files = await glob(pattern, {
66
+ cwd: searchDir,
67
+ ignore: this.ignorePatterns,
68
+ nodir: true
69
+ });
70
+
71
+ return files.map(file =>
72
+ baseDir ? path.join(baseDir, file) : file
73
+ );
74
+ } catch (error) {
75
+ console.error(`Error finding files with pattern ${pattern}:`, error);
76
+ return [];
77
+ }
78
+ }
79
+
80
+ async getDirectoryContents(dir: string): Promise<ScannedFile[]> {
81
+ const fullPath = path.join(this.rootDir, dir);
82
+ const files: ScannedFile[] = [];
83
+
84
+ try {
85
+ const entries = await fs.promises.readdir(fullPath, { withFileTypes: true });
86
+
87
+ for (const entry of entries) {
88
+ const relativePath = path.join(dir, entry.name);
89
+
90
+ if (this.shouldIgnore(relativePath)) {
91
+ continue;
92
+ }
93
+
94
+ files.push({
95
+ path: relativePath,
96
+ name: entry.name,
97
+ directory: dir,
98
+ extension: path.extname(entry.name),
99
+ isDirectory: entry.isDirectory()
100
+ });
101
+ }
102
+ } catch (error) {
103
+ // Directory doesn't exist or can't be read
104
+ return [];
105
+ }
106
+
107
+ return files;
108
+ }
109
+
110
+ getComponentDirectories(componentDirs: string[]): string[] {
111
+ const dirs: string[] = [];
112
+
113
+ for (const dir of componentDirs) {
114
+ const fullPath = path.join(this.rootDir, dir);
115
+
116
+ if (!fs.existsSync(fullPath)) {
117
+ continue;
118
+ }
119
+
120
+ try {
121
+ const entries = fs.readdirSync(fullPath, { withFileTypes: true });
122
+
123
+ for (const entry of entries) {
124
+ if (entry.isDirectory()) {
125
+ const relativePath = path.join(dir, entry.name);
126
+ if (!this.shouldIgnore(relativePath)) {
127
+ dirs.push(relativePath);
128
+ }
129
+ }
130
+ }
131
+ } catch (error) {
132
+ console.error(`Error reading component directory ${fullPath}:`, error);
133
+ }
134
+ }
135
+
136
+ return dirs;
137
+ }
138
+
139
+ private shouldIgnore(filePath: string): boolean {
140
+ if (this.ignorePatterns.length === 0) {
141
+ return false;
142
+ }
143
+
144
+ return micromatch.isMatch(filePath, this.ignorePatterns);
145
+ }
146
+
147
+ fileExists(filePath: string): boolean {
148
+ const fullPath = path.join(this.rootDir, filePath);
149
+ return fs.existsSync(fullPath);
150
+ }
151
+
152
+ isDirectory(filePath: string): boolean {
153
+ const fullPath = path.join(this.rootDir, filePath);
154
+ try {
155
+ return fs.statSync(fullPath).isDirectory();
156
+ } catch {
157
+ return false;
158
+ }
159
+ }
160
+ }
161
+
162
+ // Made with
@@ -0,0 +1,99 @@
1
+ import { NamingConvention } from '../types';
2
+
3
+ export function validateNamingConvention(
4
+ filename: string,
5
+ convention: NamingConvention
6
+ ): boolean {
7
+ // Remove extension
8
+ const nameWithoutExt = filename.replace(/\.[^/.]+$/, '');
9
+
10
+ // Remove test/spec/stories suffixes but keep the base name
11
+ const cleanName = nameWithoutExt
12
+ .replace(/\.(test|spec|stories|types|d)$/, '');
13
+
14
+ switch (convention) {
15
+ case 'PascalCase':
16
+ return /^[A-Z][a-zA-Z0-9]*$/.test(cleanName);
17
+
18
+ case 'camelCase':
19
+ return /^[a-z][a-zA-Z0-9]*$/.test(cleanName);
20
+
21
+ case 'kebab-case':
22
+ return /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/.test(cleanName);
23
+
24
+ case 'snake_case':
25
+ return /^[a-z][a-z0-9]*(_[a-z0-9]+)*$/.test(cleanName);
26
+
27
+ case 'UPPER_CASE':
28
+ return /^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$/.test(cleanName);
29
+
30
+ default:
31
+ return true;
32
+ }
33
+ }
34
+
35
+ export function getExpectedNaming(
36
+ filename: string,
37
+ convention: NamingConvention
38
+ ): string {
39
+ const nameWithoutExt = filename.replace(/\.[^/.]+$/, '');
40
+ const ext = filename.substring(nameWithoutExt.length);
41
+
42
+ let converted = nameWithoutExt;
43
+
44
+ switch (convention) {
45
+ case 'PascalCase':
46
+ converted = toPascalCase(nameWithoutExt);
47
+ break;
48
+
49
+ case 'camelCase':
50
+ converted = toCamelCase(nameWithoutExt);
51
+ break;
52
+
53
+ case 'kebab-case':
54
+ converted = toKebabCase(nameWithoutExt);
55
+ break;
56
+
57
+ case 'snake_case':
58
+ converted = toSnakeCase(nameWithoutExt);
59
+ break;
60
+
61
+ case 'UPPER_CASE':
62
+ converted = toUpperCase(nameWithoutExt);
63
+ break;
64
+ }
65
+
66
+ return converted + ext;
67
+ }
68
+
69
+ function toPascalCase(str: string): string {
70
+ return str
71
+ .replace(/[-_\s]+(.)?/g, (_, c) => (c ? c.toUpperCase() : ''))
72
+ .replace(/^(.)/, (c) => c.toUpperCase());
73
+ }
74
+
75
+ function toCamelCase(str: string): string {
76
+ return str
77
+ .replace(/[-_\s]+(.)?/g, (_, c) => (c ? c.toUpperCase() : ''))
78
+ .replace(/^(.)/, (c) => c.toLowerCase());
79
+ }
80
+
81
+ function toKebabCase(str: string): string {
82
+ return str
83
+ .replace(/([a-z])([A-Z])/g, '$1-$2')
84
+ .replace(/[\s_]+/g, '-')
85
+ .toLowerCase();
86
+ }
87
+
88
+ function toSnakeCase(str: string): string {
89
+ return str
90
+ .replace(/([a-z])([A-Z])/g, '$1_$2')
91
+ .replace(/[\s-]+/g, '_')
92
+ .toLowerCase();
93
+ }
94
+
95
+ function toUpperCase(str: string): string {
96
+ return toSnakeCase(str).toUpperCase();
97
+ }
98
+
99
+ // Made with
package/tsconfig.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "commonjs",
5
+ "lib": ["ES2020", "DOM"],
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "resolveJsonModule": true,
13
+ "declaration": true,
14
+ "declarationMap": true,
15
+ "sourceMap": true,
16
+ "moduleResolution": "node"
17
+ },
18
+ "include": ["src/**/*"],
19
+ "exclude": ["node_modules", "dist", "**/*.test.ts"]
20
+ }