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.
@@ -0,0 +1,309 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FastAPIGenerator = void 0;
4
+ const base_1 = require("../base");
5
+ class FastAPIGenerator extends base_1.BaseGenerator {
6
+ getGeneratorName() {
7
+ return "FastAPI";
8
+ }
9
+ async generateProjectStructure(root, arch, options) {
10
+ this.generateCommonFiles(root, options);
11
+ this.generateAppStructure(root, options);
12
+ }
13
+ generateCommonFiles(root, options) {
14
+ const projectName = options.projectName || "fastapi-app";
15
+ this.writeFile(root, "requirements.txt", `
16
+ fastapi>=0.100.0
17
+ uvicorn>=0.23.0
18
+ pydantic>=2.0.0
19
+ python-dotenv>=1.0.0
20
+ import-linter>=1.10.0
21
+ structlog>=23.1.0
22
+ asgi-correlation-id>=4.2.0
23
+ ${options.orm === 'sqlalchemy' ? 'sqlalchemy>=2.0.0' : ''}
24
+ ${options.orm === 'sqlalchemy' && options.database === 'postgresql' ? 'psycopg2-binary>=2.9.0' : ''}
25
+ ${options.orm === 'sqlalchemy' && options.database === 'mysql' ? 'mysqlclient>=2.2.0' : ''}
26
+ `.trim());
27
+ if (options.orm === 'sqlalchemy') {
28
+ this.generateSQLAlchemySetup(root, options);
29
+ }
30
+ this.writeFile(root, ".env", "PORT=8000\nENV=development");
31
+ this.writeFile(root, ".gitignore", "__pycache__/\n.env\nvenv/\n");
32
+ this.writeFile(root, "README.md", `# ${projectName}\n\nGenerated by ArchForge X`);
33
+ // .import-linter (Architecture Guardrails)
34
+ this.writeFile(root, ".import-linter", `
35
+ [importlinter]
36
+ root_package = app
37
+
38
+ [importlinter:contract:1]
39
+ name = API Layer Boundaries
40
+ type = layers
41
+ layers =
42
+ api
43
+ schemas
44
+ models
45
+ containers = app
46
+ `.trim());
47
+ }
48
+ generateAppStructure(root, options) {
49
+ // Main entry point
50
+ this.writeFile(root, "main.py", `
51
+ import uvicorn
52
+ import structlog
53
+ from fastapi import FastAPI, Request
54
+ from asgi_correlation_id import CorrelationIdMiddleware
55
+ from app.api.v1.endpoints import users
56
+
57
+ # Configure Structlog
58
+ structlog.configure(
59
+ processors=[
60
+ structlog.contextvars.merge_contextvars,
61
+ structlog.processors.add_log_level,
62
+ structlog.processors.TimeStamper(fmt="iso"),
63
+ structlog.processors.JSONRenderer()
64
+ ],
65
+ logger_factory=structlog.PrintLoggerFactory(),
66
+ )
67
+
68
+ logger = structlog.get_logger()
69
+
70
+ app = FastAPI(title="FastAPI App")
71
+
72
+ # Middleware
73
+ app.add_middleware(CorrelationIdMiddleware)
74
+
75
+ @app.middleware("http")
76
+ async def log_requests(request: Request, call_next):
77
+ logger.info("Incoming request", method=request.method, url=str(request.url))
78
+ response = await call_next(request)
79
+ logger.info("Request completed", status_code=response.status_code)
80
+ return response
81
+
82
+ app.include_router(users.router, prefix="/api/v1/users", tags=["users"])
83
+
84
+ @app.get("/")
85
+ def read_root():
86
+ return {"message": "Welcome to FastAPI generated by ArchForge X"}
87
+
88
+ if __name__ == "__main__":
89
+ uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)
90
+ `);
91
+ // App package
92
+ this.createDir(root, "app");
93
+ this.writeFile(root, "app/__init__.py", "");
94
+ // Models
95
+ this.writeFile(root, "app/models/__init__.py", "");
96
+ if (options.orm === 'sqlalchemy') {
97
+ this.writeFile(root, "app/models/user.py", `
98
+ from sqlalchemy import Column, String
99
+ from app.infrastructure.database import Base
100
+
101
+ class User(Base):
102
+ __tablename__ = "users"
103
+
104
+ id = Column(String, primary_key=True, index=True)
105
+ name = Column(String)
106
+ email = Column(String, unique=True, index=True)
107
+ `);
108
+ }
109
+ else {
110
+ this.writeFile(root, "app/models/user.py", `
111
+ from pydantic import BaseModel
112
+
113
+ class User(BaseModel):
114
+ id: str
115
+ name: str
116
+ email: str
117
+ `);
118
+ }
119
+ // Schemas
120
+ this.writeFile(root, "app/schemas/__init__.py", "");
121
+ this.writeFile(root, "app/schemas/user.py", `
122
+ from pydantic import BaseModel
123
+
124
+ class UserCreate(BaseModel):
125
+ name: str
126
+ email: str
127
+
128
+ class UserResponse(BaseModel):
129
+ id: str
130
+ name: str
131
+ email: str
132
+
133
+ class Config:
134
+ from_attributes = True
135
+ `);
136
+ // API
137
+ this.createDir(root, "app/api");
138
+ this.writeFile(root, "app/api/__init__.py", "");
139
+ this.createDir(root, "app/api/v1");
140
+ this.writeFile(root, "app/api/v1/__init__.py", "");
141
+ this.createDir(root, "app/api/v1/endpoints");
142
+ this.writeFile(root, "app/api/v1/endpoints/__init__.py", "");
143
+ if (options.orm === 'sqlalchemy') {
144
+ this.writeFile(root, "app/api/v1/endpoints/users.py", `
145
+ from fastapi import APIRouter, HTTPException, Depends
146
+ from sqlalchemy.orm import Session
147
+ from app.schemas.user import UserCreate, UserResponse
148
+ from app.models.user import User
149
+ from app.infrastructure.database import get_db
150
+ import uuid
151
+
152
+ router = APIRouter()
153
+
154
+ @router.post("/", response_model=UserResponse)
155
+ def create_user(user_in: UserCreate, db: Session = Depends(get_db)):
156
+ user = User(id=str(uuid.uuid4()), name=user_in.name, email=user_in.email)
157
+ db.add(user)
158
+ db.commit()
159
+ db.refresh(user)
160
+ return user
161
+
162
+ @router.get("/", response_model=list[UserResponse])
163
+ def read_users(db: Session = Depends(get_db)):
164
+ return db.query(User).all()
165
+ `);
166
+ }
167
+ else {
168
+ this.writeFile(root, "app/api/v1/endpoints/users.py", `
169
+ from fastapi import APIRouter, HTTPException
170
+ from app.schemas.user import UserCreate, UserResponse
171
+ from app.models.user import User
172
+ import uuid
173
+
174
+ router = APIRouter()
175
+
176
+ users_db = []
177
+
178
+ @router.post("/", response_model=UserResponse)
179
+ def create_user(user_in: UserCreate):
180
+ user = User(id=str(uuid.uuid4()), name=user_in.name, email=user_in.email)
181
+ users_db.append(user)
182
+ return user
183
+
184
+ @router.get("/", response_model=list[UserResponse])
185
+ def read_users():
186
+ return users_db
187
+ `);
188
+ }
189
+ }
190
+ generateDocker(root, options) {
191
+ const dbService = options.database === "postgresql" ? `
192
+ db:
193
+ image: postgres:15-alpine
194
+ environment:
195
+ - POSTGRES_USER=user
196
+ - POSTGRES_PASSWORD=password
197
+ - POSTGRES_DB=appdb
198
+ ports:
199
+ - "5432:5432"
200
+ ` : options.database === "mysql" ? `
201
+ db:
202
+ image: mysql:8
203
+ environment:
204
+ - MYSQL_ROOT_PASSWORD=password
205
+ - MYSQL_DATABASE=appdb
206
+ ports:
207
+ - "3306:3306"
208
+ ` : "";
209
+ const dbUrl = options.database === "postgresql" ? "postgresql://user:password@db:5432/appdb" :
210
+ options.database === "mysql" ? "mysql://root:password@db:3306/appdb" :
211
+ "sqlite:///./dev.db";
212
+ this.writeFile(root, "Dockerfile", `
213
+ FROM python:3.11-slim
214
+
215
+ WORKDIR /app
216
+
217
+ COPY requirements.txt .
218
+ RUN pip install --no-cache-dir -r requirements.txt
219
+
220
+ COPY . .
221
+
222
+ EXPOSE 8000
223
+ CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
224
+ `);
225
+ this.writeFile(root, "docker-compose.yml", `
226
+ version: '3.8'
227
+ services:
228
+ app:
229
+ build: .
230
+ ports:
231
+ - "8000:8000"
232
+ environment:
233
+ - PORT=8000
234
+ - ENV=production
235
+ - DATABASE_URL=${dbUrl}
236
+ depends_on:
237
+ ${dbService ? "- db" : ""}
238
+ restart: always
239
+ ${dbService}
240
+ `);
241
+ }
242
+ generateCI(root, options) {
243
+ this.writeFile(root, ".github/workflows/ci.yml", `
244
+ name: CI
245
+
246
+ on:
247
+ push:
248
+ branches: [ main ]
249
+ pull_request:
250
+ branches: [ main ]
251
+
252
+ jobs:
253
+ test:
254
+ runs-on: ubuntu-latest
255
+
256
+ steps:
257
+ - uses: actions/checkout@v3
258
+ - name: Set up Python 3.11
259
+ uses: actions/setup-python@v4
260
+ with:
261
+ python-version: "3.11"
262
+ cache: 'pip'
263
+ - name: Install dependencies
264
+ run: |
265
+ python -m pip install --upgrade pip
266
+ pip install -r requirements.txt
267
+ pip install pytest flake8
268
+ - name: Lint with flake8
269
+ run: |
270
+ flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
271
+ flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
272
+ - name: Architecture Sync & Validation
273
+ run: |
274
+ pip install archforge-x # In a real scenario, this would be available
275
+ archforge sync
276
+ `);
277
+ }
278
+ generateSQLAlchemySetup(root, options) {
279
+ const dbUrl = options.database === "postgresql" ? "os.getenv('DATABASE_URL', 'postgresql://user:password@localhost:5432/appdb')" :
280
+ options.database === "mysql" ? "os.getenv('DATABASE_URL', 'mysql://root:password@localhost:3306/appdb')" :
281
+ "os.getenv('DATABASE_URL', 'sqlite:///./dev.db')";
282
+ this.writeFile(root, "app/infrastructure/database.py", `
283
+ import os
284
+ from sqlalchemy import create_all, create_engine
285
+ from sqlalchemy.ext.declarative import declarative_base
286
+ from sqlalchemy.orm import sessionmaker
287
+
288
+ SQLALCHEMY_DATABASE_URL = ${dbUrl}
289
+
290
+ engine = create_engine(
291
+ SQLALCHEMY_DATABASE_URL,
292
+ connect_args={"check_same_thread": False} if "sqlite" in SQLALCHEMY_DATABASE_URL else {}
293
+ )
294
+ SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
295
+
296
+ Base = declarative_base()
297
+
298
+ def get_db():
299
+ db = SessionLocal()
300
+ try:
301
+ yield db
302
+ finally:
303
+ db.close()
304
+ `);
305
+ // Update main.py to create tables
306
+ this.writeFile(root, "app/infrastructure/__init__.py", "");
307
+ }
308
+ }
309
+ exports.FastAPIGenerator = FastAPIGenerator;
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GeneratorRegistry = void 0;
4
+ class GeneratorRegistry {
5
+ static register(key, generator) {
6
+ this.generators.set(key, generator);
7
+ }
8
+ static getGenerator(language, framework) {
9
+ // Try exact match first
10
+ const key = `${language}:${framework}`;
11
+ if (this.generators.has(key)) {
12
+ return this.generators.get(key);
13
+ }
14
+ // Fallback to language only if framework specific one not found (or if framework is generic)
15
+ if (this.generators.has(language)) {
16
+ return this.generators.get(language);
17
+ }
18
+ return undefined;
19
+ }
20
+ static listGenerators() {
21
+ return Array.from(this.generators.keys());
22
+ }
23
+ }
24
+ exports.GeneratorRegistry = GeneratorRegistry;
25
+ GeneratorRegistry.generators = new Map();
package/dist/index.js CHANGED
@@ -6,8 +6,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const commander_1 = require("commander");
7
7
  const chalk_1 = __importDefault(require("chalk"));
