claude-autopm 1.18.0 → 1.20.0
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 +159 -0
- package/autopm/.claude/agents/core/mcp-manager.md +1 -1
- package/autopm/.claude/commands/pm/context.md +11 -0
- package/autopm/.claude/commands/pm/epic-decompose.md +25 -2
- package/autopm/.claude/commands/pm/epic-oneshot.md +13 -0
- package/autopm/.claude/commands/pm/epic-start.md +19 -0
- package/autopm/.claude/commands/pm/epic-sync-modular.md +10 -10
- package/autopm/.claude/commands/pm/epic-sync.md +14 -14
- package/autopm/.claude/commands/pm/issue-start.md +50 -5
- package/autopm/.claude/commands/pm/issue-sync.md +15 -15
- package/autopm/.claude/commands/pm/what-next.md +11 -0
- package/autopm/.claude/mcp/MCP-REGISTRY.md +1 -1
- package/autopm/.claude/scripts/azure/active-work.js +2 -2
- package/autopm/.claude/scripts/azure/blocked.js +13 -13
- package/autopm/.claude/scripts/azure/daily.js +1 -1
- package/autopm/.claude/scripts/azure/dashboard.js +1 -1
- package/autopm/.claude/scripts/azure/feature-list.js +2 -2
- package/autopm/.claude/scripts/azure/feature-status.js +1 -1
- package/autopm/.claude/scripts/azure/next-task.js +1 -1
- package/autopm/.claude/scripts/azure/search.js +1 -1
- package/autopm/.claude/scripts/azure/setup.js +15 -15
- package/autopm/.claude/scripts/azure/sprint-report.js +2 -2
- package/autopm/.claude/scripts/azure/sync.js +1 -1
- package/autopm/.claude/scripts/azure/us-list.js +1 -1
- package/autopm/.claude/scripts/azure/us-status.js +1 -1
- package/autopm/.claude/scripts/azure/validate.js +13 -13
- package/autopm/.claude/scripts/lib/frontmatter-utils.sh +42 -7
- package/autopm/.claude/scripts/lib/logging-utils.sh +20 -16
- package/autopm/.claude/scripts/lib/validation-utils.sh +1 -1
- package/autopm/.claude/scripts/pm/context.js +338 -0
- package/autopm/.claude/scripts/pm/issue-sync/format-comment.sh +3 -3
- package/autopm/.claude/scripts/pm/lib/README.md +85 -0
- package/autopm/.claude/scripts/pm/lib/logger.js +78 -0
- package/autopm/.claude/scripts/pm/next.js +25 -1
- package/autopm/.claude/scripts/pm/what-next.js +660 -0
- package/bin/autopm.js +25 -0
- package/package.json +1 -1
- package/lib/agentExecutor.js.deprecated +0 -101
- package/lib/azure/cache.js +0 -80
- package/lib/azure/client.js +0 -77
- package/lib/azure/formatter.js +0 -177
- package/lib/commandHelpers.js +0 -177
- package/lib/context/manager.js +0 -290
- package/lib/documentation/manager.js +0 -528
- package/lib/github/workflow-manager.js +0 -546
- package/lib/helpers/azure-batch-api.js +0 -133
- package/lib/helpers/azure-cache-manager.js +0 -287
- package/lib/helpers/azure-parallel-processor.js +0 -158
- package/lib/helpers/azure-work-item-create.js +0 -278
- package/lib/helpers/gh-issue-create.js +0 -250
- package/lib/helpers/interactive-prompt.js +0 -336
- package/lib/helpers/output-manager.js +0 -335
- package/lib/helpers/progress-indicator.js +0 -258
- package/lib/performance/benchmarker.js +0 -429
- package/lib/pm/epic-decomposer.js +0 -273
- package/lib/pm/epic-syncer.js +0 -221
- package/lib/prdMetadata.js +0 -270
- package/lib/providers/azure/index.js +0 -234
- package/lib/providers/factory.js +0 -87
- package/lib/providers/github/index.js +0 -204
- package/lib/providers/interface.js +0 -73
- package/lib/python/scaffold-manager.js +0 -576
- package/lib/react/scaffold-manager.js +0 -745
- package/lib/regression/analyzer.js +0 -578
- package/lib/release/manager.js +0 -324
- package/lib/tailwind/manager.js +0 -486
- package/lib/traefik/manager.js +0 -484
- package/lib/utils/colors.js +0 -126
- package/lib/utils/config.js +0 -317
- package/lib/utils/filesystem.js +0 -316
- package/lib/utils/logger.js +0 -135
- package/lib/utils/prompts.js +0 -294
- package/lib/utils/shell.js +0 -237
- package/lib/validators/email-validator.js +0 -337
- package/lib/workflow/manager.js +0 -449
|
@@ -1,576 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Python Scaffold Manager
|
|
3
|
-
* Centralized Python project scaffolding functionality
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
const fs = require('fs').promises;
|
|
7
|
-
const path = require('path');
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Configuration
|
|
11
|
-
*/
|
|
12
|
-
const CONFIG = {
|
|
13
|
-
defaults: {
|
|
14
|
-
port: 8000,
|
|
15
|
-
database: 'postgres',
|
|
16
|
-
environment: 'development'
|
|
17
|
-
}
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Template definitions
|
|
22
|
-
*/
|
|
23
|
-
const TEMPLATES = {
|
|
24
|
-
fastapi: {
|
|
25
|
-
main: `from fastapi import FastAPI
|
|
26
|
-
from fastapi.middleware.cors import CORSMiddleware
|
|
27
|
-
|
|
28
|
-
app = FastAPI(title="API", version="1.0.0")
|
|
29
|
-
|
|
30
|
-
# Configure CORS
|
|
31
|
-
app.add_middleware(
|
|
32
|
-
CORSMiddleware,
|
|
33
|
-
allow_origins=["*"],
|
|
34
|
-
allow_credentials=True,
|
|
35
|
-
allow_methods=["*"],
|
|
36
|
-
allow_headers=["*"],
|
|
37
|
-
)
|
|
38
|
-
|
|
39
|
-
@app.get("/")
|
|
40
|
-
def read_root():
|
|
41
|
-
return {"message": "Welcome to FastAPI"}
|
|
42
|
-
|
|
43
|
-
@app.get("/health")
|
|
44
|
-
def health_check():
|
|
45
|
-
return {"status": "healthy"}
|
|
46
|
-
`,
|
|
47
|
-
requirements: `fastapi==0.104.1
|
|
48
|
-
uvicorn[standard]==0.24.0
|
|
49
|
-
pydantic==2.4.2
|
|
50
|
-
python-multipart==0.0.6
|
|
51
|
-
python-jose[cryptography]==3.3.0
|
|
52
|
-
passlib[bcrypt]==1.7.4
|
|
53
|
-
sqlalchemy==2.0.23
|
|
54
|
-
alembic==1.12.1
|
|
55
|
-
psycopg2-binary==2.9.9
|
|
56
|
-
redis==5.0.1
|
|
57
|
-
celery==5.3.4
|
|
58
|
-
pytest==7.4.3
|
|
59
|
-
pytest-asyncio==0.21.1
|
|
60
|
-
httpx==0.25.1
|
|
61
|
-
`
|
|
62
|
-
},
|
|
63
|
-
flask: {
|
|
64
|
-
app: `from flask import Flask, jsonify
|
|
65
|
-
from flask_cors import CORS
|
|
66
|
-
|
|
67
|
-
app = Flask(__name__)
|
|
68
|
-
CORS(app)
|
|
69
|
-
|
|
70
|
-
@app.route('/')
|
|
71
|
-
def index():
|
|
72
|
-
return jsonify({"message": "Welcome to Flask"})
|
|
73
|
-
|
|
74
|
-
@app.route('/health')
|
|
75
|
-
def health():
|
|
76
|
-
return jsonify({"status": "healthy"})
|
|
77
|
-
|
|
78
|
-
if __name__ == '__main__':
|
|
79
|
-
app.run(debug=True, host='0.0.0.0', port=5000)
|
|
80
|
-
`,
|
|
81
|
-
requirements: `flask==3.0.0
|
|
82
|
-
flask-cors==4.0.0
|
|
83
|
-
flask-sqlalchemy==3.1.1
|
|
84
|
-
flask-migrate==4.0.5
|
|
85
|
-
flask-jwt-extended==4.5.3
|
|
86
|
-
python-dotenv==1.0.0
|
|
87
|
-
gunicorn==21.2.0
|
|
88
|
-
psycopg2-binary==2.9.9
|
|
89
|
-
redis==5.0.1
|
|
90
|
-
celery==5.3.4
|
|
91
|
-
pytest==7.4.3
|
|
92
|
-
pytest-flask==1.3.0
|
|
93
|
-
`
|
|
94
|
-
}
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
class PythonScaffoldManager {
|
|
98
|
-
constructor(projectRoot = process.cwd()) {
|
|
99
|
-
this.projectRoot = projectRoot;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Creates API project
|
|
104
|
-
*/
|
|
105
|
-
async createAPIProject(framework = 'fastapi', projectName = 'api') {
|
|
106
|
-
if (framework === 'fastapi') {
|
|
107
|
-
return await this.createFastAPIProject(projectName);
|
|
108
|
-
} else if (framework === 'flask') {
|
|
109
|
-
return await this.createFlaskProject();
|
|
110
|
-
} else {
|
|
111
|
-
throw new Error(`Unknown framework: ${framework}`);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Creates FastAPI project
|
|
117
|
-
*/
|
|
118
|
-
async createFastAPIProject(projectName) {
|
|
119
|
-
const projectDir = path.join(this.projectRoot, projectName);
|
|
120
|
-
|
|
121
|
-
// Create directory structure
|
|
122
|
-
await fs.mkdir(projectDir, { recursive: true });
|
|
123
|
-
await fs.mkdir(path.join(projectDir, 'app'), { recursive: true });
|
|
124
|
-
await fs.mkdir(path.join(projectDir, 'app', 'api'), { recursive: true });
|
|
125
|
-
await fs.mkdir(path.join(projectDir, 'app', 'core'), { recursive: true });
|
|
126
|
-
await fs.mkdir(path.join(projectDir, 'app', 'models'), { recursive: true });
|
|
127
|
-
|
|
128
|
-
// Create main.py
|
|
129
|
-
await fs.writeFile(path.join(projectDir, 'main.py'), TEMPLATES.fastapi.main);
|
|
130
|
-
|
|
131
|
-
// Create __init__.py files
|
|
132
|
-
await fs.writeFile(path.join(projectDir, 'app', '__init__.py'), '');
|
|
133
|
-
await fs.writeFile(path.join(projectDir, 'app', 'api', '__init__.py'), '');
|
|
134
|
-
await fs.writeFile(path.join(projectDir, 'app', 'core', '__init__.py'), '');
|
|
135
|
-
await fs.writeFile(path.join(projectDir, 'app', 'models', '__init__.py'), '');
|
|
136
|
-
|
|
137
|
-
// Create requirements.txt
|
|
138
|
-
await fs.writeFile(
|
|
139
|
-
path.join(this.projectRoot, 'requirements.txt'),
|
|
140
|
-
TEMPLATES.fastapi.requirements
|
|
141
|
-
);
|
|
142
|
-
|
|
143
|
-
return {
|
|
144
|
-
projectName,
|
|
145
|
-
framework: 'fastapi',
|
|
146
|
-
mainFile: `${projectName}/main.py`,
|
|
147
|
-
structure: ['app/api', 'app/core', 'app/models']
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Creates Flask project
|
|
153
|
-
*/
|
|
154
|
-
async createFlaskProject() {
|
|
155
|
-
// Create directory structure
|
|
156
|
-
await fs.mkdir(path.join(this.projectRoot, 'templates'), { recursive: true });
|
|
157
|
-
await fs.mkdir(path.join(this.projectRoot, 'static'), { recursive: true });
|
|
158
|
-
await fs.mkdir(path.join(this.projectRoot, 'static', 'css'), { recursive: true });
|
|
159
|
-
await fs.mkdir(path.join(this.projectRoot, 'static', 'js'), { recursive: true });
|
|
160
|
-
|
|
161
|
-
// Create app.py
|
|
162
|
-
await fs.writeFile(
|
|
163
|
-
path.join(this.projectRoot, 'app.py'),
|
|
164
|
-
TEMPLATES.flask.app
|
|
165
|
-
);
|
|
166
|
-
|
|
167
|
-
// Create requirements.txt
|
|
168
|
-
await fs.writeFile(
|
|
169
|
-
path.join(this.projectRoot, 'requirements.txt'),
|
|
170
|
-
TEMPLATES.flask.requirements
|
|
171
|
-
);
|
|
172
|
-
|
|
173
|
-
return {
|
|
174
|
-
framework: 'flask',
|
|
175
|
-
mainFile: 'app.py',
|
|
176
|
-
directories: ['templates/', 'static/']
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Creates database models
|
|
182
|
-
*/
|
|
183
|
-
async createModels(database = 'postgres') {
|
|
184
|
-
await fs.mkdir(path.join(this.projectRoot, 'models'), { recursive: true });
|
|
185
|
-
|
|
186
|
-
// Base model
|
|
187
|
-
const baseModel = `from sqlalchemy import Column, Integer, DateTime, func
|
|
188
|
-
from sqlalchemy.ext.declarative import declarative_base
|
|
189
|
-
|
|
190
|
-
Base = declarative_base()
|
|
191
|
-
|
|
192
|
-
class BaseModel(Base):
|
|
193
|
-
__abstract__ = True
|
|
194
|
-
|
|
195
|
-
id = Column(Integer, primary_key=True, index=True)
|
|
196
|
-
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
|
197
|
-
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
|
|
198
|
-
`;
|
|
199
|
-
|
|
200
|
-
// User model
|
|
201
|
-
const userModel = `from sqlalchemy import Column, String, Boolean
|
|
202
|
-
from .base import BaseModel
|
|
203
|
-
|
|
204
|
-
class User(BaseModel):
|
|
205
|
-
__tablename__ = "users"
|
|
206
|
-
|
|
207
|
-
email = Column(String, unique=True, index=True, nullable=False)
|
|
208
|
-
username = Column(String, unique=True, index=True, nullable=False)
|
|
209
|
-
hashed_password = Column(String, nullable=False)
|
|
210
|
-
is_active = Column(Boolean, default=True)
|
|
211
|
-
is_superuser = Column(Boolean, default=False)
|
|
212
|
-
`;
|
|
213
|
-
|
|
214
|
-
// Database configuration
|
|
215
|
-
const dbConfig = `from sqlalchemy import create_engine
|
|
216
|
-
from sqlalchemy.orm import sessionmaker
|
|
217
|
-
|
|
218
|
-
DATABASE_URL = "${database}://user:password@localhost/dbname"
|
|
219
|
-
|
|
220
|
-
engine = create_engine(DATABASE_URL)
|
|
221
|
-
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
|
222
|
-
|
|
223
|
-
def get_db():
|
|
224
|
-
db = SessionLocal()
|
|
225
|
-
try:
|
|
226
|
-
yield db
|
|
227
|
-
finally:
|
|
228
|
-
db.close()
|
|
229
|
-
`;
|
|
230
|
-
|
|
231
|
-
await fs.writeFile(path.join(this.projectRoot, 'models', '__init__.py'), '');
|
|
232
|
-
await fs.writeFile(path.join(this.projectRoot, 'models', 'base.py'), baseModel);
|
|
233
|
-
await fs.writeFile(path.join(this.projectRoot, 'models', 'user.py'), userModel);
|
|
234
|
-
await fs.writeFile(path.join(this.projectRoot, 'models', 'database.py'), dbConfig);
|
|
235
|
-
|
|
236
|
-
return {
|
|
237
|
-
database,
|
|
238
|
-
models: ['base.py', 'user.py'],
|
|
239
|
-
config: 'database.py'
|
|
240
|
-
};
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
/**
|
|
244
|
-
* Creates API routes
|
|
245
|
-
*/
|
|
246
|
-
async createRoutes(resource = 'items') {
|
|
247
|
-
await fs.mkdir(path.join(this.projectRoot, 'routes'), { recursive: true });
|
|
248
|
-
|
|
249
|
-
const routeTemplate = `from fastapi import APIRouter, Depends, HTTPException
|
|
250
|
-
from sqlalchemy.orm import Session
|
|
251
|
-
from typing import List
|
|
252
|
-
|
|
253
|
-
router = APIRouter(prefix="/${resource}", tags=["${resource}"])
|
|
254
|
-
|
|
255
|
-
@router.get("/", response_model=List[dict])
|
|
256
|
-
def get_${resource}():
|
|
257
|
-
"""Get all ${resource}"""
|
|
258
|
-
return [{"id": 1, "name": "Example"}]
|
|
259
|
-
|
|
260
|
-
@router.get("/{id}", response_model=dict)
|
|
261
|
-
def get_${resource.slice(0, -1)}(id: int):
|
|
262
|
-
"""Get a specific ${resource.slice(0, -1)}"""
|
|
263
|
-
return {"id": id, "name": "Example"}
|
|
264
|
-
|
|
265
|
-
@router.post("/", response_model=dict)
|
|
266
|
-
def create_${resource.slice(0, -1)}(data: dict):
|
|
267
|
-
"""Create a new ${resource.slice(0, -1)}"""
|
|
268
|
-
return {"id": 1, **data}
|
|
269
|
-
|
|
270
|
-
@router.put("/{id}", response_model=dict)
|
|
271
|
-
def update_${resource.slice(0, -1)}(id: int, data: dict):
|
|
272
|
-
"""Update a ${resource.slice(0, -1)}"""
|
|
273
|
-
return {"id": id, **data}
|
|
274
|
-
|
|
275
|
-
@router.delete("/{id}")
|
|
276
|
-
def delete_${resource.slice(0, -1)}(id: int):
|
|
277
|
-
"""Delete a ${resource.slice(0, -1)}"""
|
|
278
|
-
return {"message": "Deleted successfully"}
|
|
279
|
-
`;
|
|
280
|
-
|
|
281
|
-
await fs.writeFile(path.join(this.projectRoot, 'routes', '__init__.py'), '');
|
|
282
|
-
await fs.writeFile(
|
|
283
|
-
path.join(this.projectRoot, 'routes', `${resource}.py`),
|
|
284
|
-
routeTemplate
|
|
285
|
-
);
|
|
286
|
-
|
|
287
|
-
return {
|
|
288
|
-
resource,
|
|
289
|
-
file: `routes/${resource}.py`,
|
|
290
|
-
endpoints: ['GET', 'POST', 'PUT', 'DELETE']
|
|
291
|
-
};
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
/**
|
|
295
|
-
* Adds authentication
|
|
296
|
-
*/
|
|
297
|
-
async addAuthentication(authType = 'jwt') {
|
|
298
|
-
await fs.mkdir(path.join(this.projectRoot, 'auth'), { recursive: true });
|
|
299
|
-
|
|
300
|
-
const authInit = `from .auth import *
|
|
301
|
-
from .jwt import *
|
|
302
|
-
`;
|
|
303
|
-
|
|
304
|
-
const jwtAuth = `from datetime import datetime, timedelta
|
|
305
|
-
from typing import Optional
|
|
306
|
-
from jose import JWTError, jwt
|
|
307
|
-
from passlib.context import CryptContext
|
|
308
|
-
|
|
309
|
-
SECRET_KEY = "your-secret-key-here"
|
|
310
|
-
ALGORITHM = "HS256"
|
|
311
|
-
ACCESS_TOKEN_EXPIRE_MINUTES = 30
|
|
312
|
-
|
|
313
|
-
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
|
314
|
-
|
|
315
|
-
def verify_password(plain_password, hashed_password):
|
|
316
|
-
return pwd_context.verify(plain_password, hashed_password)
|
|
317
|
-
|
|
318
|
-
def get_password_hash(password):
|
|
319
|
-
return pwd_context.hash(password)
|
|
320
|
-
|
|
321
|
-
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
|
|
322
|
-
to_encode = data.copy()
|
|
323
|
-
if expires_delta:
|
|
324
|
-
expire = datetime.utcnow() + expires_delta
|
|
325
|
-
else:
|
|
326
|
-
expire = datetime.utcnow() + timedelta(minutes=15)
|
|
327
|
-
to_encode.update({"exp": expire})
|
|
328
|
-
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
|
|
329
|
-
return encoded_jwt
|
|
330
|
-
|
|
331
|
-
def decode_token(token: str):
|
|
332
|
-
try:
|
|
333
|
-
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
|
|
334
|
-
return payload
|
|
335
|
-
except JWTError:
|
|
336
|
-
return None
|
|
337
|
-
`;
|
|
338
|
-
|
|
339
|
-
await fs.writeFile(path.join(this.projectRoot, 'auth', '__init__.py'), authInit);
|
|
340
|
-
await fs.writeFile(path.join(this.projectRoot, 'auth', 'jwt.py'), jwtAuth);
|
|
341
|
-
|
|
342
|
-
return {
|
|
343
|
-
type: authType,
|
|
344
|
-
module: 'auth/',
|
|
345
|
-
functions: ['verify_password', 'create_access_token', 'decode_token']
|
|
346
|
-
};
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
/**
|
|
350
|
-
* Generates Docker configuration
|
|
351
|
-
*/
|
|
352
|
-
async generateDockerConfig(port = CONFIG.defaults.port) {
|
|
353
|
-
const dockerfile = `FROM python:3.11-slim
|
|
354
|
-
|
|
355
|
-
WORKDIR /app
|
|
356
|
-
|
|
357
|
-
# Install system dependencies
|
|
358
|
-
RUN apt-get update && apt-get install -y \\
|
|
359
|
-
gcc \\
|
|
360
|
-
&& rm -rf /var/lib/apt/lists/*
|
|
361
|
-
|
|
362
|
-
# Copy requirements first for better caching
|
|
363
|
-
COPY requirements.txt .
|
|
364
|
-
RUN pip install --no-cache-dir -r requirements.txt
|
|
365
|
-
|
|
366
|
-
# Copy application code
|
|
367
|
-
COPY . .
|
|
368
|
-
|
|
369
|
-
# Expose port
|
|
370
|
-
EXPOSE ${port}
|
|
371
|
-
|
|
372
|
-
# Run the application
|
|
373
|
-
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "${port}"]
|
|
374
|
-
`;
|
|
375
|
-
|
|
376
|
-
const dockerCompose = `version: '3.8'
|
|
377
|
-
|
|
378
|
-
services:
|
|
379
|
-
api:
|
|
380
|
-
build: .
|
|
381
|
-
ports:
|
|
382
|
-
- "${port}:${port}"
|
|
383
|
-
environment:
|
|
384
|
-
- DATABASE_URL=postgresql://user:password@db:5432/dbname
|
|
385
|
-
- REDIS_URL=redis://redis:6379
|
|
386
|
-
depends_on:
|
|
387
|
-
- db
|
|
388
|
-
- redis
|
|
389
|
-
volumes:
|
|
390
|
-
- .:/app
|
|
391
|
-
command: uvicorn main:app --reload --host 0.0.0.0 --port ${port}
|
|
392
|
-
|
|
393
|
-
db:
|
|
394
|
-
image: postgres:15
|
|
395
|
-
environment:
|
|
396
|
-
- POSTGRES_USER=user
|
|
397
|
-
- POSTGRES_PASSWORD=password
|
|
398
|
-
- POSTGRES_DB=dbname
|
|
399
|
-
volumes:
|
|
400
|
-
- postgres_data:/var/lib/postgresql/data
|
|
401
|
-
ports:
|
|
402
|
-
- "5432:5432"
|
|
403
|
-
|
|
404
|
-
redis:
|
|
405
|
-
image: redis:7
|
|
406
|
-
ports:
|
|
407
|
-
- "6379:6379"
|
|
408
|
-
|
|
409
|
-
volumes:
|
|
410
|
-
postgres_data:
|
|
411
|
-
`;
|
|
412
|
-
|
|
413
|
-
await fs.writeFile(path.join(this.projectRoot, 'Dockerfile'), dockerfile);
|
|
414
|
-
await fs.writeFile(path.join(this.projectRoot, 'docker-compose.yml'), dockerCompose);
|
|
415
|
-
|
|
416
|
-
return {
|
|
417
|
-
dockerfile: 'Dockerfile',
|
|
418
|
-
compose: 'docker-compose.yml',
|
|
419
|
-
port,
|
|
420
|
-
services: ['api', 'db', 'redis']
|
|
421
|
-
};
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
/**
|
|
425
|
-
* Creates environment configuration
|
|
426
|
-
*/
|
|
427
|
-
async createEnvironmentConfig(env = CONFIG.defaults.environment) {
|
|
428
|
-
const envConfig = `# Environment
|
|
429
|
-
ENV=${env}
|
|
430
|
-
DEBUG=${env === 'development' ? 'true' : 'false'}
|
|
431
|
-
|
|
432
|
-
# Application
|
|
433
|
-
APP_NAME=Python API
|
|
434
|
-
APP_VERSION=1.0.0
|
|
435
|
-
SECRET_KEY=your-secret-key-here-change-in-production
|
|
436
|
-
|
|
437
|
-
# Database
|
|
438
|
-
DATABASE_URL=postgresql://user:password@localhost:5432/dbname
|
|
439
|
-
DATABASE_POOL_SIZE=5
|
|
440
|
-
DATABASE_MAX_OVERFLOW=10
|
|
441
|
-
|
|
442
|
-
# Redis
|
|
443
|
-
REDIS_URL=redis://localhost:6379/0
|
|
444
|
-
|
|
445
|
-
# JWT
|
|
446
|
-
JWT_SECRET_KEY=your-jwt-secret-key
|
|
447
|
-
JWT_ALGORITHM=HS256
|
|
448
|
-
JWT_EXPIRATION_MINUTES=30
|
|
449
|
-
|
|
450
|
-
# CORS
|
|
451
|
-
CORS_ORIGINS=http://localhost:3000,http://localhost:8080
|
|
452
|
-
|
|
453
|
-
# Logging
|
|
454
|
-
LOG_LEVEL=${env === 'development' ? 'DEBUG' : 'INFO'}
|
|
455
|
-
`;
|
|
456
|
-
|
|
457
|
-
const configLoader = `import os
|
|
458
|
-
from dotenv import load_dotenv
|
|
459
|
-
|
|
460
|
-
# Load environment variables
|
|
461
|
-
load_dotenv(f".env.{os.getenv('ENV', 'development')}")
|
|
462
|
-
|
|
463
|
-
class Settings:
|
|
464
|
-
# Environment
|
|
465
|
-
ENV: str = os.getenv("ENV", "development")
|
|
466
|
-
DEBUG: bool = os.getenv("DEBUG", "false").lower() == "true"
|
|
467
|
-
|
|
468
|
-
# Application
|
|
469
|
-
APP_NAME: str = os.getenv("APP_NAME", "API")
|
|
470
|
-
APP_VERSION: str = os.getenv("APP_VERSION", "1.0.0")
|
|
471
|
-
SECRET_KEY: str = os.getenv("SECRET_KEY", "")
|
|
472
|
-
|
|
473
|
-
# Database
|
|
474
|
-
DATABASE_URL: str = os.getenv("DATABASE_URL", "")
|
|
475
|
-
|
|
476
|
-
# Redis
|
|
477
|
-
REDIS_URL: str = os.getenv("REDIS_URL", "")
|
|
478
|
-
|
|
479
|
-
# JWT
|
|
480
|
-
JWT_SECRET_KEY: str = os.getenv("JWT_SECRET_KEY", "")
|
|
481
|
-
JWT_ALGORITHM: str = os.getenv("JWT_ALGORITHM", "HS256")
|
|
482
|
-
JWT_EXPIRATION_MINUTES: int = int(os.getenv("JWT_EXPIRATION_MINUTES", "30"))
|
|
483
|
-
|
|
484
|
-
settings = Settings()
|
|
485
|
-
`;
|
|
486
|
-
|
|
487
|
-
await fs.writeFile(path.join(this.projectRoot, `.env.${env}`), envConfig);
|
|
488
|
-
await fs.writeFile(path.join(this.projectRoot, 'config.py'), configLoader);
|
|
489
|
-
|
|
490
|
-
return {
|
|
491
|
-
environment: env,
|
|
492
|
-
configFile: `.env.${env}`,
|
|
493
|
-
loader: 'config.py'
|
|
494
|
-
};
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
/**
|
|
498
|
-
* Sets up testing framework
|
|
499
|
-
*/
|
|
500
|
-
async setupTesting(framework = 'pytest') {
|
|
501
|
-
await fs.mkdir(path.join(this.projectRoot, 'tests'), { recursive: true });
|
|
502
|
-
await fs.mkdir(path.join(this.projectRoot, 'tests', 'unit'), { recursive: true });
|
|
503
|
-
await fs.mkdir(path.join(this.projectRoot, 'tests', 'integration'), { recursive: true });
|
|
504
|
-
|
|
505
|
-
// Create pytest.ini
|
|
506
|
-
const pytestConfig = `[pytest]
|
|
507
|
-
testpaths = tests
|
|
508
|
-
python_files = test_*.py
|
|
509
|
-
python_classes = Test*
|
|
510
|
-
python_functions = test_*
|
|
511
|
-
addopts = -v --tb=short --strict-markers --cov=app --cov-report=term-missing
|
|
512
|
-
markers =
|
|
513
|
-
unit: Unit tests
|
|
514
|
-
integration: Integration tests
|
|
515
|
-
slow: Slow tests
|
|
516
|
-
`;
|
|
517
|
-
|
|
518
|
-
// Create conftest.py
|
|
519
|
-
const conftest = `import pytest
|
|
520
|
-
from fastapi.testclient import TestClient
|
|
521
|
-
from sqlalchemy import create_engine
|
|
522
|
-
from sqlalchemy.orm import sessionmaker
|
|
523
|
-
|
|
524
|
-
@pytest.fixture
|
|
525
|
-
def client():
|
|
526
|
-
from main import app
|
|
527
|
-
return TestClient(app)
|
|
528
|
-
|
|
529
|
-
@pytest.fixture
|
|
530
|
-
def db_session():
|
|
531
|
-
# Create test database session
|
|
532
|
-
engine = create_engine("sqlite:///:memory:")
|
|
533
|
-
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
|
534
|
-
session = SessionLocal()
|
|
535
|
-
yield session
|
|
536
|
-
session.close()
|
|
537
|
-
`;
|
|
538
|
-
|
|
539
|
-
// Sample test
|
|
540
|
-
const sampleTest = `import pytest
|
|
541
|
-
|
|
542
|
-
def test_health_check(client):
|
|
543
|
-
response = client.get("/health")
|
|
544
|
-
assert response.status_code == 200
|
|
545
|
-
assert response.json() == {"status": "healthy"}
|
|
546
|
-
|
|
547
|
-
def test_root(client):
|
|
548
|
-
response = client.get("/")
|
|
549
|
-
assert response.status_code == 200
|
|
550
|
-
assert "message" in response.json()
|
|
551
|
-
|
|
552
|
-
@pytest.mark.unit
|
|
553
|
-
def test_example_unit():
|
|
554
|
-
assert 1 + 1 == 2
|
|
555
|
-
|
|
556
|
-
@pytest.mark.integration
|
|
557
|
-
def test_example_integration(db_session):
|
|
558
|
-
# Test with database
|
|
559
|
-
assert db_session is not None
|
|
560
|
-
`;
|
|
561
|
-
|
|
562
|
-
await fs.writeFile(path.join(this.projectRoot, 'pytest.ini'), pytestConfig);
|
|
563
|
-
await fs.writeFile(path.join(this.projectRoot, 'tests', '__init__.py'), '');
|
|
564
|
-
await fs.writeFile(path.join(this.projectRoot, 'tests', 'conftest.py'), conftest);
|
|
565
|
-
await fs.writeFile(path.join(this.projectRoot, 'tests', 'test_main.py'), sampleTest);
|
|
566
|
-
|
|
567
|
-
return {
|
|
568
|
-
framework,
|
|
569
|
-
config: 'pytest.ini',
|
|
570
|
-
testsDir: 'tests/',
|
|
571
|
-
fixtures: 'conftest.py'
|
|
572
|
-
};
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
module.exports = PythonScaffoldManager;
|