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 CHANGED
@@ -1,90 +1,190 @@
1
- # 🛡️ ArchForge X
1
+ # ArchForge X
2
2
 
3
- **Enterprise Architecture Engine** for scaffolding, analyzing, and visualizing software architecture.
3
+ A configuration-driven architecture engine for generating and enforcing production-grade project structures across multiple languages and frameworks.
4
4
 
5
- ArchForge X helps development teams maintain clean, consistent, and scalable codebases by enforcing architectural boundaries and providing intelligent scaffolding.
5
+ ## Why This Exists
6
6
 
7
- ## 🚀 Features
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
- - **🏗️ Intelligent Scaffolding**: Generate project structures for TypeScript, JavaScript, Python, and Go using patterns like Clean Architecture, DDD, and Hexagonal Architecture.
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
- ## 📦 Installation
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-x
31
+ npm install -g archforge
19
32
  ```
20
33
 
21
- ## 🛠️ Usage
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
- ### 2. Interactive Wizard (Recommended)
30
- Scaffold a new project with a guided setup:
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 interactive
55
+ archforge sync
33
56
  ```
34
57
 
35
- ### 3. Analyze Dependencies
36
- Check your project for architectural violations:
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 analyze
66
+ archforge validate
39
67
  ```
40
68
 
41
- ### 4. Visualize Architecture
42
- Generate an SVG graph of your project's layers:
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 visualize -o architecture.svg
78
+ archforge upgrade
45
79
  ```
46
80
 
47
- ### 5. Deep Audit
48
- Get intelligent fix suggestions based on architectural principles:
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 audit
88
+ archforge generate module user
51
89
  ```
52
90
 
53
- ## ⚙️ Configuration (`archforge.yaml`)
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
- Define your layers and dependency rules:
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
- version: "1.0"
59
- name: "My Project Architecture"
60
- project:
61
- name: "my-app"
62
- root: "."
63
-
64
- metadata:
65
- type: "clean"
66
- language: "ts"
67
- framework: "express"
68
-
69
- layers:
70
- - name: "domain"
71
- path: "src/domain"
72
- description: "Core business logic"
73
- forbiddenImports: ["infrastructure", "application"]
74
-
75
- - name: "application"
76
- path: "src/application"
77
- allowedImports: ["domain"]
78
-
79
- - name: "infrastructure"
80
- path: "src/infrastructure"
81
- allowedImports: ["domain", "application"]
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
- ## 🤝 Contributing
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
- Contributions are welcome! Please feel free to submit a Pull Request.
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
- ## 📄 License
188
+ ## Contributing & License
89
189
 
90
- This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
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
+ }
@@ -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;