8
8
  const fs_1 = __importDefault(require("fs"));
9
- const init_1 = require("./cli/init");
10
9
  const interactive_1 = require("./cli/interactive");
10
+ const sync_1 = require("./cli/commands/sync");
11
11
  const parser_1 = require("./core/architecture/parser");
12
12
  const generator_1 = require("./generators/generator");
13
13
  const dependency_1 = require("./analyzers/dependency");
@@ -26,10 +26,22 @@ const loadConfig = (configPath) => {
26
26
  return (0, parser_1.loadArchitecture)(configPath);
27
27
  };
28
28
  // --- COMMANDS ---
29
- program.addCommand(init_1.initCommand);
29
+ program
30
+ .command("init")
31
+ .description("Initialize a new project with interactive wizard (Recommended)")
32
+ .action(async () => {
33
+ await (0, interactive_1.interactiveCLI)();
34
+ });
35
+ program
36
+ .command("sync")
37
+ .description("Synchronize architecture with archforge.yaml and validate structure")
38
+ .action(async () => {
39
+ await (0, sync_1.syncCommand)();
40
+ });
30
41
  program
31
42
  .command("generate")
32
- .description("Scaffold a project based on architecture definition")
43
+ .alias("create")
44
+ .description("Scaffold a project or module based on architecture definition")
33
45
  .option("-c, --config <path>", "Path to yaml config", "archforge.yaml")
