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 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
+ [![npm version](https://img.shields.io/npm/v/directory-lint.svg)](https://www.npmjs.com/package/directory-lint)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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
+ });
@@ -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 };
@@ -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
+ }