directory-lint 1.0.0
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/README.md +460 -0
- package/dist/index.cjs +145 -0
- package/dist/index.d.cts +49 -0
- package/dist/index.d.ts +49 -0
- package/dist/index.js +104 -0
- package/package.json +40 -0
package/README.md
ADDED
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
# Directory Lint
|
|
2
|
+
|
|
3
|
+
A powerful TypeScript library for validating and generating directory structures based on schema definitions. Ensure your project follows the correct file and folder organization with ease.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/directory-lint)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
|
|
8
|
+
## ✨ Features
|
|
9
|
+
|
|
10
|
+
- 🔍 **Schema-based Validation** - Define your directory structure using intuitive schemas
|
|
11
|
+
- 🏗️ **Automatic Generation** - Create directory structures from schemas with templates
|
|
12
|
+
- 📝 **Pattern Matching** - Support for wildcards and regex patterns
|
|
13
|
+
- 🎯 **15+ Framework Presets** - Built-in schemas for popular frameworks (Next.js, NestJS, Vite, etc.)
|
|
14
|
+
- 🔧 **Flexible Backend** - Pluggable backend system (File System or custom)
|
|
15
|
+
- 📦 **Zero Dependencies** - Lightweight with minimal footprint
|
|
16
|
+
- 🎯 **TypeScript First** - Full TypeScript support with comprehensive types
|
|
17
|
+
|
|
18
|
+
## 📦 Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install directory-lint
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## 🚀 Quick Start
|
|
25
|
+
|
|
26
|
+
### Validating a Directory Structure
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import { DirectoryLint, type LintSchema } from "directory-lint";
|
|
30
|
+
|
|
31
|
+
const schema: LintSchema = {
|
|
32
|
+
"src": {
|
|
33
|
+
type: "dir",
|
|
34
|
+
required: true,
|
|
35
|
+
children: {
|
|
36
|
+
"index.ts": {
|
|
37
|
+
type: "file",
|
|
38
|
+
required: true
|
|
39
|
+
},
|
|
40
|
+
"components": {
|
|
41
|
+
type: "dir",
|
|
42
|
+
required: true
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"package.json": {
|
|
47
|
+
type: "file",
|
|
48
|
+
required: true
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const linter = new DirectoryLint();
|
|
53
|
+
|
|
54
|
+
const result = await linter.validate("./my-project", schema, {
|
|
55
|
+
ignore: ["node_modules", "dist", ".git"]
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
if (result.valid) {
|
|
59
|
+
console.log("✅ Directory structure is valid!");
|
|
60
|
+
} else {
|
|
61
|
+
console.error("❌ Validation errors:", result.errors);
|
|
62
|
+
console.warn("⚠️ Warnings:", result.warnings);
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Generating a Directory Structure
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
import { DirectoryLint, type LintSchema } from "directory-lint";
|
|
70
|
+
|
|
71
|
+
const schema: LintSchema = {
|
|
72
|
+
"src": {
|
|
73
|
+
type: "dir",
|
|
74
|
+
required: true,
|
|
75
|
+
children: {
|
|
76
|
+
"index.ts": {
|
|
77
|
+
type: "file",
|
|
78
|
+
required: true,
|
|
79
|
+
template: "export * from './components';\n"
|
|
80
|
+
},
|
|
81
|
+
"utils": {
|
|
82
|
+
type: "dir",
|
|
83
|
+
children: {
|
|
84
|
+
"helper.ts": {
|
|
85
|
+
type: "file",
|
|
86
|
+
template: () => `// Generated on ${new Date().toISOString()}\n`
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const linter = new DirectoryLint();
|
|
95
|
+
|
|
96
|
+
const result = await linter.generate("./new-project", schema, {
|
|
97
|
+
overwrite: true,
|
|
98
|
+
recursive: true
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
console.log("✅ Directory structure generated!");
|
|
102
|
+
if (result.warnings.length > 0) {
|
|
103
|
+
console.warn("⚠️ Warnings:", result.warnings);
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## 📚 Schema Definition
|
|
108
|
+
|
|
109
|
+
### Basic Schema Structure
|
|
110
|
+
|
|
111
|
+
A schema is defined as a `LintSchema` object where keys represent file/directory names or patterns, and values define the node type and properties.
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
type LintSchema = Record<string, LintNode>;
|
|
115
|
+
|
|
116
|
+
type LintNode = FileLintNode | DirectoryLintNode;
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Node Types
|
|
120
|
+
|
|
121
|
+
#### File Node
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
{
|
|
125
|
+
type: "file",
|
|
126
|
+
required?: boolean, // Default: true
|
|
127
|
+
example?: string, // Example name for pattern-based keys
|
|
128
|
+
template?: string | (() => string), // File content template
|
|
129
|
+
validate?: (content: string) => boolean | Promise<boolean> // Custom validation
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
#### Directory Node
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
{
|
|
137
|
+
type: "dir",
|
|
138
|
+
required?: boolean, // Default: true
|
|
139
|
+
example?: string, // Example name for pattern-based keys
|
|
140
|
+
children?: LintSchema // Nested schema for subdirectories
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Pattern Matching
|
|
145
|
+
|
|
146
|
+
Directory Lint supports flexible pattern matching:
|
|
147
|
+
|
|
148
|
+
#### 1. Exact Match
|
|
149
|
+
```typescript
|
|
150
|
+
{
|
|
151
|
+
"config.json": {
|
|
152
|
+
type: "file",
|
|
153
|
+
required: true
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
#### 2. Wildcard Pattern
|
|
159
|
+
```typescript
|
|
160
|
+
{
|
|
161
|
+
"*.test.ts": {
|
|
162
|
+
type: "file",
|
|
163
|
+
required: false,
|
|
164
|
+
example: "example.test.ts"
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
#### 3. Regex Pattern
|
|
170
|
+
```typescript
|
|
171
|
+
{
|
|
172
|
+
"/^component-.+\\.tsx$/": {
|
|
173
|
+
type: "file",
|
|
174
|
+
required: false,
|
|
175
|
+
example: "component-button.tsx"
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## 🎯 Framework Presets
|
|
181
|
+
|
|
182
|
+
Directory Lint includes **15+ pre-configured schemas** for popular frameworks:
|
|
183
|
+
|
|
184
|
+
### Frontend
|
|
185
|
+
- **Vite** - React, Vue, Svelte + Tailwind + Vitest
|
|
186
|
+
- **Next.js** - App Router & Pages Router
|
|
187
|
+
- **React** - CRA with Redux & Router
|
|
188
|
+
- **Vue** - Vue 3 with Pinia & Router
|
|
189
|
+
- **Angular** - Standalone & Module-based
|
|
190
|
+
- **Svelte/SvelteKit** - With adapters
|
|
191
|
+
- **Nuxt** - Nuxt 3 with plugins
|
|
192
|
+
- **Astro** - Multi-framework support
|
|
193
|
+
- **Remix** - Full-stack framework
|
|
194
|
+
- **Gatsby** - Static site generator
|
|
195
|
+
|
|
196
|
+
### Backend
|
|
197
|
+
- **NestJS** - Microservices, GraphQL, Prisma
|
|
198
|
+
- **Express** - REST APIs with databases
|
|
199
|
+
|
|
200
|
+
### Desktop
|
|
201
|
+
- **Electron** - Desktop applications
|
|
202
|
+
|
|
203
|
+
### Monorepo
|
|
204
|
+
- **Turborepo** - Modern monorepo tool
|
|
205
|
+
- **Nx** - Smart monorepo
|
|
206
|
+
- **Lerna** - Classic monorepo
|
|
207
|
+
|
|
208
|
+
### Using Presets
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
import { DirectoryLint } from "directory-lint";
|
|
212
|
+
import { nextjs, nestjs, vite } from "directory-lint/presets";
|
|
213
|
+
|
|
214
|
+
// Next.js with App Router
|
|
215
|
+
const nextSchema = nextjs({
|
|
216
|
+
appRouter: true,
|
|
217
|
+
withTypeScript: true,
|
|
218
|
+
withTailwind: true,
|
|
219
|
+
withSrcDir: true
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// NestJS with Prisma and GraphQL
|
|
223
|
+
const nestSchema = nestjs({
|
|
224
|
+
withPrisma: true,
|
|
225
|
+
withGraphQL: true,
|
|
226
|
+
withTesting: true
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// Vite with React
|
|
230
|
+
const viteSchema = vite({
|
|
231
|
+
withTypeScript: true,
|
|
232
|
+
withReact: true,
|
|
233
|
+
withVitest: true
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
const linter = new DirectoryLint();
|
|
237
|
+
await linter.validate("./my-app", nextSchema);
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
See the [Presets Documentation](./src/presets/README.md) for all available options.
|
|
241
|
+
|
|
242
|
+
## 🔧 Advanced Features
|
|
243
|
+
|
|
244
|
+
### File Templates
|
|
245
|
+
|
|
246
|
+
```typescript
|
|
247
|
+
const schema: LintSchema = {
|
|
248
|
+
"README.md": {
|
|
249
|
+
type: "file",
|
|
250
|
+
template: "# My Project\n\nGenerated with Directory Lint"
|
|
251
|
+
},
|
|
252
|
+
"package.json": {
|
|
253
|
+
type: "file",
|
|
254
|
+
template: () => JSON.stringify({
|
|
255
|
+
name: "my-project",
|
|
256
|
+
version: "1.0.0",
|
|
257
|
+
createdAt: new Date().toISOString()
|
|
258
|
+
}, null, 2)
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### Custom Validation
|
|
264
|
+
|
|
265
|
+
```typescript
|
|
266
|
+
const schema: LintSchema = {
|
|
267
|
+
"package.json": {
|
|
268
|
+
type: "file",
|
|
269
|
+
required: true,
|
|
270
|
+
validate: async (content) => {
|
|
271
|
+
const pkg = JSON.parse(content);
|
|
272
|
+
return pkg.name && pkg.version;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### Validation Options
|
|
279
|
+
|
|
280
|
+
```typescript
|
|
281
|
+
const result = await linter.validate("./project", schema, {
|
|
282
|
+
ignore: ["node_modules", "dist", ".git", "*.log"]
|
|
283
|
+
});
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### Generation Options
|
|
287
|
+
|
|
288
|
+
```typescript
|
|
289
|
+
const result = await linter.generate("./project", schema, {
|
|
290
|
+
overwrite: true, // Overwrite existing files
|
|
291
|
+
recursive: true // Create parent directories
|
|
292
|
+
});
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### Validation Results
|
|
296
|
+
|
|
297
|
+
```typescript
|
|
298
|
+
interface ValidationResult {
|
|
299
|
+
valid: boolean;
|
|
300
|
+
errors: Array<{
|
|
301
|
+
path: string;
|
|
302
|
+
message: string;
|
|
303
|
+
type: "missing" | "invalid-type" | "custom";
|
|
304
|
+
}>;
|
|
305
|
+
warnings: Array<{
|
|
306
|
+
path: string;
|
|
307
|
+
message: string;
|
|
308
|
+
}>;
|
|
309
|
+
}
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
## 💡 Use Cases
|
|
313
|
+
|
|
314
|
+
### 1. Project Templates
|
|
315
|
+
Ensure scaffolded projects follow the correct structure:
|
|
316
|
+
|
|
317
|
+
```typescript
|
|
318
|
+
import { DirectoryLint } from "directory-lint";
|
|
319
|
+
import { react } from "directory-lint/presets";
|
|
320
|
+
|
|
321
|
+
async function createProject(name: string) {
|
|
322
|
+
const linter = new DirectoryLint();
|
|
323
|
+
const schema = react({ withTypeScript: true, withRedux: true });
|
|
324
|
+
|
|
325
|
+
await linter.generate(`./${name}`, schema);
|
|
326
|
+
console.log(`✅ Created ${name}`);
|
|
327
|
+
}
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### 2. CI/CD Validation
|
|
331
|
+
Verify repository structure in build pipelines:
|
|
332
|
+
|
|
333
|
+
```typescript
|
|
334
|
+
// validate-structure.ts
|
|
335
|
+
import { DirectoryLint } from "directory-lint";
|
|
336
|
+
import { nextjs } from "directory-lint/presets";
|
|
337
|
+
|
|
338
|
+
const linter = new DirectoryLint();
|
|
339
|
+
const schema = nextjs({ appRouter: true, withTypeScript: true });
|
|
340
|
+
|
|
341
|
+
const result = await linter.validate(".", schema, {
|
|
342
|
+
ignore: ["node_modules", "dist", ".next"]
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
if (!result.valid) {
|
|
346
|
+
console.error("Structure validation failed!");
|
|
347
|
+
console.error(result.errors);
|
|
348
|
+
process.exit(1);
|
|
349
|
+
}
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
### 3. Monorepo Validation
|
|
353
|
+
Ensure consistent structure across packages:
|
|
354
|
+
|
|
355
|
+
```typescript
|
|
356
|
+
import { DirectoryLint, type LintSchema } from "directory-lint";
|
|
357
|
+
import { monorepo, nextjs, nestjs } from "directory-lint/presets";
|
|
358
|
+
|
|
359
|
+
const schema: LintSchema = {
|
|
360
|
+
...monorepo({ withTurborepo: true, packageManager: "pnpm" }),
|
|
361
|
+
"apps": {
|
|
362
|
+
type: "dir",
|
|
363
|
+
required: true,
|
|
364
|
+
children: {
|
|
365
|
+
"web": {
|
|
366
|
+
type: "dir",
|
|
367
|
+
children: nextjs({ appRouter: true })
|
|
368
|
+
},
|
|
369
|
+
"api": {
|
|
370
|
+
type: "dir",
|
|
371
|
+
children: nestjs({ withPrisma: true })
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
const linter = new DirectoryLint();
|
|
378
|
+
await linter.validate(".", schema);
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
## 🔌 Custom Backend
|
|
382
|
+
|
|
383
|
+
Create custom backends for different environments:
|
|
384
|
+
|
|
385
|
+
```typescript
|
|
386
|
+
import { LintBackend, LintItem } from "directory-lint";
|
|
387
|
+
|
|
388
|
+
const S3Backend: LintBackend = {
|
|
389
|
+
getAllItems(path: string): LintItem[] {
|
|
390
|
+
// Your S3 implementation
|
|
391
|
+
return [];
|
|
392
|
+
},
|
|
393
|
+
|
|
394
|
+
writeFile(path: string, content: string): void {
|
|
395
|
+
// Your S3 implementation
|
|
396
|
+
},
|
|
397
|
+
|
|
398
|
+
makeDirectory(path: string, recursive?: boolean): void {
|
|
399
|
+
// Your S3 implementation
|
|
400
|
+
},
|
|
401
|
+
|
|
402
|
+
exists(path: string): boolean {
|
|
403
|
+
// Your S3 implementation
|
|
404
|
+
return false;
|
|
405
|
+
}
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
const linter = new DirectoryLint(S3Backend);
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
## 📖 API Reference
|
|
412
|
+
|
|
413
|
+
### `DirectoryLint`
|
|
414
|
+
|
|
415
|
+
#### Constructor
|
|
416
|
+
```typescript
|
|
417
|
+
constructor(backend?: LintBackend)
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
#### Methods
|
|
421
|
+
|
|
422
|
+
##### `validate(path: string, schema: LintSchema, options?: ValidateOptions): Promise<ValidationResult>`
|
|
423
|
+
Validates a directory structure against a schema.
|
|
424
|
+
|
|
425
|
+
**Options:**
|
|
426
|
+
- `ignore`: Array of patterns to ignore
|
|
427
|
+
|
|
428
|
+
**Returns:** Validation result with errors and warnings
|
|
429
|
+
|
|
430
|
+
##### `generate(path: string, schema: LintSchema, options?: GenerateOptions): Promise<GenerationResult>`
|
|
431
|
+
Generates a directory structure based on a schema.
|
|
432
|
+
|
|
433
|
+
**Options:**
|
|
434
|
+
- `overwrite`: Overwrite existing files (default: false)
|
|
435
|
+
- `recursive`: Create parent directories (default: false)
|
|
436
|
+
|
|
437
|
+
**Returns:** Generation result with warnings
|
|
438
|
+
|
|
439
|
+
## 🤝 Contributing
|
|
440
|
+
|
|
441
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
442
|
+
|
|
443
|
+
### Adding a New Preset
|
|
444
|
+
|
|
445
|
+
1. Create a new file in `src/presets/`
|
|
446
|
+
2. Export a function that returns a `LintSchema`
|
|
447
|
+
3. Add the export to `src/presets/index.ts`
|
|
448
|
+
4. Update the presets README
|
|
449
|
+
|
|
450
|
+
## 📝 License
|
|
451
|
+
|
|
452
|
+
MIT © Henry Vilani
|
|
453
|
+
|
|
454
|
+
## 🙏 Acknowledgments
|
|
455
|
+
|
|
456
|
+
Built with ❤️ for the developer community.
|
|
457
|
+
|
|
458
|
+
---
|
|
459
|
+
|
|
460
|
+
**Keywords:** linter,validation, filesystem, directorystructure, architecture, lint
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
DirectoryLint: () => DirectoryLint,
|
|
34
|
+
FileSystemBackend: () => FileSystemBackend,
|
|
35
|
+
InvalidItemType: () => InvalidItemType,
|
|
36
|
+
InvalidLint: () => InvalidLint,
|
|
37
|
+
NotFoundRequiredItem: () => NotFoundRequiredItem
|
|
38
|
+
});
|
|
39
|
+
module.exports = __toCommonJS(index_exports);
|
|
40
|
+
|
|
41
|
+
// src/errors.ts
|
|
42
|
+
var InvalidLint = class extends Error {
|
|
43
|
+
constructor(path) {
|
|
44
|
+
super(`Invalid Lint detected in path: ${path}`);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
var NotFoundRequiredItem = class extends Error {
|
|
48
|
+
constructor(item) {
|
|
49
|
+
super(`Not found required item: ${item}`);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
var InvalidItemType = class extends Error {
|
|
53
|
+
constructor(item) {
|
|
54
|
+
super(`Invalid item type (file or dir): ${item}`);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// src/core/lint.ts
|
|
59
|
+
var import_path = require("path");
|
|
60
|
+
|
|
61
|
+
// src/backends/FileSystemBackend.ts
|
|
62
|
+
var fs = __toESM(require("fs"), 1);
|
|
63
|
+
var FileSystemBackend = {
|
|
64
|
+
getAllItems(path) {
|
|
65
|
+
return fs.readdirSync(path, { withFileTypes: true }).map((item) => ({
|
|
66
|
+
name: item.name,
|
|
67
|
+
type: item.isDirectory() ? "dir" : "file"
|
|
68
|
+
}));
|
|
69
|
+
},
|
|
70
|
+
writeFile(path, content) {
|
|
71
|
+
fs.writeFileSync(path, content, { encoding: "utf-8" });
|
|
72
|
+
},
|
|
73
|
+
makeDirectory(path) {
|
|
74
|
+
fs.mkdirSync(path);
|
|
75
|
+
},
|
|
76
|
+
exists(path) {
|
|
77
|
+
return fs.existsSync(path);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// src/core/lint.ts
|
|
82
|
+
var DirectoryLint = class {
|
|
83
|
+
constructor(backend = FileSystemBackend) {
|
|
84
|
+
this.backend = backend;
|
|
85
|
+
}
|
|
86
|
+
generate(path, schema) {
|
|
87
|
+
if (!this.backend.exists(path)) this.backend.makeDirectory(path);
|
|
88
|
+
for (const [pattern, node] of Object.entries(schema)) {
|
|
89
|
+
const isRegexLiteral = pattern.startsWith("/") && pattern.endsWith("/");
|
|
90
|
+
const name = node.example || (isRegexLiteral ? null : pattern.replace(/\*/g, ""));
|
|
91
|
+
if (!name) {
|
|
92
|
+
console.warn(`[Warn] No example field setted to: ${pattern}.`);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
const fullPath = (0, import_path.join)(path, name);
|
|
96
|
+
if (node.type === "dir") {
|
|
97
|
+
if (!this.backend.exists(fullPath)) {
|
|
98
|
+
this.backend.makeDirectory(fullPath);
|
|
99
|
+
}
|
|
100
|
+
if (node.children) {
|
|
101
|
+
this.generate(fullPath, node.children);
|
|
102
|
+
}
|
|
103
|
+
} else if (node.type === "file") {
|
|
104
|
+
if (!this.backend.exists(fullPath)) {
|
|
105
|
+
this.backend.writeFile(fullPath, "");
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
validate(path, schema) {
|
|
111
|
+
this.recursiveValidate(path, schema);
|
|
112
|
+
}
|
|
113
|
+
patternToRegex(pattern) {
|
|
114
|
+
if (pattern.startsWith("/") && pattern.endsWith("/")) {
|
|
115
|
+
return new RegExp(pattern.slice(1 - 1));
|
|
116
|
+
}
|
|
117
|
+
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
118
|
+
return new RegExp(`^${escaped}$`);
|
|
119
|
+
}
|
|
120
|
+
recursiveValidate(path, schema) {
|
|
121
|
+
const items = this.backend.getAllItems(path);
|
|
122
|
+
for (const [pattern, node] of Object.entries(schema)) {
|
|
123
|
+
const regex = this.patternToRegex(pattern);
|
|
124
|
+
const matchedItems = items.filter((item) => regex.test(item.name));
|
|
125
|
+
if (matchedItems.length === 0 && node.required !== false) throw new NotFoundRequiredItem(pattern);
|
|
126
|
+
for (const { name, type } of matchedItems) {
|
|
127
|
+
const fullPath = (0, import_path.join)(path, name);
|
|
128
|
+
if (node.type === "dir") {
|
|
129
|
+
if (type !== "dir") throw new InvalidItemType(name);
|
|
130
|
+
else if (node.children) {
|
|
131
|
+
this.recursiveValidate(fullPath, node.children);
|
|
132
|
+
}
|
|
133
|
+
} else if (node.type === "file" && type !== "file") throw new InvalidItemType(name);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
139
|
+
0 && (module.exports = {
|
|
140
|
+
DirectoryLint,
|
|
141
|
+
FileSystemBackend,
|
|
142
|
+
InvalidItemType,
|
|
143
|
+
InvalidLint,
|
|
144
|
+
NotFoundRequiredItem
|
|
145
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
declare class InvalidLint extends Error {
|
|
2
|
+
constructor(path: string);
|
|
3
|
+
}
|
|
4
|
+
declare class NotFoundRequiredItem extends Error {
|
|
5
|
+
constructor(item: string);
|
|
6
|
+
}
|
|
7
|
+
declare class InvalidItemType extends Error {
|
|
8
|
+
constructor(item: string);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface BaseLintNode {
|
|
12
|
+
required?: boolean;
|
|
13
|
+
example?: string;
|
|
14
|
+
}
|
|
15
|
+
interface FileLintNode extends BaseLintNode {
|
|
16
|
+
type: "file";
|
|
17
|
+
}
|
|
18
|
+
interface DirectoryLintNode extends BaseLintNode {
|
|
19
|
+
type: "dir";
|
|
20
|
+
children?: LintSchema;
|
|
21
|
+
}
|
|
22
|
+
type LintTypes = "file" | "dir";
|
|
23
|
+
type LintSchema = {
|
|
24
|
+
[K: string]: (DirectoryLintNode | FileLintNode);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
type LintItem = {
|
|
28
|
+
name: string;
|
|
29
|
+
type: LintTypes;
|
|
30
|
+
};
|
|
31
|
+
interface LintBackend {
|
|
32
|
+
getAllItems(path: string): LintItem[];
|
|
33
|
+
writeFile(path: string, content: string): void;
|
|
34
|
+
makeDirectory(path: string): void;
|
|
35
|
+
exists(path: string): boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
declare class DirectoryLint {
|
|
39
|
+
private readonly backend;
|
|
40
|
+
constructor(backend?: LintBackend);
|
|
41
|
+
generate(path: string, schema: LintSchema): void;
|
|
42
|
+
validate(path: string, schema: LintSchema): void;
|
|
43
|
+
private patternToRegex;
|
|
44
|
+
private recursiveValidate;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
declare const FileSystemBackend: LintBackend;
|
|
48
|
+
|
|
49
|
+
export { DirectoryLint, FileSystemBackend, InvalidItemType, InvalidLint, type LintBackend, type LintItem, type LintSchema, type LintTypes, NotFoundRequiredItem };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
declare class InvalidLint extends Error {
|
|
2
|
+
constructor(path: string);
|
|
3
|
+
}
|
|
4
|
+
declare class NotFoundRequiredItem extends Error {
|
|
5
|
+
constructor(item: string);
|
|
6
|
+
}
|
|
7
|
+
declare class InvalidItemType extends Error {
|
|
8
|
+
constructor(item: string);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface BaseLintNode {
|
|
12
|
+
required?: boolean;
|
|
13
|
+
example?: string;
|
|
14
|
+
}
|
|
15
|
+
interface FileLintNode extends BaseLintNode {
|
|
16
|
+
type: "file";
|
|
17
|
+
}
|
|
18
|
+
interface DirectoryLintNode extends BaseLintNode {
|
|
19
|
+
type: "dir";
|
|
20
|
+
children?: LintSchema;
|
|
21
|
+
}
|
|
22
|
+
type LintTypes = "file" | "dir";
|
|
23
|
+
type LintSchema = {
|
|
24
|
+
[K: string]: (DirectoryLintNode | FileLintNode);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
type LintItem = {
|
|
28
|
+
name: string;
|
|
29
|
+
type: LintTypes;
|
|
30
|
+
};
|
|
31
|
+
interface LintBackend {
|
|
32
|
+
getAllItems(path: string): LintItem[];
|
|
33
|
+
writeFile(path: string, content: string): void;
|
|
34
|
+
makeDirectory(path: string): void;
|
|
35
|
+
exists(path: string): boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
declare class DirectoryLint {
|
|
39
|
+
private readonly backend;
|
|
40
|
+
constructor(backend?: LintBackend);
|
|
41
|
+
generate(path: string, schema: LintSchema): void;
|
|
42
|
+
validate(path: string, schema: LintSchema): void;
|
|
43
|
+
private patternToRegex;
|
|
44
|
+
private recursiveValidate;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
declare const FileSystemBackend: LintBackend;
|
|
48
|
+
|
|
49
|
+
export { DirectoryLint, FileSystemBackend, InvalidItemType, InvalidLint, type LintBackend, type LintItem, type LintSchema, type LintTypes, NotFoundRequiredItem };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var InvalidLint = class extends Error {
|
|
3
|
+
constructor(path) {
|
|
4
|
+
super(`Invalid Lint detected in path: ${path}`);
|
|
5
|
+
}
|
|
6
|
+
};
|
|
7
|
+
var NotFoundRequiredItem = class extends Error {
|
|
8
|
+
constructor(item) {
|
|
9
|
+
super(`Not found required item: ${item}`);
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
var InvalidItemType = class extends Error {
|
|
13
|
+
constructor(item) {
|
|
14
|
+
super(`Invalid item type (file or dir): ${item}`);
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// src/core/lint.ts
|
|
19
|
+
import { join } from "path";
|
|
20
|
+
|
|
21
|
+
// src/backends/FileSystemBackend.ts
|
|
22
|
+
import * as fs from "fs";
|
|
23
|
+
var FileSystemBackend = {
|
|
24
|
+
getAllItems(path) {
|
|
25
|
+
return fs.readdirSync(path, { withFileTypes: true }).map((item) => ({
|
|
26
|
+
name: item.name,
|
|
27
|
+
type: item.isDirectory() ? "dir" : "file"
|
|
28
|
+
}));
|
|
29
|
+
},
|
|
30
|
+
writeFile(path, content) {
|
|
31
|
+
fs.writeFileSync(path, content, { encoding: "utf-8" });
|
|
32
|
+
},
|
|
33
|
+
makeDirectory(path) {
|
|
34
|
+
fs.mkdirSync(path);
|
|
35
|
+
},
|
|
36
|
+
exists(path) {
|
|
37
|
+
return fs.existsSync(path);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// src/core/lint.ts
|
|
42
|
+
var DirectoryLint = class {
|
|
43
|
+
constructor(backend = FileSystemBackend) {
|
|
44
|
+
this.backend = backend;
|
|
45
|
+
}
|
|
46
|
+
generate(path, schema) {
|
|
47
|
+
if (!this.backend.exists(path)) this.backend.makeDirectory(path);
|
|
48
|
+
for (const [pattern, node] of Object.entries(schema)) {
|
|
49
|
+
const isRegexLiteral = pattern.startsWith("/") && pattern.endsWith("/");
|
|
50
|
+
const name = node.example || (isRegexLiteral ? null : pattern.replace(/\*/g, ""));
|
|
51
|
+
if (!name) {
|
|
52
|
+
console.warn(`[Warn] No example field setted to: ${pattern}.`);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
const fullPath = join(path, name);
|
|
56
|
+
if (node.type === "dir") {
|
|
57
|
+
if (!this.backend.exists(fullPath)) {
|
|
58
|
+
this.backend.makeDirectory(fullPath);
|
|
59
|
+
}
|
|
60
|
+
if (node.children) {
|
|
61
|
+
this.generate(fullPath, node.children);
|
|
62
|
+
}
|
|
63
|
+
} else if (node.type === "file") {
|
|
64
|
+
if (!this.backend.exists(fullPath)) {
|
|
65
|
+
this.backend.writeFile(fullPath, "");
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
validate(path, schema) {
|
|
71
|
+
this.recursiveValidate(path, schema);
|
|
72
|
+
}
|
|
73
|
+
patternToRegex(pattern) {
|
|
74
|
+
if (pattern.startsWith("/") && pattern.endsWith("/")) {
|
|
75
|
+
return new RegExp(pattern.slice(1 - 1));
|
|
76
|
+
}
|
|
77
|
+
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
78
|
+
return new RegExp(`^${escaped}$`);
|
|
79
|
+
}
|
|
80
|
+
recursiveValidate(path, schema) {
|
|
81
|
+
const items = this.backend.getAllItems(path);
|
|
82
|
+
for (const [pattern, node] of Object.entries(schema)) {
|
|
83
|
+
const regex = this.patternToRegex(pattern);
|
|
84
|
+
const matchedItems = items.filter((item) => regex.test(item.name));
|
|
85
|
+
if (matchedItems.length === 0 && node.required !== false) throw new NotFoundRequiredItem(pattern);
|
|
86
|
+
for (const { name, type } of matchedItems) {
|
|
87
|
+
const fullPath = join(path, name);
|
|
88
|
+
if (node.type === "dir") {
|
|
89
|
+
if (type !== "dir") throw new InvalidItemType(name);
|
|
90
|
+
else if (node.children) {
|
|
91
|
+
this.recursiveValidate(fullPath, node.children);
|
|
92
|
+
}
|
|
93
|
+
} else if (node.type === "file" && type !== "file") throw new InvalidItemType(name);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
export {
|
|
99
|
+
DirectoryLint,
|
|
100
|
+
FileSystemBackend,
|
|
101
|
+
InvalidItemType,
|
|
102
|
+
InvalidLint,
|
|
103
|
+
NotFoundRequiredItem
|
|
104
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "directory-lint",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Directory Lint",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Henry Vilani",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"main": "./dist/index.js",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"require": "./dist/index.cjs"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsup src/index.ts --format cjs,esm --dts --clean",
|
|
22
|
+
"example:basic": "ts-node examples/basic-generation.ts"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"linter",
|
|
26
|
+
"validation",
|
|
27
|
+
"filesystem",
|
|
28
|
+
"directory-structure",
|
|
29
|
+
"architecture",
|
|
30
|
+
"lint"
|
|
31
|
+
],
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/node": "^25.2.0",
|
|
34
|
+
"tsup": "^8.5.1",
|
|
35
|
+
"typescript": "^5.9.3"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"ts-node": "^10.9.2"
|
|
39
|
+
}
|
|
40
|
+
}
|