34
46
  .option("-m, --mode <mode>", "Generation mode: 'standard' or 'professional'", "professional")
35
47
  .action(async (options) => {
@@ -41,7 +53,9 @@ program
41
53
  language: arch.metadata.language,
42
54
  framework: arch.metadata.framework,
43
55
  modules: arch.metadata.modules,
44
- projectName: arch.project.name
56
+ projectName: arch.project.name,
57
+ orm: arch.metadata.orm,
58
+ database: arch.metadata.database
45
59
  };
46
60
  await (0, generator_1.generateProject)(arch, genOptions);
47
61
  }
@@ -51,14 +65,15 @@ program
51
65
  }
52
66
  });
53
67
  program
54
- .command("analyze")
55
- .description("Static analysis of dependency graph against rules")
68
+ .command("validate")
69
+ .alias("check")
70
+ .description("Validate project structure and dependencies against rules")
56
71
  .option("-c, --config <path>", "Path to yaml config", "archforge.yaml")
57
72
  .option("--ignore-tests", "Exclude test files from analysis")
58
73
  .option("-o, --output <file>", "Export report to JSON/Markdown")
59
74
  .action((options) => {
60
75
  try {
61
- console.log(chalk_1.default.cyan("šŸ” Analyzing Dependencies..."));
76
+ console.log(chalk_1.default.cyan("šŸ” Validating Architecture..."));
62
77
  const arch = loadConfig(options.config);
63
78
  const violations = (0, dependency_1.analyzeDependencies)(arch, {
64
79
  ignoreTests: options.ignoreTests,
@@ -69,13 +84,13 @@ program
69
84
  process.exit(1);
70
85
  }
71
86
  catch (err) {
72
- console.error(chalk_1.default.red.bold("\nāŒ Analysis Error:"), err.message);
87
+ console.error(chalk_1.default.red.bold("\nāŒ Validation Error:"), err.message);
73
88
  process.exit(1);
74
89
  }
75
90
  });
76
91
  program
77
92
  .command("audit")
78
- .description("Deep architectural audit with auto-fix suggestions")
93
+ .description("Perform a deep architectural audit with auto-fix suggestions")
79
94
  .option("-c, --config <path>", "Path to yaml config", "archforge.yaml")
80
95
  .option("-o, --output <file>", "Export audit results")
81
96
  .action((options) => {
@@ -91,8 +106,9 @@ program
91
106
  }
92
107
  });
93
108
  program
94
- .command("visualize")
95
- .description("Generate visual dependency graph (SVG)")
109
+ .command("graph")
110
+ .alias("visualize")
111
+ .description("Generate a visual dependency graph (SVG)")
96
112
  .option("-c, --config <path>", "Path to yaml config", "archforge.yaml")
97
113
  .option("-o, --output <file>", "Output file path", "architecture-graph.svg")
98
114
  .action(async (options) => {
@@ -106,10 +122,8 @@ program
106
122
  console.error(chalk_1.default.red.bold("\nāŒ Visualization Error:"), err.message);
107
123
  }
108
124
  });
