archforge-x 1.0.2 → 1.0.3

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
@@ -8,6 +8,7 @@ const chalk_1 = __importDefault(require("chalk"));
8
8
  const fs_1 = __importDefault(require("fs"));
9
9
  const init_1 = require("./cli/init");
10
10
  const interactive_1 = require("./cli/interactive");
11
+ const sync_1 = require("./cli/commands/sync");
11
12
  const parser_1 = require("./core/architecture/parser");
12
13
  const generator_1 = require("./generators/generator");
13
14
  const dependency_1 = require("./analyzers/dependency");
@@ -27,6 +28,12 @@ const loadConfig = (configPath) => {
27
28
  };
28
29
  // --- COMMANDS ---
29
30
  program.addCommand(init_1.initCommand);
31
+ program
32
+ .command("sync")
33
+ .description("Sync architecture with archforge.yaml and validate structure")
34
+ .action(async () => {
35
+ await (0, sync_1.syncCommand)();
36
+ });
30
37
  program
31
38
  .command("generate")
32
39
  .description("Scaffold a project based on architecture definition")
@@ -41,7 +48,9 @@ program
41
48
  language: arch.metadata.language,
42
49
  framework: arch.metadata.framework,
43
50
  modules: arch.metadata.modules,
44
- projectName: arch.project.name
51
+ projectName: arch.project.name,
52
+ orm: arch.metadata.orm,
53
+ database: arch.metadata.database
45
54
  };
46
55
  await (0, generator_1.generateProject)(arch, genOptions);
47
56
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "archforge-x",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
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",