archforge-x 1.0.2 → 1.0.4
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 +156 -56
- package/dist/cli/commands/sync.js +22 -0
- package/dist/cli/interactive.js +41 -3
- package/dist/core/architecture/schema.js +23 -2
- package/dist/core/architecture/validator.js +112 -0
- package/dist/generators/base.js +166 -0
- package/dist/generators/generator.js +35 -332
- package/dist/generators/go/gin.js +327 -0
- package/dist/generators/node/express.js +920 -0
- package/dist/generators/node/nestjs.js +770 -0
- package/dist/generators/node/nextjs.js +252 -0
- package/dist/generators/python/django.js +327 -0
- package/dist/generators/python/fastapi.js +309 -0
- package/dist/generators/registry.js +25 -0
- package/dist/index.js +29 -15
- package/package.json +3 -1
- package/dist/cli/init.js +0 -74
package/README.md
CHANGED
|
@@ -1,90 +1,190 @@
|
|
|
1
|
-
#
|
|
1
|
+
# ArchForge X
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A configuration-driven architecture engine for generating and enforcing production-grade project structures across multiple languages and frameworks.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Why This Exists
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Modern software development often suffers from two primary issues: **setup fatigue** and **architecture drift**. Developers spend hours configuring boilerplate for logging, caching, and database layers, only for the project's architectural boundaries to erode over time as the team grows.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
- **🔍 Dependency Analysis**: Detect architectural violations and circular dependencies between layers.
|
|
11
|
-
- **📊 Visual Graphs**: Generate high-resolution SVG dependency graphs to visualize your architecture.
|
|
12
|
-
- **🛡️ Rule Enforcement**: Define custom architectural rules in `archforge.yaml` to ensure your team follows the intended design.
|
|
13
|
-
- **✨ Interactive CLI**: A user-friendly wizard to get you started in seconds.
|
|
9
|
+
ArchForge X solves this by moving architecture from "documentation" to "executable configuration." By defining your project's structure and rules in a central `archforge.yaml`, you ensure that every project starts with production-ready defaults and stays compliant throughout its lifecycle.
|
|
14
10
|
|
|
15
|
-
##
|
|
11
|
+
## Key Features
|
|
12
|
+
|
|
13
|
+
- **Config-Driven Generation**: Generate entire project skeletons from a single YAML definition.
|
|
14
|
+
- **Architecture Enforcement**: Automatically validate naming conventions and import boundaries.
|
|
15
|
+
- **Production-Ready Defaults**: Built-in support for structured logging, health checks, and global error handling.
|
|
16
|
+
- **Multi-Stack Support**: Consistent patterns across Node.js, Python, and Go.
|
|
17
|
+
- **Zero-Drift Sync**: Re-apply architectural rules to existing projects without losing custom logic.
|
|
18
|
+
|
|
19
|
+
## Supported Capabilities
|
|
20
|
+
|
|
21
|
+
- **Architectures**: Clean Architecture (DDD), Layered Architecture, MVC.
|
|
22
|
+
- **Frameworks**: NestJS, Express, FastAPI, Django, Go Gin, Next.js.
|
|
23
|
+
- **ORMs & Databases**: Prisma, SQLAlchemy, GORM, PostgreSQL, MySQL, SQLite, MongoDB.
|
|
24
|
+
- **DevOps**: Automated Dockerfile and Docker Compose generation, GitHub Actions CI/CD workflows.
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
Install ArchForge X globally via npm:
|
|
16
29
|
|
|
17
30
|
```bash
|
|
18
|
-
npm install -g archforge
|
|
31
|
+
npm install -g archforge
|
|
19
32
|
```
|
|
20
33
|
|
|
21
|
-
##
|
|
34
|
+
## CLI Commands
|
|
35
|
+
|
|
36
|
+
### init
|
|
37
|
+
|
|
38
|
+
The `init` command starts an interactive wizard to bootstrap a new project.
|
|
22
39
|
|
|
23
|
-
### 1. Initialize a Project
|
|
24
|
-
Create a new architecture configuration file:
|
|
25
40
|
```bash
|
|
26
41
|
archforge init
|
|
27
42
|
```
|
|
28
43
|
|
|
29
|
-
|
|
30
|
-
|
|
44
|
+
During initialization, you will select:
|
|
45
|
+
- **Architecture Style**: Choose between Clean, Layered, or MVC.
|
|
46
|
+
- **Framework**: Select your preferred backend or frontend framework.
|
|
47
|
+
- **ORM & Database**: Configure your data persistence layer.
|
|
48
|
+
- **Optional Modules**: Add Docker support, CI/CD workflows, Caching (Redis), and more.
|
|
49
|
+
|
|
50
|
+
### sync
|
|
51
|
+
|
|
52
|
+
The `sync` command re-applies the architectural rules defined in `archforge.yaml` to your project.
|
|
53
|
+
|
|
31
54
|
```bash
|
|
32
|
-
archforge
|
|
55
|
+
archforge sync
|
|
33
56
|
```
|
|
34
57
|
|
|
35
|
-
|
|
36
|
-
|
|
58
|
+
- **What it changes**: Updates configuration files, architecture guardrails (e.g., ESLint rules), and missing infrastructure layers.
|
|
59
|
+
- **What it never changes**: Your business logic, use cases, or custom implementation files.
|
|
60
|
+
|
|
61
|
+
### validate
|
|
62
|
+
|
|
63
|
+
The `validate` command checks your project against the rules defined in `archforge.yaml`.
|
|
64
|
+
|
|
37
65
|
```bash
|
|
38
|
-
archforge
|
|
66
|
+
archforge validate
|
|
39
67
|
```
|
|
40
68
|
|
|
41
|
-
|
|
42
|
-
|
|
69
|
+
- **Validation**: Checks naming conventions, directory structure, and restricted import paths.
|
|
70
|
+
- **Usage**: Used locally during development or in CI pipelines to prevent architectural violations.
|
|
71
|
+
- **Behavior**: Returns a non-zero exit code on failure, making it ideal for blocking invalid PRs.
|
|
72
|
+
|
|
73
|
+
### upgrade
|
|
74
|
+
|
|
75
|
+
The `upgrade` command adds new production-grade features to an existing ArchForge project.
|
|
76
|
+
|
|
43
77
|
```bash
|
|
44
|
-
archforge
|
|
78
|
+
archforge upgrade
|
|
45
79
|
```
|
|
46
80
|
|
|
47
|
-
|
|
48
|
-
|
|
81
|
+
- **Purpose**: Safely introduces enhancements like new logging providers or health check endpoints without breaking existing code.
|
|
82
|
+
|
|
83
|
+
### generate
|
|
84
|
+
|
|
85
|
+
The `generate` command creates specific architectural components within an existing project.
|
|
86
|
+
|
|
49
87
|
```bash
|
|
50
|
-
archforge
|
|
88
|
+
archforge generate module user
|
|
51
89
|
```
|
|
52
90
|
|
|
53
|
-
|
|
91
|
+
- **Usage**: Use this to quickly add new features, layers, or modules that follow the project's established patterns.
|
|
92
|
+
- **Alias**: `create`
|
|
93
|
+
|
|
94
|
+
### graph
|
|
54
95
|
|
|
55
|
-
|
|
96
|
+
The `graph` command generates a visual representation of your project's architecture.
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
archforge graph
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
- **Output**: Generates an `architecture-graph.svg` file showing the relationships and dependencies between your project's layers.
|
|
103
|
+
- **Alias**: `visualize`
|
|
104
|
+
|
|
105
|
+
## Configuration (archforge.yaml)
|
|
106
|
+
|
|
107
|
+
The `archforge.yaml` file is the single source of truth for your project's architecture. It defines the structure, naming conventions, and enforcement policies.
|
|
56
108
|
|
|
57
109
|
```yaml
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
110
|
+
name: my-production-app
|
|
111
|
+
architecture: clean
|
|
112
|
+
framework: nestjs
|
|
113
|
+
orm: prisma
|
|
114
|
+
database: postgresql
|
|
115
|
+
|
|
116
|
+
structure:
|
|
117
|
+
layers:
|
|
118
|
+
- name: domain
|
|
119
|
+
path: src/domain
|
|
120
|
+
canImport: []
|
|
121
|
+
- name: application
|
|
122
|
+
path: src/application
|
|
123
|
+
canImport: [domain]
|
|
124
|
+
- name: infrastructure
|
|
125
|
+
path: src/infrastructure
|
|
126
|
+
canImport: [domain, application]
|
|
127
|
+
- name: presentation
|
|
128
|
+
path: src/presentation
|
|
129
|
+
canImport: [domain, application]
|
|
130
|
+
|
|
131
|
+
naming:
|
|
132
|
+
controllers: "{name}.controller.ts"
|
|
133
|
+
useCases: "{name}.use-case.ts"
|
|
134
|
+
repositories: "{name}.repository.ts"
|
|
135
|
+
|
|
136
|
+
enforcement:
|
|
137
|
+
strictImports: true
|
|
138
|
+
namingConvention: pascalCase
|
|
82
139
|
```
|
|
83
140
|
|
|
84
|
-
##
|
|
141
|
+
## Rule Enforcement & Verification
|
|
142
|
+
|
|
143
|
+
ArchForge X enforces rules through a combination of static analysis and framework-specific configurations:
|
|
144
|
+
|
|
145
|
+
1. **Local Enforcement**: During `sync`, ArchForge configures tools like ESLint with `import/no-restricted-paths` to provide real-time feedback in your IDE.
|
|
146
|
+
2. **CI Verification**: Running `archforge validate` in your CI pipeline ensures that no code violating the architecture is merged.
|
|
147
|
+
3. **Fixing Violations**: If a violation is detected (e.g., a Domain entity importing from Infrastructure), the CLI will provide a detailed report pointing to the offending file and rule.
|
|
148
|
+
|
|
149
|
+
## Running the Project
|
|
150
|
+
|
|
151
|
+
### Local Development
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
# Install dependencies
|
|
155
|
+
npm install
|
|
156
|
+
|
|
157
|
+
# Start in development mode with hot-reload
|
|
158
|
+
npm run start:dev
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Docker-Based Development
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
# Build and start all services (App, Database, Redis)
|
|
165
|
+
docker-compose up --build
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Environment Variables
|
|
169
|
+
|
|
170
|
+
ArchForge generates a `.env.example` file. Copy it to `.env` and configure your secrets:
|
|
171
|
+
- `PORT`: Server port (default: 3000)
|
|
172
|
+
- `DATABASE_URL`: Connection string for your database.
|
|
173
|
+
- `REDIS_URL`: Connection string for Redis caching.
|
|
174
|
+
|
|
175
|
+
## Team & Company Usage
|
|
176
|
+
|
|
177
|
+
ArchForge X is designed for engineering organizations to:
|
|
178
|
+
- **Standardize**: Ensure every microservice or project follows the same structural patterns.
|
|
179
|
+
- **Onboard**: New developers can navigate any project instantly because the structure is predictable.
|
|
180
|
+
- **Scale**: Enforce best practices across hundreds of repositories without manual code reviews for "folder structure."
|
|
181
|
+
|
|
182
|
+
## Design Philosophy
|
|
85
183
|
|
|
86
|
-
|
|
184
|
+
- **Enforcement over Documentation**: Rules that aren't enforced will eventually be broken.
|
|
185
|
+
- **Production-Ready by Default**: Every project starts with the scaffolding needed for a real-world deployment.
|
|
186
|
+
- **Config-Driven Flexibility**: The tool adapts to your team's preferred patterns, not the other way around.
|
|
87
187
|
|
|
88
|
-
##
|
|
188
|
+
## Contributing & License
|
|
89
189
|
|
|
90
|
-
|
|
190
|
+
ArchForge X is open-source software licensed under the MIT License. Contributions are welcome via GitHub Pull Requests.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.syncCommand = syncCommand;
|
|
7
|
+
const validator_1 = require("../../core/architecture/validator");
|
|
8
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
+
async function syncCommand() {
|
|
10
|
+
const projectRoot = process.cwd();
|
|
11
|
+
const validator = new validator_1.ArchitectureValidator();
|
|
12
|
+
console.log(chalk_1.default.blue("🔄 Syncing architecture with archforge.yaml..."));
|
|
13
|
+
const result = await validator.validate(projectRoot);
|
|
14
|
+
if (result.success) {
|
|
15
|
+
console.log(chalk_1.default.green.bold("✅ Architecture is valid and in sync!"));
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
console.log(chalk_1.default.red.bold("❌ Architecture violations found:"));
|
|
19
|
+
result.errors.forEach(err => console.log(chalk_1.default.red(` - ${err}`)));
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
}
|
package/dist/cli/interactive.js
CHANGED
|
@@ -52,13 +52,47 @@ async function interactiveCLI() {
|
|
|
52
52
|
{ title: "Hexagonal Architecture", value: "hexagonal" },
|
|
53
53
|
],
|
|
54
54
|
});
|
|
55
|
+
const ormResponse = await (0, prompts_1.default)({
|
|
56
|
+
type: "select",
|
|
57
|
+
name: "orm",
|
|
58
|
+
message: "Select ORM:",
|
|
59
|
+
choices: langResponse.language === "ts" || langResponse.language === "js"
|
|
60
|
+
? [
|
|
61
|
+
{ title: "Prisma", value: "prisma" },
|
|
62
|
+
{ title: "TypeORM", value: "typeorm" },
|
|
63
|
+
{ title: "Sequelize", value: "sequelize" },
|
|
64
|
+
{ title: "Drizzle", value: "drizzle" },
|
|
65
|
+
{ title: "None", value: "none" },
|
|
66
|
+
]
|
|
67
|
+
: langResponse.language === "py"
|
|
68
|
+
? [
|
|
69
|
+
{ title: "SQLAlchemy", value: "sqlalchemy" },
|
|
70
|
+
{ title: "Django ORM", value: "django" },
|
|
71
|
+
{ title: "None", value: "none" },
|
|
72
|
+
]
|
|
73
|
+
: [
|
|
74
|
+
{ title: "GORM", value: "gorm" },
|
|
75
|
+
{ title: "SQLC", value: "sqlc" },
|
|
76
|
+
{ title: "None", value: "none" },
|
|
77
|
+
],
|
|
78
|
+
});
|
|
79
|
+
const dbResponse = await (0, prompts_1.default)({
|
|
80
|
+
type: ormResponse.orm === "none" ? null : "select",
|
|
81
|
+
name: "database",
|
|
82
|
+
message: "Select Database:",
|
|
83
|
+
choices: [
|
|
84
|
+
{ title: "PostgreSQL", value: "postgresql" },
|
|
85
|
+
{ title: "MySQL", value: "mysql" },
|
|
86
|
+
{ title: "SQLite", value: "sqlite" },
|
|
87
|
+
{ title: "MongoDB", value: "mongodb" },
|
|
88
|
+
],
|
|
89
|
+
});
|
|
55
90
|
const modulesResponse = await (0, prompts_1.default)({
|
|
56
91
|
type: "multiselect",
|
|
57
92
|
name: "modules",
|
|
58
93
|
message: "Select optional modules:",
|
|
59
94
|
choices: [
|
|
60
95
|
{ title: "Authentication (JWT)", value: "auth" },
|
|
61
|
-
{ title: "Database (ORM setup)", value: "db" },
|
|
62
96
|
{ title: "Caching (Redis)", value: "cache" },
|
|
63
97
|
{ title: "Docker Support", value: "docker" },
|
|
64
98
|
{ title: "CI/CD (GitHub Actions)", value: "ci" },
|
|
@@ -73,12 +107,14 @@ async function interactiveCLI() {
|
|
|
73
107
|
});
|
|
74
108
|
console.log(chalk_1.default.yellow("\n🔧 Generating project..."));
|
|
75
109
|
const options = {
|
|
76
|
-
mode: 'professional',
|
|
110
|
+
// mode: 'professional',
|
|
77
111
|
language: langResponse.language,
|
|
78
112
|
framework: frameworkResponse.framework,
|
|
79
113
|
architecture: archResponse.architecture,
|
|
80
114
|
modules: modulesResponse.modules,
|
|
81
115
|
projectName: projectNameResp.projectName,
|
|
116
|
+
orm: ormResponse.orm,
|
|
117
|
+
database: dbResponse.database,
|
|
82
118
|
};
|
|
83
119
|
// Create a dummy arch definition for the generator
|
|
84
120
|
const dummyArch = {
|
|
@@ -92,7 +128,9 @@ async function interactiveCLI() {
|
|
|
92
128
|
type: archResponse.architecture,
|
|
93
129
|
language: langResponse.language,
|
|
94
130
|
framework: frameworkResponse.framework,
|
|
95
|
-
modules: modulesResponse.modules
|
|
131
|
+
modules: modulesResponse.modules,
|
|
132
|
+
orm: ormResponse.orm,
|
|
133
|
+
database: dbResponse.database
|
|
96
134
|
},
|
|
97
135
|
layers: []
|
|
98
136
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.architectureSchema = exports.metadataSchema = exports.layerSchema = void 0;
|
|
3
|
+
exports.architectureSchema = exports.customStructureSchema = exports.rulesSchema = exports.namingSchema = exports.metadataSchema = exports.layerSchema = void 0;
|
|
4
4
|
// src/core/architecture/schema.ts
|
|
5
5
|
const zod_1 = require("zod");
|
|
6
6
|
exports.layerSchema = zod_1.z.object({
|
|
@@ -9,15 +9,33 @@ exports.layerSchema = zod_1.z.object({
|
|
|
9
9
|
description: zod_1.z.string().optional(),
|
|
10
10
|
allowedImports: zod_1.z.array(zod_1.z.string()).optional(),
|
|
11
11
|
forbiddenImports: zod_1.z.array(zod_1.z.string()).optional(),
|
|
12
|
+
canImport: zod_1.z.array(zod_1.z.string()).optional(),
|
|
12
13
|
strict: zod_1.z.boolean().optional(),
|
|
13
14
|
});
|
|
14
15
|
exports.metadataSchema = zod_1.z.object({
|
|
15
|
-
type: zod_1.z.enum(['clean', 'ddd', 'layered', 'hexagonal', 'microservices']),
|
|
16
|
+
type: zod_1.z.enum(['clean', 'ddd', 'layered', 'hexagonal', 'microservices', 'mvc']),
|
|
16
17
|
language: zod_1.z.enum(['ts', 'js', 'py', 'go']),
|
|
17
18
|
framework: zod_1.z.string().optional(),
|
|
19
|
+
orm: zod_1.z.string().optional(),
|
|
20
|
+
database: zod_1.z.string().optional(),
|
|
18
21
|
modules: zod_1.z.array(zod_1.z.string()).optional(),
|
|
19
22
|
version: zod_1.z.string().optional(),
|
|
20
23
|
});
|
|
24
|
+
exports.namingSchema = zod_1.z.object({
|
|
25
|
+
services: zod_1.z.string().optional(),
|
|
26
|
+
repositories: zod_1.z.string().optional(),
|
|
27
|
+
controllers: zod_1.z.string().optional(),
|
|
28
|
+
entities: zod_1.z.string().optional(),
|
|
29
|
+
});
|
|
30
|
+
exports.rulesSchema = zod_1.z.object({
|
|
31
|
+
forbidDirectDbAccess: zod_1.z.boolean().optional(),
|
|
32
|
+
forbidFrameworkInDomain: zod_1.z.boolean().optional(),
|
|
33
|
+
enforceLayerBoundaries: zod_1.z.boolean().optional(),
|
|
34
|
+
});
|
|
35
|
+
exports.customStructureSchema = zod_1.z.object({
|
|
36
|
+
enabled: zod_1.z.boolean(),
|
|
37
|
+
folders: zod_1.z.array(zod_1.z.object({ path: zod_1.z.string() })),
|
|
38
|
+
});
|
|
21
39
|
exports.architectureSchema = zod_1.z.object({
|
|
22
40
|
version: zod_1.z.string().default("1.0"),
|
|
23
41
|
name: zod_1.z.string(),
|
|
@@ -28,5 +46,8 @@ exports.architectureSchema = zod_1.z.object({
|
|
|
28
46
|
}),
|
|
29
47
|
metadata: exports.metadataSchema,
|
|
30
48
|
layers: zod_1.z.array(exports.layerSchema),
|
|
49
|
+
naming: exports.namingSchema.optional(),
|
|
50
|
+
rules: exports.rulesSchema.optional(),
|
|
51
|
+
customStructure: exports.customStructureSchema.optional(),
|
|
31
52
|
strict: zod_1.z.boolean().optional(),
|
|
32
53
|
});
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ArchitectureValidator = void 0;
|
|
7
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const js_yaml_1 = __importDefault(require("js-yaml"));
|
|
10
|
+
class ArchitectureValidator {
|
|
11
|
+
async validate(projectRoot) {
|
|
12
|
+
const configPath = path_1.default.join(projectRoot, "archforge.yaml");
|
|
13
|
+
if (!fs_extra_1.default.existsSync(configPath)) {
|
|
14
|
+
return { success: false, errors: ["archforge.yaml not found"], warnings: [] };
|
|
15
|
+
}
|
|
16
|
+
const config = js_yaml_1.default.load(fs_extra_1.default.readFileSync(configPath, "utf8"));
|
|
17
|
+
const errors = [];
|
|
18
|
+
const warnings = [];
|
|
19
|
+
// 1. Validate Folder Structure
|
|
20
|
+
if (config.layers) {
|
|
21
|
+
for (const [name, layer] of Object.entries(config.layers)) {
|
|
22
|
+
const layerPath = path_1.default.join(projectRoot, layer.path);
|
|
23
|
+
if (!fs_extra_1.default.existsSync(layerPath)) {
|
|
24
|
+
errors.push(`Layer "${name}" path not found: ${layer.path}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
// 2. Validate Custom Folders
|
|
29
|
+
if (config.customStructure?.enabled && config.customStructure.folders) {
|
|
30
|
+
for (const folder of config.customStructure.folders) {
|
|
31
|
+
const folderPath = path_1.default.join(projectRoot, folder.path);
|
|
32
|
+
if (!fs_extra_1.default.existsSync(folderPath)) {
|
|
33
|
+
errors.push(`Custom folder not found: ${folder.path}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
// 3. Validate Naming Conventions
|
|
38
|
+
if (config.naming) {
|
|
39
|
+
this.validateNamingConventions(projectRoot, config.naming, errors);
|
|
40
|
+
}
|
|
41
|
+
// 4. Validate Layer Imports (Basic check for forbidden imports in files)
|
|
42
|
+
if (config.layers && config.rules?.enforceLayerBoundaries) {
|
|
43
|
+
this.validateLayerImports(projectRoot, config.layers, errors);
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
success: errors.length === 0,
|
|
47
|
+
errors,
|
|
48
|
+
warnings
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
validateNamingConventions(root, naming, errors) {
|
|
52
|
+
// Simple check: scan src and verify file suffixes
|
|
53
|
+
const scanDir = (dir) => {
|
|
54
|
+
if (!fs_extra_1.default.existsSync(dir))
|
|
55
|
+
return;
|
|
56
|
+
const files = fs_extra_1.default.readdirSync(dir);
|
|
57
|
+
for (const file of files) {
|
|
58
|
+
const fullPath = path_1.default.join(dir, file);
|
|
59
|
+
if (fs_extra_1.default.statSync(fullPath).isDirectory()) {
|
|
60
|
+
scanDir(fullPath);
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
const lowerFile = file.toLowerCase();
|
|
64
|
+
if (dir.includes("services") && naming.services) {
|
|
65
|
+
const pattern = naming.services.replace("*", "").toLowerCase();
|
|
66
|
+
if (!lowerFile.includes(pattern)) {
|
|
67
|
+
errors.push(`Naming violation: Service file "${file}" does not match pattern "${naming.services}"`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (dir.includes("repositories") && naming.repositories) {
|
|
71
|
+
const pattern = naming.repositories.replace("*", "").toLowerCase();
|
|
72
|
+
if (!lowerFile.includes(pattern)) {
|
|
73
|
+
errors.push(`Naming violation: Repository file "${file}" does not match pattern "${naming.repositories}"`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
scanDir(path_1.default.join(root, "src"));
|
|
80
|
+
}
|
|
81
|
+
validateLayerImports(root, layers, errors) {
|
|
82
|
+
// This is a complex task for a simple validator, but we can do a basic grep-like check
|
|
83
|
+
// for forbidden layer paths in files of other layers.
|
|
84
|
+
for (const [name, layer] of Object.entries(layers)) {
|
|
85
|
+
const layerPath = path_1.default.join(root, layer.path);
|
|
86
|
+
if (!fs_extra_1.default.existsSync(layerPath))
|
|
87
|
+
continue;
|
|
88
|
+
const canImport = layer.canImport || [];
|
|
89
|
+
const forbiddenLayers = Object.keys(layers).filter(l => l !== name && !canImport.includes(l));
|
|
90
|
+
const scanFiles = (dir) => {
|
|
91
|
+
const files = fs_extra_1.default.readdirSync(dir);
|
|
92
|
+
for (const file of files) {
|
|
93
|
+
const fullPath = path_1.default.join(dir, file);
|
|
94
|
+
if (fs_extra_1.default.statSync(fullPath).isDirectory()) {
|
|
95
|
+
scanFiles(fullPath);
|
|
96
|
+
}
|
|
97
|
+
else if (file.endsWith(".ts") || file.endsWith(".py") || file.endsWith(".go")) {
|
|
98
|
+
const content = fs_extra_1.default.readFileSync(fullPath, "utf8");
|
|
99
|
+
for (const forbidden of forbiddenLayers) {
|
|
100
|
+
const forbiddenPath = layers[forbidden].path.replace("src/", "");
|
|
101
|
+
if (content.includes(forbiddenPath) && !content.includes(`from .`)) {
|
|
102
|
+
errors.push(`Architecture violation: Layer "${name}" (${file}) imports from forbidden layer "${forbidden}"`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
scanFiles(layerPath);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
exports.ArchitectureValidator = ArchitectureValidator;
|