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.
- 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 +10 -1
- package/package.json +3 -1
|
@@ -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.
|
|
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",
|