directory-lint 1.0.1 → 2.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 +321 -235
- package/dist/index.cjs +80 -52
- package/dist/index.d.cts +71 -23
- package/dist/index.d.ts +71 -23
- package/dist/index.js +77 -49
- package/package.json +2 -4
package/README.md
CHANGED
|
@@ -8,12 +8,12 @@ A powerful TypeScript library for validating and generating directory structures
|
|
|
8
8
|
## ✨ Features
|
|
9
9
|
|
|
10
10
|
- 🔍 **Schema-based Validation** - Define your directory structure using intuitive schemas
|
|
11
|
-
- 🏗️ **Automatic Generation** - Create directory structures from schemas with templates
|
|
11
|
+
- 🏗️ **Automatic Generation** - Create directory structures from schemas with content templates
|
|
12
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
13
|
- 🔧 **Flexible Backend** - Pluggable backend system (File System or custom)
|
|
15
14
|
- 📦 **Zero Dependencies** - Lightweight with minimal footprint
|
|
16
15
|
- 🎯 **TypeScript First** - Full TypeScript support with comprehensive types
|
|
16
|
+
- ✅ **Custom Validation** - Validate file content with custom functions
|
|
17
17
|
|
|
18
18
|
## 📦 Installation
|
|
19
19
|
|
|
@@ -26,11 +26,11 @@ npm install directory-lint
|
|
|
26
26
|
### Validating a Directory Structure
|
|
27
27
|
|
|
28
28
|
```typescript
|
|
29
|
-
import { DirectoryLint, type
|
|
29
|
+
import { DirectoryLint, type ValidateSchema } from "directory-lint";
|
|
30
30
|
|
|
31
|
-
const schema:
|
|
31
|
+
const schema: ValidateSchema = {
|
|
32
32
|
"src": {
|
|
33
|
-
type: "
|
|
33
|
+
type: "directory",
|
|
34
34
|
required: true,
|
|
35
35
|
children: {
|
|
36
36
|
"index.ts": {
|
|
@@ -38,7 +38,7 @@ const schema: LintSchema = {
|
|
|
38
38
|
required: true
|
|
39
39
|
},
|
|
40
40
|
"components": {
|
|
41
|
-
type: "
|
|
41
|
+
type: "directory",
|
|
42
42
|
required: true
|
|
43
43
|
}
|
|
44
44
|
}
|
|
@@ -55,35 +55,28 @@ const result = await linter.validate("./my-project", schema, {
|
|
|
55
55
|
ignore: ["node_modules", "dist", ".git"]
|
|
56
56
|
});
|
|
57
57
|
|
|
58
|
-
|
|
59
|
-
console.log("✅ Directory structure is valid!");
|
|
60
|
-
} else {
|
|
61
|
-
console.error("❌ Validation errors:", result.errors);
|
|
62
|
-
console.warn("⚠️ Warnings:", result.warnings);
|
|
63
|
-
}
|
|
58
|
+
console.log("Validation result:", result);
|
|
64
59
|
```
|
|
65
60
|
|
|
66
61
|
### Generating a Directory Structure
|
|
67
62
|
|
|
68
63
|
```typescript
|
|
69
|
-
import { DirectoryLint, type
|
|
64
|
+
import { DirectoryLint, type GenerateSchema } from "directory-lint";
|
|
70
65
|
|
|
71
|
-
const schema:
|
|
66
|
+
const schema: GenerateSchema = {
|
|
72
67
|
"src": {
|
|
73
|
-
type: "
|
|
74
|
-
required: true,
|
|
68
|
+
type: "directory",
|
|
75
69
|
children: {
|
|
76
70
|
"index.ts": {
|
|
77
71
|
type: "file",
|
|
78
|
-
|
|
79
|
-
template: "export * from './components';\n"
|
|
72
|
+
content: "export * from './components';\n"
|
|
80
73
|
},
|
|
81
74
|
"utils": {
|
|
82
|
-
type: "
|
|
75
|
+
type: "directory",
|
|
83
76
|
children: {
|
|
84
77
|
"helper.ts": {
|
|
85
78
|
type: "file",
|
|
86
|
-
|
|
79
|
+
content: `// Generated on ${new Date().toISOString()}\n`
|
|
87
80
|
}
|
|
88
81
|
}
|
|
89
82
|
}
|
|
@@ -99,51 +92,59 @@ const result = await linter.generate("./new-project", schema, {
|
|
|
99
92
|
});
|
|
100
93
|
|
|
101
94
|
console.log("✅ Directory structure generated!");
|
|
102
|
-
|
|
103
|
-
console.warn("⚠️ Warnings:", result.warnings);
|
|
104
|
-
}
|
|
95
|
+
console.log("Generated paths:", result.paths);
|
|
105
96
|
```
|
|
106
97
|
|
|
107
98
|
## 📚 Schema Definition
|
|
108
99
|
|
|
109
|
-
###
|
|
100
|
+
### Schema Types
|
|
110
101
|
|
|
111
|
-
|
|
102
|
+
Directory Lint uses two distinct schema types:
|
|
112
103
|
|
|
113
|
-
|
|
114
|
-
|
|
104
|
+
- **`GenerateSchema`** - For creating directory structures
|
|
105
|
+
- **`ValidateSchema`** - For validating existing directory structures
|
|
115
106
|
|
|
116
|
-
|
|
117
|
-
|
|
107
|
+
### Validate Schema Structure
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
type ValidateSchema = Record<string, ValidateNode>;
|
|
118
111
|
|
|
119
|
-
|
|
112
|
+
type ValidateNode = ValidateFileSchema | ValidateDirectorySchema;
|
|
120
113
|
|
|
121
|
-
|
|
114
|
+
interface ValidateFileSchema {
|
|
115
|
+
type: "file";
|
|
116
|
+
validate?: (content: any) => boolean;
|
|
117
|
+
required?: boolean; // Default: true
|
|
118
|
+
}
|
|
122
119
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
required?: boolean
|
|
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
|
|
120
|
+
interface ValidateDirectorySchema {
|
|
121
|
+
type: "directory";
|
|
122
|
+
children?: ValidateSchema;
|
|
123
|
+
required?: boolean; // Default: true
|
|
130
124
|
}
|
|
131
125
|
```
|
|
132
126
|
|
|
133
|
-
|
|
127
|
+
### Generate Schema Structure
|
|
134
128
|
|
|
135
129
|
```typescript
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
130
|
+
type GenerateSchema = Record<string, GenerateNode>;
|
|
131
|
+
|
|
132
|
+
type GenerateNode = GenerateFileSchema | GenerateDirectorySchema;
|
|
133
|
+
|
|
134
|
+
interface GenerateFileSchema {
|
|
135
|
+
type: "file";
|
|
136
|
+
content?: any; // File content (string, Buffer, etc.)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
interface GenerateDirectorySchema {
|
|
140
|
+
type: "directory";
|
|
141
|
+
children?: GenerateSchema;
|
|
141
142
|
}
|
|
142
143
|
```
|
|
143
144
|
|
|
144
145
|
### Pattern Matching
|
|
145
146
|
|
|
146
|
-
Directory Lint supports flexible pattern matching:
|
|
147
|
+
Directory Lint supports flexible pattern matching in validation schemas:
|
|
147
148
|
|
|
148
149
|
#### 1. Exact Match
|
|
149
150
|
```typescript
|
|
@@ -160,8 +161,7 @@ Directory Lint supports flexible pattern matching:
|
|
|
160
161
|
{
|
|
161
162
|
"*.test.ts": {
|
|
162
163
|
type: "file",
|
|
163
|
-
required: false
|
|
164
|
-
example: "example.test.ts"
|
|
164
|
+
required: false
|
|
165
165
|
}
|
|
166
166
|
}
|
|
167
167
|
```
|
|
@@ -171,281 +171,371 @@ Directory Lint supports flexible pattern matching:
|
|
|
171
171
|
{
|
|
172
172
|
"/^component-.+\\.tsx$/": {
|
|
173
173
|
type: "file",
|
|
174
|
-
required: false
|
|
175
|
-
example: "component-button.tsx"
|
|
174
|
+
required: false
|
|
176
175
|
}
|
|
177
176
|
}
|
|
178
177
|
```
|
|
179
178
|
|
|
180
|
-
|
|
179
|
+
**Note:** Regex patterns are **not supported** in `GenerateSchema` and will throw a `RegexNotSupported` error.
|
|
181
180
|
|
|
182
|
-
|
|
181
|
+
## 💡 Examples
|
|
183
182
|
|
|
184
|
-
###
|
|
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
|
|
183
|
+
### Basic Generation
|
|
195
184
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
- **Express** - REST APIs with databases
|
|
185
|
+
```typescript
|
|
186
|
+
import { DirectoryLint, type GenerateSchema } from "directory-lint";
|
|
199
187
|
|
|
200
|
-
|
|
201
|
-
|
|
188
|
+
const schema: GenerateSchema = {
|
|
189
|
+
"basic_file": {
|
|
190
|
+
type: "file",
|
|
191
|
+
},
|
|
192
|
+
"basic_folder": {
|
|
193
|
+
type: "directory",
|
|
194
|
+
children: {
|
|
195
|
+
"sub_dir_file": {
|
|
196
|
+
type: "file",
|
|
197
|
+
content: "Hello, World!"
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
202
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
- **Lerna** - Classic monorepo
|
|
203
|
+
const linter = new DirectoryLint();
|
|
204
|
+
await linter.generate("./generated", schema, { overwrite: true });
|
|
205
|
+
```
|
|
207
206
|
|
|
208
|
-
###
|
|
207
|
+
### Basic Validation with Custom Validation
|
|
209
208
|
|
|
210
209
|
```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
|
-
});
|
|
210
|
+
import { DirectoryLint, type ValidateSchema } from "directory-lint";
|
|
228
211
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
212
|
+
const schema: ValidateSchema = {
|
|
213
|
+
"generated": {
|
|
214
|
+
type: "directory",
|
|
215
|
+
required: true,
|
|
216
|
+
children: {
|
|
217
|
+
"content.txt": {
|
|
218
|
+
type: "file",
|
|
219
|
+
required: true
|
|
220
|
+
},
|
|
221
|
+
"*.txt": {
|
|
222
|
+
type: "file",
|
|
223
|
+
validate: (content: string): boolean => {
|
|
224
|
+
return content.length > 1;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
235
230
|
|
|
236
231
|
const linter = new DirectoryLint();
|
|
237
|
-
await linter.validate("./
|
|
232
|
+
const result = await linter.validate("./", schema, {
|
|
233
|
+
ignore: ["node_modules"]
|
|
234
|
+
});
|
|
238
235
|
```
|
|
239
236
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
## 🔧 Advanced Features
|
|
243
|
-
|
|
244
|
-
### File Templates
|
|
237
|
+
### Project Template Generation
|
|
245
238
|
|
|
246
239
|
```typescript
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
240
|
+
import { DirectoryLint, type GenerateSchema } from "directory-lint";
|
|
241
|
+
|
|
242
|
+
const projectSchema: GenerateSchema = {
|
|
243
|
+
"src": {
|
|
244
|
+
type: "directory",
|
|
245
|
+
children: {
|
|
246
|
+
"index.ts": {
|
|
247
|
+
type: "file",
|
|
248
|
+
content: "console.log('Hello, TypeScript!');"
|
|
249
|
+
},
|
|
250
|
+
"types": {
|
|
251
|
+
type: "directory",
|
|
252
|
+
children: {
|
|
253
|
+
"index.d.ts": {
|
|
254
|
+
type: "file",
|
|
255
|
+
content: "export {};"
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
251
260
|
},
|
|
252
261
|
"package.json": {
|
|
253
262
|
type: "file",
|
|
254
|
-
|
|
263
|
+
content: JSON.stringify({
|
|
255
264
|
name: "my-project",
|
|
256
265
|
version: "1.0.0",
|
|
257
|
-
|
|
266
|
+
main: "src/index.ts"
|
|
267
|
+
}, null, 2)
|
|
268
|
+
},
|
|
269
|
+
"tsconfig.json": {
|
|
270
|
+
type: "file",
|
|
271
|
+
content: JSON.stringify({
|
|
272
|
+
compilerOptions: {
|
|
273
|
+
target: "ES2020",
|
|
274
|
+
module: "commonjs",
|
|
275
|
+
strict: true
|
|
276
|
+
}
|
|
258
277
|
}, null, 2)
|
|
259
278
|
}
|
|
260
279
|
};
|
|
280
|
+
|
|
281
|
+
const linter = new DirectoryLint();
|
|
282
|
+
await linter.generate("./my-new-project", projectSchema, {
|
|
283
|
+
recursive: true,
|
|
284
|
+
overwrite: false
|
|
285
|
+
});
|
|
261
286
|
```
|
|
262
287
|
|
|
263
|
-
###
|
|
288
|
+
### CI/CD Validation
|
|
264
289
|
|
|
265
290
|
```typescript
|
|
266
|
-
|
|
291
|
+
import { DirectoryLint, type ValidateSchema } from "directory-lint";
|
|
292
|
+
|
|
293
|
+
const ciSchema: ValidateSchema = {
|
|
294
|
+
"src": {
|
|
295
|
+
type: "directory",
|
|
296
|
+
required: true,
|
|
297
|
+
children: {
|
|
298
|
+
"index.ts": {
|
|
299
|
+
type: "file",
|
|
300
|
+
required: true
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
},
|
|
267
304
|
"package.json": {
|
|
268
305
|
type: "file",
|
|
269
306
|
required: true,
|
|
270
|
-
validate:
|
|
271
|
-
|
|
272
|
-
|
|
307
|
+
validate: (content: any) => {
|
|
308
|
+
try {
|
|
309
|
+
const pkg = JSON.parse(content);
|
|
310
|
+
return pkg.name && pkg.version;
|
|
311
|
+
} catch {
|
|
312
|
+
return false;
|
|
313
|
+
}
|
|
273
314
|
}
|
|
315
|
+
},
|
|
316
|
+
"*.config.js": {
|
|
317
|
+
type: "file",
|
|
318
|
+
required: false
|
|
274
319
|
}
|
|
275
320
|
};
|
|
321
|
+
|
|
322
|
+
const linter = new DirectoryLint();
|
|
323
|
+
|
|
324
|
+
try {
|
|
325
|
+
const result = await linter.validate(".", ciSchema, {
|
|
326
|
+
ignore: ["node_modules", "dist", ".git"]
|
|
327
|
+
});
|
|
328
|
+
console.log("✅ Structure validation passed!");
|
|
329
|
+
} catch (error) {
|
|
330
|
+
console.error("❌ Structure validation failed:", error.message);
|
|
331
|
+
process.exit(1);
|
|
332
|
+
}
|
|
276
333
|
```
|
|
277
334
|
|
|
278
|
-
|
|
335
|
+
## 🔌 Custom Backend
|
|
336
|
+
|
|
337
|
+
Create custom backends for different storage systems:
|
|
279
338
|
|
|
280
339
|
```typescript
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
340
|
+
import { LintBackend, LintItem } from "directory-lint";
|
|
341
|
+
|
|
342
|
+
const CustomBackend: LintBackend = {
|
|
343
|
+
getAllItems(path: string): LintItem[] {
|
|
344
|
+
// Return items in the directory
|
|
345
|
+
return [
|
|
346
|
+
{ name: "file.txt", type: "file" },
|
|
347
|
+
{ name: "folder", type: "directory" }
|
|
348
|
+
];
|
|
349
|
+
},
|
|
350
|
+
|
|
351
|
+
writeFile(path: string, content: any): void {
|
|
352
|
+
// Write file content
|
|
353
|
+
console.log(`Writing to ${path}:`, content);
|
|
354
|
+
},
|
|
355
|
+
|
|
356
|
+
readFile(path: string): any {
|
|
357
|
+
// Read file content
|
|
358
|
+
return "file content";
|
|
359
|
+
},
|
|
360
|
+
|
|
361
|
+
makeDirectory(path: string, recursive?: boolean): void {
|
|
362
|
+
// Create directory
|
|
363
|
+
console.log(`Creating directory ${path}, recursive: ${recursive}`);
|
|
364
|
+
},
|
|
365
|
+
|
|
366
|
+
exists(path: string): boolean {
|
|
367
|
+
// Check if path exists
|
|
368
|
+
return true;
|
|
369
|
+
}
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
const linter = new DirectoryLint(CustomBackend);
|
|
284
373
|
```
|
|
285
374
|
|
|
286
|
-
|
|
375
|
+
## 📖 API Reference
|
|
376
|
+
|
|
377
|
+
### `DirectoryLint`
|
|
287
378
|
|
|
379
|
+
#### Constructor
|
|
288
380
|
```typescript
|
|
289
|
-
|
|
290
|
-
overwrite: true, // Overwrite existing files
|
|
291
|
-
recursive: true // Create parent directories
|
|
292
|
-
});
|
|
381
|
+
constructor(backend?: LintBackend)
|
|
293
382
|
```
|
|
294
383
|
|
|
295
|
-
|
|
384
|
+
Creates a new DirectoryLint instance with an optional custom backend. Defaults to `FileSystemBackend`.
|
|
385
|
+
|
|
386
|
+
#### Methods
|
|
387
|
+
|
|
388
|
+
##### `generate(cwd: string, schema: GenerateSchema, options?: GenerateOptions): Promise<GenerateResult>`
|
|
389
|
+
|
|
390
|
+
Generates a directory structure based on a schema.
|
|
391
|
+
|
|
392
|
+
**Parameters:**
|
|
393
|
+
- `cwd`: Current working directory (base path)
|
|
394
|
+
- `schema`: Generation schema definition
|
|
395
|
+
- `options`: Optional generation options
|
|
396
|
+
|
|
397
|
+
**Options:**
|
|
398
|
+
- `overwrite?: boolean` - Overwrite existing files (default: false)
|
|
399
|
+
- `recursive?: boolean` - Create parent directories if they don't exist (default: false)
|
|
296
400
|
|
|
401
|
+
**Returns:**
|
|
297
402
|
```typescript
|
|
298
|
-
interface
|
|
299
|
-
|
|
300
|
-
|
|
403
|
+
interface GenerateResult {
|
|
404
|
+
cwd: string;
|
|
405
|
+
paths: Record<string, {
|
|
406
|
+
type: "file" | "directory";
|
|
301
407
|
path: string;
|
|
302
|
-
|
|
303
|
-
type: "missing" | "invalid-type" | "custom";
|
|
304
|
-
}>;
|
|
305
|
-
warnings: Array<{
|
|
306
|
-
path: string;
|
|
307
|
-
message: string;
|
|
408
|
+
children?: Record<string, ...>;
|
|
308
409
|
}>;
|
|
309
410
|
}
|
|
310
411
|
```
|
|
311
412
|
|
|
312
|
-
|
|
413
|
+
**Throws:**
|
|
414
|
+
- `RegexNotSupported` - If regex patterns are used in the schema
|
|
415
|
+
|
|
416
|
+
##### `validate(cwd: string, schema: ValidateSchema, options?: ValidateOptions): Promise<ValidateResult>`
|
|
417
|
+
|
|
418
|
+
Validates a directory structure against a schema.
|
|
419
|
+
|
|
420
|
+
**Parameters:**
|
|
421
|
+
- `cwd`: Current working directory (base path)
|
|
422
|
+
- `schema`: Validation schema definition
|
|
423
|
+
- `options`: Optional validation options
|
|
313
424
|
|
|
314
|
-
|
|
315
|
-
|
|
425
|
+
**Options:**
|
|
426
|
+
- `ignore: string[]` - Array of file/directory names to ignore during validation
|
|
316
427
|
|
|
428
|
+
**Returns:**
|
|
317
429
|
```typescript
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
console.log(`✅ Created ${name}`);
|
|
430
|
+
interface ValidateResult {
|
|
431
|
+
cwd: string;
|
|
432
|
+
paths: Record<string, {
|
|
433
|
+
type: "file" | "directory";
|
|
434
|
+
name: string;
|
|
435
|
+
path: string;
|
|
436
|
+
children?: Record<string, ...>;
|
|
437
|
+
}>;
|
|
327
438
|
}
|
|
328
439
|
```
|
|
329
440
|
|
|
330
|
-
|
|
331
|
-
|
|
441
|
+
**Throws:**
|
|
442
|
+
- `InvalidStructure` - If required items are missing or have incorrect types
|
|
443
|
+
- `InvalidContent` - If file content fails custom validation
|
|
332
444
|
|
|
333
|
-
|
|
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 });
|
|
445
|
+
### Backend Interface
|
|
340
446
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
447
|
+
```typescript
|
|
448
|
+
interface LintBackend {
|
|
449
|
+
getAllItems(path: string): LintItem[];
|
|
450
|
+
writeFile(path: string, content: any): void;
|
|
451
|
+
readFile(path: string): any;
|
|
452
|
+
makeDirectory(path: string, recursive?: boolean): void;
|
|
453
|
+
exists(path: string): boolean;
|
|
454
|
+
}
|
|
344
455
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
process.exit(1);
|
|
456
|
+
interface LintItem {
|
|
457
|
+
name: string;
|
|
458
|
+
type: "file" | "directory";
|
|
349
459
|
}
|
|
350
460
|
```
|
|
351
461
|
|
|
352
|
-
|
|
353
|
-
Ensure consistent structure across packages:
|
|
462
|
+
## 🚨 Error Handling
|
|
354
463
|
|
|
355
|
-
|
|
356
|
-
import { DirectoryLint, type LintSchema } from "directory-lint";
|
|
357
|
-
import { monorepo, nextjs, nestjs } from "directory-lint/presets";
|
|
464
|
+
Directory Lint throws specific errors for different failure scenarios:
|
|
358
465
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
children: nestjs({ withPrisma: true })
|
|
372
|
-
}
|
|
373
|
-
}
|
|
466
|
+
```typescript
|
|
467
|
+
import { InvalidStructure, InvalidContent, RegexNotSupported } from "directory-lint";
|
|
468
|
+
|
|
469
|
+
try {
|
|
470
|
+
await linter.validate("./project", schema, { ignore: [] });
|
|
471
|
+
} catch (error) {
|
|
472
|
+
if (error instanceof InvalidStructure) {
|
|
473
|
+
console.error("Structure error:", error.message);
|
|
474
|
+
} else if (error instanceof InvalidContent) {
|
|
475
|
+
console.error("Content validation failed:", error.message);
|
|
476
|
+
} else if (error instanceof RegexNotSupported) {
|
|
477
|
+
console.error("Regex not allowed in generate schema:", error.message);
|
|
374
478
|
}
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
const linter = new DirectoryLint();
|
|
378
|
-
await linter.validate(".", schema);
|
|
479
|
+
}
|
|
379
480
|
```
|
|
380
481
|
|
|
381
|
-
##
|
|
482
|
+
## 🔍 Pattern Matching Details
|
|
382
483
|
|
|
383
|
-
|
|
484
|
+
### Wildcard Patterns
|
|
384
485
|
|
|
385
|
-
|
|
386
|
-
import { LintBackend, LintItem } from "directory-lint";
|
|
486
|
+
Use `*` to match any sequence of characters:
|
|
387
487
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
488
|
+
```typescript
|
|
489
|
+
{
|
|
490
|
+
"*.ts": { type: "file" }, // Matches: file.ts, index.ts, etc.
|
|
491
|
+
"test-*": { type: "directory" } // Matches: test-unit, test-e2e, etc.
|
|
492
|
+
}
|
|
493
|
+
```
|
|
393
494
|
|
|
394
|
-
|
|
395
|
-
// Your S3 implementation
|
|
396
|
-
},
|
|
495
|
+
### Regex Patterns
|
|
397
496
|
|
|
398
|
-
|
|
399
|
-
// Your S3 implementation
|
|
400
|
-
},
|
|
497
|
+
Use `/pattern/` syntax for complex matching:
|
|
401
498
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
499
|
+
```typescript
|
|
500
|
+
{
|
|
501
|
+
"/^[a-z]+\\.config\\.(js|ts)$/": {
|
|
502
|
+
type: "file"
|
|
405
503
|
}
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
const linter = new DirectoryLint(S3Backend);
|
|
504
|
+
}
|
|
505
|
+
// Matches: vite.config.js, jest.config.ts, etc.
|
|
409
506
|
```
|
|
410
507
|
|
|
411
|
-
|
|
508
|
+
### Exact Match
|
|
412
509
|
|
|
413
|
-
|
|
510
|
+
Simple string keys match exactly:
|
|
414
511
|
|
|
415
|
-
#### Constructor
|
|
416
512
|
```typescript
|
|
417
|
-
|
|
513
|
+
{
|
|
514
|
+
"package.json": { type: "file" },
|
|
515
|
+
"src": { type: "directory" }
|
|
516
|
+
}
|
|
418
517
|
```
|
|
419
518
|
|
|
420
|
-
|
|
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.
|
|
519
|
+
## 🤝 Contributing
|
|
432
520
|
|
|
433
|
-
|
|
434
|
-
- `overwrite`: Overwrite existing files (default: false)
|
|
435
|
-
- `recursive`: Create parent directories (default: false)
|
|
521
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
436
522
|
|
|
437
|
-
|
|
523
|
+
### Development Setup
|
|
438
524
|
|
|
439
|
-
|
|
525
|
+
```bash
|
|
526
|
+
# Clone the repository
|
|
527
|
+
git clone https://github.com/yourusername/directory-lint.git
|
|
440
528
|
|
|
441
|
-
|
|
529
|
+
# Install dependencies
|
|
530
|
+
npm install
|
|
442
531
|
|
|
443
|
-
|
|
532
|
+
# Build the project
|
|
533
|
+
npm run build
|
|
444
534
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
535
|
+
# Run examples
|
|
536
|
+
npm run example:generate
|
|
537
|
+
npm run example:validate
|
|
538
|
+
```
|
|
449
539
|
|
|
450
540
|
## 📝 License
|
|
451
541
|
|
|
@@ -454,7 +544,3 @@ MIT © Henry Vilani
|
|
|
454
544
|
## 🙏 Acknowledgments
|
|
455
545
|
|
|
456
546
|
Built with ❤️ for the developer community.
|
|
457
|
-
|
|
458
|
-
---
|
|
459
|
-
|
|
460
|
-
**Keywords:** linter,validation, filesystem, directorystructure, architecture, lint
|
package/dist/index.cjs
CHANGED
|
@@ -32,26 +32,26 @@ var index_exports = {};
|
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
DirectoryLint: () => DirectoryLint,
|
|
34
34
|
FileSystemBackend: () => FileSystemBackend,
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
InvalidContent: () => InvalidContent,
|
|
36
|
+
InvalidStructure: () => InvalidStructure,
|
|
37
|
+
RegexNotSupported: () => RegexNotSupported
|
|
38
38
|
});
|
|
39
39
|
module.exports = __toCommonJS(index_exports);
|
|
40
40
|
|
|
41
|
-
// src/errors.ts
|
|
42
|
-
var
|
|
43
|
-
constructor(
|
|
44
|
-
super(
|
|
41
|
+
// src/core/errors.ts
|
|
42
|
+
var RegexNotSupported = class extends Error {
|
|
43
|
+
constructor(value) {
|
|
44
|
+
super(`${value}: Regex not supported in GenerateSchema`);
|
|
45
45
|
}
|
|
46
46
|
};
|
|
47
|
-
var
|
|
48
|
-
constructor(
|
|
49
|
-
super(
|
|
47
|
+
var InvalidStructure = class extends Error {
|
|
48
|
+
constructor(path) {
|
|
49
|
+
super(`${path}: Invalid structured`);
|
|
50
50
|
}
|
|
51
51
|
};
|
|
52
|
-
var
|
|
53
|
-
constructor(
|
|
54
|
-
super(
|
|
52
|
+
var InvalidContent = class extends Error {
|
|
53
|
+
constructor(path) {
|
|
54
|
+
super(`${path}: Invalid content`);
|
|
55
55
|
}
|
|
56
56
|
};
|
|
57
57
|
|
|
@@ -64,14 +64,17 @@ var FileSystemBackend = {
|
|
|
64
64
|
getAllItems(path) {
|
|
65
65
|
return fs.readdirSync(path, { withFileTypes: true }).map((item) => ({
|
|
66
66
|
name: item.name,
|
|
67
|
-
type: item.isDirectory() ? "
|
|
67
|
+
type: item.isDirectory() ? "directory" : "file"
|
|
68
68
|
}));
|
|
69
69
|
},
|
|
70
70
|
writeFile(path, content) {
|
|
71
|
-
fs.writeFileSync(path, content
|
|
71
|
+
fs.writeFileSync(path, content);
|
|
72
72
|
},
|
|
73
|
-
|
|
74
|
-
fs.
|
|
73
|
+
readFile(path) {
|
|
74
|
+
return fs.readFileSync(path);
|
|
75
|
+
},
|
|
76
|
+
makeDirectory(path, recursive) {
|
|
77
|
+
fs.mkdirSync(path, { recursive });
|
|
75
78
|
},
|
|
76
79
|
exists(path) {
|
|
77
80
|
return fs.existsSync(path);
|
|
@@ -83,63 +86,88 @@ var DirectoryLint = class {
|
|
|
83
86
|
constructor(backend = FileSystemBackend) {
|
|
84
87
|
this.backend = backend;
|
|
85
88
|
}
|
|
86
|
-
generate(
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
const fullPath = (0, import_path.join)(
|
|
96
|
-
if (node.type === "
|
|
89
|
+
async generate(cwd, schema, options) {
|
|
90
|
+
const result = {
|
|
91
|
+
cwd,
|
|
92
|
+
paths: {}
|
|
93
|
+
};
|
|
94
|
+
if (!this.backend.exists(cwd)) this.backend.makeDirectory(cwd, options?.recursive);
|
|
95
|
+
for (const [name, node] of Object.entries(schema)) {
|
|
96
|
+
const isRegexLiteral = name.startsWith("/") && name.endsWith("/");
|
|
97
|
+
if (isRegexLiteral) throw new RegexNotSupported(name);
|
|
98
|
+
const fullPath = (0, import_path.join)(cwd, name);
|
|
99
|
+
if (node.type === "directory") {
|
|
100
|
+
let childrenResult = void 0;
|
|
97
101
|
if (!this.backend.exists(fullPath)) {
|
|
98
|
-
this.backend.makeDirectory(fullPath);
|
|
102
|
+
this.backend.makeDirectory(fullPath, options?.recursive);
|
|
99
103
|
}
|
|
100
104
|
if (node.children) {
|
|
101
|
-
this.generate(fullPath, node.children);
|
|
105
|
+
childrenResult = await this.generate(fullPath, node.children, options);
|
|
102
106
|
}
|
|
107
|
+
result.paths[name] = {
|
|
108
|
+
type: node.type,
|
|
109
|
+
path: fullPath,
|
|
110
|
+
...childrenResult?.paths && { children: childrenResult.paths }
|
|
111
|
+
};
|
|
103
112
|
} else if (node.type === "file") {
|
|
104
113
|
if (!this.backend.exists(fullPath)) {
|
|
105
|
-
this.backend.writeFile(fullPath, "");
|
|
114
|
+
this.backend.writeFile(fullPath, node.content === void 0 ? "" : node.content);
|
|
115
|
+
} else {
|
|
116
|
+
if (options?.overwrite) {
|
|
117
|
+
this.backend.writeFile(fullPath, node.content === void 0 ? "" : node.content);
|
|
118
|
+
}
|
|
106
119
|
}
|
|
107
120
|
}
|
|
108
121
|
}
|
|
122
|
+
return result;
|
|
109
123
|
}
|
|
110
|
-
validate(
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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);
|
|
124
|
+
async validate(cwd, schema, options) {
|
|
125
|
+
const result = {
|
|
126
|
+
cwd,
|
|
127
|
+
paths: {}
|
|
128
|
+
};
|
|
129
|
+
const items = this.backend.getAllItems(cwd);
|
|
122
130
|
for (const [pattern, node] of Object.entries(schema)) {
|
|
123
131
|
const regex = this.patternToRegex(pattern);
|
|
124
132
|
const matchedItems = items.filter((item) => regex.test(item.name));
|
|
125
|
-
if (matchedItems.length === 0 && node.required !== false) throw new
|
|
133
|
+
if (matchedItems.length === 0 && node.required !== false) throw new InvalidStructure(pattern);
|
|
126
134
|
for (const { name, type } of matchedItems) {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
135
|
+
if (options?.ignore.includes(name)) continue;
|
|
136
|
+
const fullPath = (0, import_path.join)(cwd, name);
|
|
137
|
+
if (node.type === "directory") {
|
|
138
|
+
let childrenResult = void 0;
|
|
139
|
+
if (type !== "directory") throw new InvalidStructure(fullPath);
|
|
130
140
|
else if (node.children) {
|
|
131
|
-
this.
|
|
141
|
+
childrenResult = await this.validate(fullPath, node.children, options);
|
|
132
142
|
}
|
|
133
|
-
|
|
143
|
+
result.paths[name] = {
|
|
144
|
+
path: fullPath,
|
|
145
|
+
name,
|
|
146
|
+
type: node.type,
|
|
147
|
+
...childrenResult?.paths && { children: childrenResult.paths }
|
|
148
|
+
};
|
|
149
|
+
} else if (node.type === "file") {
|
|
150
|
+
if (type !== "file") throw new InvalidStructure(fullPath);
|
|
151
|
+
const content = this.backend.readFile(fullPath);
|
|
152
|
+
if (node.validate ? !node.validate(content) : false) throw new InvalidContent(fullPath);
|
|
153
|
+
}
|
|
134
154
|
}
|
|
135
155
|
}
|
|
156
|
+
return result;
|
|
157
|
+
}
|
|
158
|
+
patternToRegex(pattern) {
|
|
159
|
+
if (pattern.startsWith("/") && pattern.endsWith("/")) {
|
|
160
|
+
return new RegExp(pattern.slice(1, -1));
|
|
161
|
+
}
|
|
162
|
+
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
163
|
+
return new RegExp(`^${escaped}$`);
|
|
136
164
|
}
|
|
137
165
|
};
|
|
138
166
|
// Annotate the CommonJS export names for ESM import in node:
|
|
139
167
|
0 && (module.exports = {
|
|
140
168
|
DirectoryLint,
|
|
141
169
|
FileSystemBackend,
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
170
|
+
InvalidContent,
|
|
171
|
+
InvalidStructure,
|
|
172
|
+
RegexNotSupported
|
|
145
173
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -1,49 +1,97 @@
|
|
|
1
|
-
declare class
|
|
2
|
-
constructor(
|
|
1
|
+
declare class RegexNotSupported extends Error {
|
|
2
|
+
constructor(value: string);
|
|
3
3
|
}
|
|
4
|
-
declare class
|
|
5
|
-
constructor(
|
|
4
|
+
declare class InvalidStructure extends Error {
|
|
5
|
+
constructor(path: string);
|
|
6
6
|
}
|
|
7
|
-
declare class
|
|
8
|
-
constructor(
|
|
7
|
+
declare class InvalidContent extends Error {
|
|
8
|
+
constructor(path: string);
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
interface
|
|
11
|
+
interface ValidateFileSchema {
|
|
12
|
+
type: "file";
|
|
13
|
+
validate?: (content: any) => boolean;
|
|
14
|
+
required?: boolean;
|
|
15
|
+
}
|
|
16
|
+
interface ValidateDirectorySchema {
|
|
17
|
+
type: "directory";
|
|
18
|
+
children?: ValidateSchema;
|
|
12
19
|
required?: boolean;
|
|
13
|
-
example?: string;
|
|
14
20
|
}
|
|
15
|
-
|
|
21
|
+
type ValidateNode = ValidateFileSchema | ValidateDirectorySchema;
|
|
22
|
+
type ValidateSchema = Record<string, ValidateNode>;
|
|
23
|
+
interface GenerateFileSchema {
|
|
16
24
|
type: "file";
|
|
25
|
+
content?: any;
|
|
17
26
|
}
|
|
18
|
-
interface
|
|
19
|
-
type: "
|
|
20
|
-
children?:
|
|
27
|
+
interface GenerateDirectorySchema {
|
|
28
|
+
type: "directory";
|
|
29
|
+
children?: GenerateSchema;
|
|
21
30
|
}
|
|
22
|
-
type
|
|
23
|
-
type
|
|
24
|
-
[K: string]: (DirectoryLintNode | FileLintNode);
|
|
25
|
-
};
|
|
31
|
+
type GenerateNode = GenerateFileSchema | GenerateDirectorySchema;
|
|
32
|
+
type GenerateSchema = Record<string, GenerateNode>;
|
|
26
33
|
|
|
27
34
|
type LintItem = {
|
|
28
35
|
name: string;
|
|
29
|
-
type:
|
|
36
|
+
type: ValidateNode["type"];
|
|
30
37
|
};
|
|
38
|
+
|
|
31
39
|
interface LintBackend {
|
|
32
40
|
getAllItems(path: string): LintItem[];
|
|
33
|
-
writeFile(path: string, content:
|
|
34
|
-
|
|
41
|
+
writeFile(path: string, content: any): void;
|
|
42
|
+
readFile(path: string): any;
|
|
43
|
+
makeDirectory(path: string, recursive?: boolean): void;
|
|
35
44
|
exists(path: string): boolean;
|
|
36
45
|
}
|
|
37
46
|
|
|
47
|
+
interface GenerateOptions {
|
|
48
|
+
overwrite?: boolean;
|
|
49
|
+
recursive?: boolean;
|
|
50
|
+
}
|
|
51
|
+
interface ValidateOptions {
|
|
52
|
+
ignore: string[];
|
|
53
|
+
}
|
|
54
|
+
interface ValidateFileResult {
|
|
55
|
+
type: "file";
|
|
56
|
+
name: string;
|
|
57
|
+
path: string;
|
|
58
|
+
}
|
|
59
|
+
interface ValidateDirectoryResult {
|
|
60
|
+
type: "directory";
|
|
61
|
+
name: string;
|
|
62
|
+
path: string;
|
|
63
|
+
children?: ValidatePathResult;
|
|
64
|
+
}
|
|
65
|
+
type ValidateNodeResult = ValidateFileResult | ValidateDirectoryResult;
|
|
66
|
+
type ValidatePathResult = Record<string, ValidateNodeResult>;
|
|
67
|
+
interface ValidateResult {
|
|
68
|
+
cwd: string;
|
|
69
|
+
paths: ValidatePathResult;
|
|
70
|
+
}
|
|
71
|
+
interface GenerateFileResult {
|
|
72
|
+
type: "file";
|
|
73
|
+
path: string;
|
|
74
|
+
}
|
|
75
|
+
interface GenerateDirectoryResult {
|
|
76
|
+
type: "directory";
|
|
77
|
+
path: string;
|
|
78
|
+
children?: GeneratePathResult;
|
|
79
|
+
}
|
|
80
|
+
type GenerateNodeResult = GenerateFileResult | GenerateDirectoryResult;
|
|
81
|
+
type GeneratePathResult = Record<string, GenerateNodeResult>;
|
|
82
|
+
interface GenerateResult {
|
|
83
|
+
cwd: string;
|
|
84
|
+
paths: GeneratePathResult;
|
|
85
|
+
}
|
|
86
|
+
|
|
38
87
|
declare class DirectoryLint {
|
|
39
88
|
private readonly backend;
|
|
40
89
|
constructor(backend?: LintBackend);
|
|
41
|
-
generate(
|
|
42
|
-
validate(
|
|
90
|
+
generate(cwd: string, schema: GenerateSchema, options?: GenerateOptions): Promise<GenerateResult>;
|
|
91
|
+
validate(cwd: string, schema: ValidateSchema, options?: ValidateOptions): Promise<ValidateResult>;
|
|
43
92
|
private patternToRegex;
|
|
44
|
-
private recursiveValidate;
|
|
45
93
|
}
|
|
46
94
|
|
|
47
95
|
declare const FileSystemBackend: LintBackend;
|
|
48
96
|
|
|
49
|
-
export { DirectoryLint, FileSystemBackend,
|
|
97
|
+
export { DirectoryLint, FileSystemBackend, type GenerateNode, type GenerateOptions, type GenerateResult, type GenerateSchema, InvalidContent, InvalidStructure, type LintBackend, type LintItem, RegexNotSupported, type ValidateNode, type ValidateOptions, type ValidateResult, type ValidateSchema };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,49 +1,97 @@
|
|
|
1
|
-
declare class
|
|
2
|
-
constructor(
|
|
1
|
+
declare class RegexNotSupported extends Error {
|
|
2
|
+
constructor(value: string);
|
|
3
3
|
}
|
|
4
|
-
declare class
|
|
5
|
-
constructor(
|
|
4
|
+
declare class InvalidStructure extends Error {
|
|
5
|
+
constructor(path: string);
|
|
6
6
|
}
|
|
7
|
-
declare class
|
|
8
|
-
constructor(
|
|
7
|
+
declare class InvalidContent extends Error {
|
|
8
|
+
constructor(path: string);
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
interface
|
|
11
|
+
interface ValidateFileSchema {
|
|
12
|
+
type: "file";
|
|
13
|
+
validate?: (content: any) => boolean;
|
|
14
|
+
required?: boolean;
|
|
15
|
+
}
|
|
16
|
+
interface ValidateDirectorySchema {
|
|
17
|
+
type: "directory";
|
|
18
|
+
children?: ValidateSchema;
|
|
12
19
|
required?: boolean;
|
|
13
|
-
example?: string;
|
|
14
20
|
}
|
|
15
|
-
|
|
21
|
+
type ValidateNode = ValidateFileSchema | ValidateDirectorySchema;
|
|
22
|
+
type ValidateSchema = Record<string, ValidateNode>;
|
|
23
|
+
interface GenerateFileSchema {
|
|
16
24
|
type: "file";
|
|
25
|
+
content?: any;
|
|
17
26
|
}
|
|
18
|
-
interface
|
|
19
|
-
type: "
|
|
20
|
-
children?:
|
|
27
|
+
interface GenerateDirectorySchema {
|
|
28
|
+
type: "directory";
|
|
29
|
+
children?: GenerateSchema;
|
|
21
30
|
}
|
|
22
|
-
type
|
|
23
|
-
type
|
|
24
|
-
[K: string]: (DirectoryLintNode | FileLintNode);
|
|
25
|
-
};
|
|
31
|
+
type GenerateNode = GenerateFileSchema | GenerateDirectorySchema;
|
|
32
|
+
type GenerateSchema = Record<string, GenerateNode>;
|
|
26
33
|
|
|
27
34
|
type LintItem = {
|
|
28
35
|
name: string;
|
|
29
|
-
type:
|
|
36
|
+
type: ValidateNode["type"];
|
|
30
37
|
};
|
|
38
|
+
|
|
31
39
|
interface LintBackend {
|
|
32
40
|
getAllItems(path: string): LintItem[];
|
|
33
|
-
writeFile(path: string, content:
|
|
34
|
-
|
|
41
|
+
writeFile(path: string, content: any): void;
|
|
42
|
+
readFile(path: string): any;
|
|
43
|
+
makeDirectory(path: string, recursive?: boolean): void;
|
|
35
44
|
exists(path: string): boolean;
|
|
36
45
|
}
|
|
37
46
|
|
|
47
|
+
interface GenerateOptions {
|
|
48
|
+
overwrite?: boolean;
|
|
49
|
+
recursive?: boolean;
|
|
50
|
+
}
|
|
51
|
+
interface ValidateOptions {
|
|
52
|
+
ignore: string[];
|
|
53
|
+
}
|
|
54
|
+
interface ValidateFileResult {
|
|
55
|
+
type: "file";
|
|
56
|
+
name: string;
|
|
57
|
+
path: string;
|
|
58
|
+
}
|
|
59
|
+
interface ValidateDirectoryResult {
|
|
60
|
+
type: "directory";
|
|
61
|
+
name: string;
|
|
62
|
+
path: string;
|
|
63
|
+
children?: ValidatePathResult;
|
|
64
|
+
}
|
|
65
|
+
type ValidateNodeResult = ValidateFileResult | ValidateDirectoryResult;
|
|
66
|
+
type ValidatePathResult = Record<string, ValidateNodeResult>;
|
|
67
|
+
interface ValidateResult {
|
|
68
|
+
cwd: string;
|
|
69
|
+
paths: ValidatePathResult;
|
|
70
|
+
}
|
|
71
|
+
interface GenerateFileResult {
|
|
72
|
+
type: "file";
|
|
73
|
+
path: string;
|
|
74
|
+
}
|
|
75
|
+
interface GenerateDirectoryResult {
|
|
76
|
+
type: "directory";
|
|
77
|
+
path: string;
|
|
78
|
+
children?: GeneratePathResult;
|
|
79
|
+
}
|
|
80
|
+
type GenerateNodeResult = GenerateFileResult | GenerateDirectoryResult;
|
|
81
|
+
type GeneratePathResult = Record<string, GenerateNodeResult>;
|
|
82
|
+
interface GenerateResult {
|
|
83
|
+
cwd: string;
|
|
84
|
+
paths: GeneratePathResult;
|
|
85
|
+
}
|
|
86
|
+
|
|
38
87
|
declare class DirectoryLint {
|
|
39
88
|
private readonly backend;
|
|
40
89
|
constructor(backend?: LintBackend);
|
|
41
|
-
generate(
|
|
42
|
-
validate(
|
|
90
|
+
generate(cwd: string, schema: GenerateSchema, options?: GenerateOptions): Promise<GenerateResult>;
|
|
91
|
+
validate(cwd: string, schema: ValidateSchema, options?: ValidateOptions): Promise<ValidateResult>;
|
|
43
92
|
private patternToRegex;
|
|
44
|
-
private recursiveValidate;
|
|
45
93
|
}
|
|
46
94
|
|
|
47
95
|
declare const FileSystemBackend: LintBackend;
|
|
48
96
|
|
|
49
|
-
export { DirectoryLint, FileSystemBackend,
|
|
97
|
+
export { DirectoryLint, FileSystemBackend, type GenerateNode, type GenerateOptions, type GenerateResult, type GenerateSchema, InvalidContent, InvalidStructure, type LintBackend, type LintItem, RegexNotSupported, type ValidateNode, type ValidateOptions, type ValidateResult, type ValidateSchema };
|
package/dist/index.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
// src/errors.ts
|
|
2
|
-
var
|
|
3
|
-
constructor(
|
|
4
|
-
super(
|
|
1
|
+
// src/core/errors.ts
|
|
2
|
+
var RegexNotSupported = class extends Error {
|
|
3
|
+
constructor(value) {
|
|
4
|
+
super(`${value}: Regex not supported in GenerateSchema`);
|
|
5
5
|
}
|
|
6
6
|
};
|
|
7
|
-
var
|
|
8
|
-
constructor(
|
|
9
|
-
super(
|
|
7
|
+
var InvalidStructure = class extends Error {
|
|
8
|
+
constructor(path) {
|
|
9
|
+
super(`${path}: Invalid structured`);
|
|
10
10
|
}
|
|
11
11
|
};
|
|
12
|
-
var
|
|
13
|
-
constructor(
|
|
14
|
-
super(
|
|
12
|
+
var InvalidContent = class extends Error {
|
|
13
|
+
constructor(path) {
|
|
14
|
+
super(`${path}: Invalid content`);
|
|
15
15
|
}
|
|
16
16
|
};
|
|
17
17
|
|
|
@@ -24,14 +24,17 @@ var FileSystemBackend = {
|
|
|
24
24
|
getAllItems(path) {
|
|
25
25
|
return fs.readdirSync(path, { withFileTypes: true }).map((item) => ({
|
|
26
26
|
name: item.name,
|
|
27
|
-
type: item.isDirectory() ? "
|
|
27
|
+
type: item.isDirectory() ? "directory" : "file"
|
|
28
28
|
}));
|
|
29
29
|
},
|
|
30
30
|
writeFile(path, content) {
|
|
31
|
-
fs.writeFileSync(path, content
|
|
31
|
+
fs.writeFileSync(path, content);
|
|
32
32
|
},
|
|
33
|
-
|
|
34
|
-
fs.
|
|
33
|
+
readFile(path) {
|
|
34
|
+
return fs.readFileSync(path);
|
|
35
|
+
},
|
|
36
|
+
makeDirectory(path, recursive) {
|
|
37
|
+
fs.mkdirSync(path, { recursive });
|
|
35
38
|
},
|
|
36
39
|
exists(path) {
|
|
37
40
|
return fs.existsSync(path);
|
|
@@ -43,62 +46,87 @@ var DirectoryLint = class {
|
|
|
43
46
|
constructor(backend = FileSystemBackend) {
|
|
44
47
|
this.backend = backend;
|
|
45
48
|
}
|
|
46
|
-
generate(
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const fullPath = join(
|
|
56
|
-
if (node.type === "
|
|
49
|
+
async generate(cwd, schema, options) {
|
|
50
|
+
const result = {
|
|
51
|
+
cwd,
|
|
52
|
+
paths: {}
|
|
53
|
+
};
|
|
54
|
+
if (!this.backend.exists(cwd)) this.backend.makeDirectory(cwd, options?.recursive);
|
|
55
|
+
for (const [name, node] of Object.entries(schema)) {
|
|
56
|
+
const isRegexLiteral = name.startsWith("/") && name.endsWith("/");
|
|
57
|
+
if (isRegexLiteral) throw new RegexNotSupported(name);
|
|
58
|
+
const fullPath = join(cwd, name);
|
|
59
|
+
if (node.type === "directory") {
|
|
60
|
+
let childrenResult = void 0;
|
|
57
61
|
if (!this.backend.exists(fullPath)) {
|
|
58
|
-
this.backend.makeDirectory(fullPath);
|
|
62
|
+
this.backend.makeDirectory(fullPath, options?.recursive);
|
|
59
63
|
}
|
|
60
64
|
if (node.children) {
|
|
61
|
-
this.generate(fullPath, node.children);
|
|
65
|
+
childrenResult = await this.generate(fullPath, node.children, options);
|
|
62
66
|
}
|
|
67
|
+
result.paths[name] = {
|
|
68
|
+
type: node.type,
|
|
69
|
+
path: fullPath,
|
|
70
|
+
...childrenResult?.paths && { children: childrenResult.paths }
|
|
71
|
+
};
|
|
63
72
|
} else if (node.type === "file") {
|
|
64
73
|
if (!this.backend.exists(fullPath)) {
|
|
65
|
-
this.backend.writeFile(fullPath, "");
|
|
74
|
+
this.backend.writeFile(fullPath, node.content === void 0 ? "" : node.content);
|
|
75
|
+
} else {
|
|
76
|
+
if (options?.overwrite) {
|
|
77
|
+
this.backend.writeFile(fullPath, node.content === void 0 ? "" : node.content);
|
|
78
|
+
}
|
|
66
79
|
}
|
|
67
80
|
}
|
|
68
81
|
}
|
|
82
|
+
return result;
|
|
69
83
|
}
|
|
70
|
-
validate(
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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);
|
|
84
|
+
async validate(cwd, schema, options) {
|
|
85
|
+
const result = {
|
|
86
|
+
cwd,
|
|
87
|
+
paths: {}
|
|
88
|
+
};
|
|
89
|
+
const items = this.backend.getAllItems(cwd);
|
|
82
90
|
for (const [pattern, node] of Object.entries(schema)) {
|
|
83
91
|
const regex = this.patternToRegex(pattern);
|
|
84
92
|
const matchedItems = items.filter((item) => regex.test(item.name));
|
|
85
|
-
if (matchedItems.length === 0 && node.required !== false) throw new
|
|
93
|
+
if (matchedItems.length === 0 && node.required !== false) throw new InvalidStructure(pattern);
|
|
86
94
|
for (const { name, type } of matchedItems) {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
95
|
+
if (options?.ignore.includes(name)) continue;
|
|
96
|
+
const fullPath = join(cwd, name);
|
|
97
|
+
if (node.type === "directory") {
|
|
98
|
+
let childrenResult = void 0;
|
|
99
|
+
if (type !== "directory") throw new InvalidStructure(fullPath);
|
|
90
100
|
else if (node.children) {
|
|
91
|
-
this.
|
|
101
|
+
childrenResult = await this.validate(fullPath, node.children, options);
|
|
92
102
|
}
|
|
93
|
-
|
|
103
|
+
result.paths[name] = {
|
|
104
|
+
path: fullPath,
|
|
105
|
+
name,
|
|
106
|
+
type: node.type,
|
|
107
|
+
...childrenResult?.paths && { children: childrenResult.paths }
|
|
108
|
+
};
|
|
109
|
+
} else if (node.type === "file") {
|
|
110
|
+
if (type !== "file") throw new InvalidStructure(fullPath);
|
|
111
|
+
const content = this.backend.readFile(fullPath);
|
|
112
|
+
if (node.validate ? !node.validate(content) : false) throw new InvalidContent(fullPath);
|
|
113
|
+
}
|
|
94
114
|
}
|
|
95
115
|
}
|
|
116
|
+
return result;
|
|
117
|
+
}
|
|
118
|
+
patternToRegex(pattern) {
|
|
119
|
+
if (pattern.startsWith("/") && pattern.endsWith("/")) {
|
|
120
|
+
return new RegExp(pattern.slice(1, -1));
|
|
121
|
+
}
|
|
122
|
+
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
123
|
+
return new RegExp(`^${escaped}$`);
|
|
96
124
|
}
|
|
97
125
|
};
|
|
98
126
|
export {
|
|
99
127
|
DirectoryLint,
|
|
100
128
|
FileSystemBackend,
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
129
|
+
InvalidContent,
|
|
130
|
+
InvalidStructure,
|
|
131
|
+
RegexNotSupported
|
|
104
132
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "directory-lint",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Directory Lint",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Henry Vilani",
|
|
@@ -31,10 +31,8 @@
|
|
|
31
31
|
],
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@types/node": "^25.2.0",
|
|
34
|
+
"ts-node": "^10.9.2",
|
|
34
35
|
"tsup": "^8.5.1",
|
|
35
36
|
"typescript": "^5.9.3"
|
|
36
|
-
},
|
|
37
|
-
"dependencies": {
|
|
38
|
-
"ts-node": "^10.9.2"
|
|
39
37
|
}
|
|
40
38
|
}
|