109
- program
110
- .command("interactive", { isDefault: true })
111
- .description("Interactive Wizard (Recommended)")
112
- .action(async () => {
125
+ // Set default command to init
126
+ program.action(async () => {
113
127
  await (0, interactive_1.interactiveCLI)();
114
128
  });
115
129
  program.parse(process.argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "archforge-x",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "Enterprise Architecture Engine for scaffolding, analyzing, and visualizing software architecture.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -39,6 +39,7 @@
39
39
  "homepage": "https://github.com/henabakos/archforge-x#readme",
40
40
  "devDependencies": {
41
41
  "@types/fs-extra": "^11.0.4",
42
+ "@types/js-yaml": "^4.0.9",
42
43
  "@types/node": "^25.0.3",
43
44
  "@types/prompts": "^2.4.9",
44
45
  "@types/viz.js": "^2.1.5",
@@ -48,6 +49,7 @@
48
49
  "chalk": "^5.6.2",
49
50
  "commander": "^14.0.2",
50
51
  "fs-extra": "^11.3.3",
52
+ "js-yaml": "^4.1.1",
51
53
  "prompts": "^2.4.2",
52
54
  "typescript": "^5.9.3",
53
55
  "viz.js": "^2.1.2",
package/dist/cli/init.js DELETED
@@ -1,74 +0,0 @@
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.initCommand = void 0;
7
- const commander_1 = require("commander");
8
- const fs_1 = __importDefault(require("fs"));
9
- const chalk_1 = __importDefault(require("chalk"));
10
- const path_1 = __importDefault(require("path"));
11
- exports.initCommand = new commander_1.Command("init")
12
- .description("Initialize a new architecture configuration file (archforge.yaml)")
13
- .action(() => {
14
- const configPath = path_1.default.resolve(process.cwd(), "archforge.yaml");
15
- // 1. Safety Check: Don't overwrite existing config
16
- if (fs_1.default.existsSync(configPath)) {
17
- console.log(chalk_1.default.yellow("āš ļø Configuration file 'archforge.yaml' already exists."));
18
- console.log(chalk_1.default.gray(` Path: ${configPath}`));
19
- console.log(chalk_1.default.blue(" Run 'archforge analyze' or 'archforge generate' to use it."));
20
- return;
21
- }
22
- const template = `
23
- version: "1.0"
24
- name: "Enterprise Architecture Config"
25
- project:
26
- name: "my-awesome-project"
27
- root: "."
28
-
29
- metadata:
30
- type: "clean" # Options: clean, ddd, hexagonal, layered
31
- language: "ts" # Options: ts, js, py, go
32
- framework: "nestjs" # Options: nestjs, express, django, flask, gin
33
- version: "1.0.0"
34
- modules: # Optional modules to scaffold
35
- - "auth"
36
- - "docker"
37
-
38
- # Define your architecture layers and dependency rules here
39
- layers:
40
- - name: "domain"
41
- path: "src/domain"
42
- description: "Core business logic and entities (Enterprise Rules)"
43
- strict: true
44
- forbiddenImports: ["infrastructure", "interface", "application"]
45
-
46
- - name: "application"
47
- path: "src/application"
48
- description: "Use cases and application logic"
49
- allowedImports: ["domain"]
50
-
51
- - name: "infrastructure"
52
- path: "src/infrastructure"
53
- description: "External tools, databases, and third-party services"
54
- allowedImports: ["domain", "application"]
55
-
56
- - name: "interface"
57
- path: "src/interface"
58
- description: "Controllers, API endpoints, and Presenters"
59
- allowedImports: ["application"]
60
- `.trim();
61
- // 3. Write the file
62
- try {
63
- fs_1.default.writeFileSync(configPath, template, "utf-8");
64
- console.log(chalk_1.default.green.bold("āœ… Successfully initialized 'archforge.yaml'"));
65
- console.log(chalk_1.default.white(" You can now customize the layers in the file."));
66
- console.log(chalk_1.default.gray("\nNext steps:"));
67
- console.log(chalk_1.default.cyan(" 1. archforge generate"));
68
- console.log(chalk_1.default.cyan(" 2. archforge analyze"));
69
- }
70
- catch (err) {
71
- console.error(chalk_1.default.red("āŒ Failed to create configuration file:"), err.message);
72
- process.exit(1);
73
- }
74
- });