create-backlist 5.0.7 ā 6.0.2
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/bin/backlist.js +227 -0
- package/package.json +10 -4
- package/src/analyzer.js +210 -89
- package/src/db/prisma.ts +4 -0
- package/src/generators/dotnet.js +120 -94
- package/src/generators/java.js +205 -75
- package/src/generators/node.js +262 -85
- package/src/generators/python.js +54 -25
- package/src/generators/template.js +38 -2
- package/src/scanner/index.js +99 -0
- package/src/templates/dotnet/partials/Controller.cs.ejs +7 -14
- package/src/templates/dotnet/partials/Dto.cs.ejs +8 -0
- package/src/templates/java-spring/partials/ApplicationSeeder.java.ejs +30 -0
- package/src/templates/java-spring/partials/AuthController.java.ejs +62 -0
- package/src/templates/java-spring/partials/Controller.java.ejs +40 -50
- package/src/templates/java-spring/partials/Dockerfile.ejs +16 -0
- package/src/templates/java-spring/partials/Entity.java.ejs +16 -15
- package/src/templates/java-spring/partials/JwtAuthFilter.java.ejs +66 -0
- package/src/templates/java-spring/partials/JwtService.java.ejs +58 -0
- package/src/templates/java-spring/partials/Repository.java.ejs +9 -3
- package/src/templates/java-spring/partials/SecurityConfig.java.ejs +44 -0
- package/src/templates/java-spring/partials/Service.java.ejs +69 -0
- package/src/templates/java-spring/partials/User.java.ejs +33 -0
- package/src/templates/java-spring/partials/UserDetailsServiceImpl.java.ejs +33 -0
- package/src/templates/java-spring/partials/UserRepository.java.ejs +20 -0
- package/src/templates/java-spring/partials/docker-compose.yml.ejs +35 -0
- package/src/templates/node-ts-express/base/server.ts +12 -5
- package/src/templates/node-ts-express/base/tsconfig.json +13 -3
- package/src/templates/node-ts-express/partials/ApiDocs.ts.ejs +17 -7
- package/src/templates/node-ts-express/partials/App.test.ts.ejs +27 -27
- package/src/templates/node-ts-express/partials/Auth.controller.ts.ejs +56 -62
- package/src/templates/node-ts-express/partials/Auth.middleware.ts.ejs +21 -10
- package/src/templates/node-ts-express/partials/Controller.ts.ejs +40 -40
- package/src/templates/node-ts-express/partials/DbContext.cs.ejs +3 -3
- package/src/templates/node-ts-express/partials/Dockerfile.ejs +9 -11
- package/src/templates/node-ts-express/partials/Model.cs.ejs +25 -7
- package/src/templates/node-ts-express/partials/Model.ts.ejs +20 -12
- package/src/templates/node-ts-express/partials/PrismaController.ts.ejs +72 -55
- package/src/templates/node-ts-express/partials/PrismaSchema.prisma.ejs +27 -12
- package/src/templates/node-ts-express/partials/README.md.ejs +9 -12
- package/src/templates/node-ts-express/partials/Seeder.ts.ejs +44 -64
- package/src/templates/node-ts-express/partials/docker-compose.yml.ejs +31 -16
- package/src/templates/node-ts-express/partials/package.json.ejs +3 -1
- package/src/templates/node-ts-express/partials/prismaClient.ts.ejs +4 -0
- package/src/templates/node-ts-express/partials/routes.ts.ejs +35 -24
- package/src/templates/python-fastapi/Dockerfile.ejs +8 -0
- package/src/templates/python-fastapi/app/core/config.py.ejs +8 -0
- package/src/templates/python-fastapi/app/core/security.py.ejs +8 -0
- package/src/templates/python-fastapi/app/db.py.ejs +7 -0
- package/src/templates/python-fastapi/app/main.py.ejs +24 -0
- package/src/templates/python-fastapi/app/models/user.py.ejs +9 -0
- package/src/templates/python-fastapi/app/routers/auth.py.ejs +33 -0
- package/src/templates/python-fastapi/app/routers/model_routes.py.ejs +72 -0
- package/src/templates/python-fastapi/app/schemas/user.py.ejs +16 -0
- package/src/templates/python-fastapi/docker-compose.yml.ejs +19 -0
- package/src/templates/python-fastapi/requirements.txt.ejs +5 -1
- package/src/utils.js +19 -4
- package/bin/index.js +0 -141
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
from sqlalchemy import create_engine
|
|
2
|
+
from sqlalchemy.orm import sessionmaker, declarative_base
|
|
3
|
+
from app.core.config import settings
|
|
4
|
+
|
|
5
|
+
engine = create_engine(settings.DB_URL)
|
|
6
|
+
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
|
7
|
+
Base = declarative_base()
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from fastapi import FastAPI
|
|
2
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
3
|
+
from app.db import Base, engine
|
|
4
|
+
from app.models.user import User
|
|
5
|
+
from app.routers import auth as auth_routes
|
|
6
|
+
<% (controllers || []).forEach(c => { %>from app.routers import <%= c.toLowerCase() %>_routes
|
|
7
|
+
<% }) %>
|
|
8
|
+
|
|
9
|
+
app = FastAPI(title="<%= projectName %>")
|
|
10
|
+
|
|
11
|
+
app.add_middleware(
|
|
12
|
+
CORSMiddleware,
|
|
13
|
+
allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"],
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
Base.metadata.create_all(bind=engine)
|
|
17
|
+
|
|
18
|
+
@app.get("/")
|
|
19
|
+
def root():
|
|
20
|
+
return {"ok": True, "message": "FastAPI backend generated by create-backlist"}
|
|
21
|
+
|
|
22
|
+
app.include_router(auth_routes.router)
|
|
23
|
+
<% (controllers || []).forEach(c => { %>app.include_router(<%= c.toLowerCase() %>_routes.router)
|
|
24
|
+
<% }) %>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from sqlalchemy import Column, Integer, String
|
|
2
|
+
from app.db import Base
|
|
3
|
+
|
|
4
|
+
class User(Base):
|
|
5
|
+
__tablename__ = "users"
|
|
6
|
+
id = Column(Integer, primary_key=True, index=True)
|
|
7
|
+
name = Column(String)
|
|
8
|
+
email = Column(String, unique=True, index=True)
|
|
9
|
+
password = Column(String)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from fastapi import APIRouter, Depends, HTTPException, status
|
|
2
|
+
from sqlalchemy.orm import Session
|
|
3
|
+
from passlib.hash import bcrypt
|
|
4
|
+
from app.db import SessionLocal
|
|
5
|
+
from app.models.user import User
|
|
6
|
+
from app.schemas.user import UserCreate, Token
|
|
7
|
+
from app.core.security import create_token
|
|
8
|
+
|
|
9
|
+
router = APIRouter(prefix="/api/auth", tags=["Auth"])
|
|
10
|
+
|
|
11
|
+
def get_db():
|
|
12
|
+
db = SessionLocal()
|
|
13
|
+
try:
|
|
14
|
+
yield db
|
|
15
|
+
finally:
|
|
16
|
+
db.close()
|
|
17
|
+
|
|
18
|
+
@router.post("/register", response_model=Token, status_code=status.HTTP_201_CREATED)
|
|
19
|
+
def register(payload: UserCreate, db: Session = Depends(get_db)):
|
|
20
|
+
if db.query(User).filter(User.email == payload.email).first():
|
|
21
|
+
raise HTTPException(status_code=400, detail="User already exists")
|
|
22
|
+
u = User(name=payload.name, email=payload.email, password=bcrypt.hash(payload.password))
|
|
23
|
+
db.add(u)
|
|
24
|
+
db.commit()
|
|
25
|
+
db.refresh(u)
|
|
26
|
+
return {"token": create_token(u.email)}
|
|
27
|
+
|
|
28
|
+
@router.post("/login", response_model=Token)
|
|
29
|
+
def login(payload: UserCreate, db: Session = Depends(get_db)):
|
|
30
|
+
u = db.query(User).filter(User.email == payload.email).first()
|
|
31
|
+
if not u or not bcrypt.verify(payload.password, u.password):
|
|
32
|
+
raise HTTPException(status_code=400, detail="Invalid credentials")
|
|
33
|
+
return {"token": create_token(u.email)}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from fastapi import APIRouter, Depends, HTTPException, status
|
|
2
|
+
from typing import List
|
|
3
|
+
from sqlalchemy.orm import Session
|
|
4
|
+
from app.db import SessionLocal
|
|
5
|
+
from pydantic import BaseModel
|
|
6
|
+
|
|
7
|
+
# Build schema from fields
|
|
8
|
+
<% if (schema && schema.fields) { %>
|
|
9
|
+
class <%= modelName %>Base(BaseModel):
|
|
10
|
+
<% schema.fields.forEach(f => { %>
|
|
11
|
+
<%= f.name %>: <%= f.type === 'Number' ? 'int' : 'str' %>
|
|
12
|
+
<% }) %>
|
|
13
|
+
|
|
14
|
+
class <%= modelName %>Create(<%= modelName %>Base):
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
class <%= modelName %>(<%= modelName %>Base):
|
|
18
|
+
id: int
|
|
19
|
+
class Config:
|
|
20
|
+
orm_mode = True
|
|
21
|
+
<% } %>
|
|
22
|
+
|
|
23
|
+
router = APIRouter(prefix="/api/<%= modelName.toLowerCase() %>s", tags=["<%= modelName %>s"])
|
|
24
|
+
|
|
25
|
+
def get_db():
|
|
26
|
+
db = SessionLocal()
|
|
27
|
+
try:
|
|
28
|
+
yield db
|
|
29
|
+
finally:
|
|
30
|
+
db.close()
|
|
31
|
+
|
|
32
|
+
# NOTE: For brevity this uses a generic in-memory table replacement.
|
|
33
|
+
# In a full implementation you'd generate SQLAlchemy model files per entity.
|
|
34
|
+
|
|
35
|
+
_db = []
|
|
36
|
+
_counter = 0
|
|
37
|
+
|
|
38
|
+
@router.get("/", response_model=List[<%= modelName %>])
|
|
39
|
+
def list_items():
|
|
40
|
+
return _db
|
|
41
|
+
|
|
42
|
+
@router.post("/", response_model=<%= modelName %>, status_code=status.HTTP_201_CREATED)
|
|
43
|
+
def create_item(payload: <%= modelName %>Create):
|
|
44
|
+
global _counter
|
|
45
|
+
_counter += 1
|
|
46
|
+
item = {"id": _counter, **payload.dict()}
|
|
47
|
+
_db.append(item)
|
|
48
|
+
return item
|
|
49
|
+
|
|
50
|
+
@router.get("/{item_id}", response_model=<%= modelName %>)
|
|
51
|
+
def get_item(item_id: int):
|
|
52
|
+
for it in _db:
|
|
53
|
+
if it["id"] == item_id:
|
|
54
|
+
return it
|
|
55
|
+
raise HTTPException(status_code=404, detail="Not found")
|
|
56
|
+
|
|
57
|
+
@router.put("/{item_id}", response_model=<%= modelName %>)
|
|
58
|
+
def update_item(item_id: int, payload: <%= modelName %>Create):
|
|
59
|
+
for idx, it in enumerate(_db):
|
|
60
|
+
if it["id"] == item_id:
|
|
61
|
+
new_it = {"id": item_id, **payload.dict()}
|
|
62
|
+
_db[idx] = new_it
|
|
63
|
+
return new_it
|
|
64
|
+
raise HTTPException(status_code=404, detail="Not found")
|
|
65
|
+
|
|
66
|
+
@router.delete("/{item_id}", status_code=status.HTTP_204_NO_CONTENT)
|
|
67
|
+
def delete_item(item_id: int):
|
|
68
|
+
for idx, it in enumerate(_db):
|
|
69
|
+
if it["id"] == item_id:
|
|
70
|
+
_db.pop(idx)
|
|
71
|
+
return
|
|
72
|
+
raise HTTPException(status_code=404, detail="Not found")
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from pydantic import BaseModel
|
|
2
|
+
|
|
3
|
+
class UserBase(BaseModel):
|
|
4
|
+
name: str
|
|
5
|
+
email: str
|
|
6
|
+
|
|
7
|
+
class UserCreate(UserBase):
|
|
8
|
+
password: str
|
|
9
|
+
|
|
10
|
+
class UserOut(UserBase):
|
|
11
|
+
id: int
|
|
12
|
+
class Config:
|
|
13
|
+
orm_mode = True
|
|
14
|
+
|
|
15
|
+
class Token(BaseModel):
|
|
16
|
+
token: str
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
version: '3.8'
|
|
2
|
+
services:
|
|
3
|
+
db:
|
|
4
|
+
image: postgres:16-alpine
|
|
5
|
+
environment:
|
|
6
|
+
POSTGRES_USER: ${DB_USER:-postgres}
|
|
7
|
+
POSTGRES_PASSWORD: ${DB_PASSWORD:-password}
|
|
8
|
+
POSTGRES_DB: ${DB_NAME:-<%= projectName %>}
|
|
9
|
+
ports: ["5432:5432"]
|
|
10
|
+
volumes: [pgdata:/var/lib/postgresql/data]
|
|
11
|
+
api:
|
|
12
|
+
build: .
|
|
13
|
+
environment:
|
|
14
|
+
- DB_URL=postgresql://postgres:password@db:5432/<%= projectName %>
|
|
15
|
+
- JWT_SECRET=change_me_super_secret
|
|
16
|
+
ports: ["8000:8000"]
|
|
17
|
+
depends_on: [db]
|
|
18
|
+
volumes:
|
|
19
|
+
pgdata:
|
package/src/utils.js
CHANGED
|
@@ -1,12 +1,27 @@
|
|
|
1
1
|
const { execa } = require('execa');
|
|
2
2
|
|
|
3
|
+
const VERSION_ARGS = {
|
|
4
|
+
java: ['-version'],
|
|
5
|
+
python: ['--version'],
|
|
6
|
+
python3: ['--version'],
|
|
7
|
+
node: ['--version'],
|
|
8
|
+
npm: ['--version'],
|
|
9
|
+
dotnet: ['--version'],
|
|
10
|
+
mvn: ['-v'],
|
|
11
|
+
git: ['--version'],
|
|
12
|
+
};
|
|
13
|
+
|
|
3
14
|
async function isCommandAvailable(command) {
|
|
15
|
+
const args = VERSION_ARGS[command] || ['--version'];
|
|
16
|
+
|
|
4
17
|
try {
|
|
5
|
-
//
|
|
6
|
-
|
|
7
|
-
await execa(command, [checkCommand]);
|
|
18
|
+
// Reject false only if spawn fails (ENOENT). Non-zero exit still counts as "available".
|
|
19
|
+
await execa(command, args, { reject: false });
|
|
8
20
|
return true;
|
|
9
|
-
} catch {
|
|
21
|
+
} catch (err) {
|
|
22
|
+
// If command not found, execa throws with code 'ENOENT'
|
|
23
|
+
if (err && err.code === 'ENOENT') return false;
|
|
24
|
+
// Other unexpected errors: treat as not available
|
|
10
25
|
return false;
|
|
11
26
|
}
|
|
12
27
|
}
|
package/bin/index.js
DELETED
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const inquirer = require('inquirer');
|
|
4
|
-
const chalk = require('chalk');
|
|
5
|
-
const fs = require('fs-extra');
|
|
6
|
-
const path = require('path'); // FIX: Correctly require the 'path' module
|
|
7
|
-
const { isCommandAvailable } = require('../src/utils');
|
|
8
|
-
|
|
9
|
-
// Import ALL generators
|
|
10
|
-
const { generateNodeProject } = require('../src/generators/node');
|
|
11
|
-
const { generateDotnetProject } = require('../src/generators/dotnet');
|
|
12
|
-
const { generateJavaProject } = require('../src/generators/java');
|
|
13
|
-
const { generatePythonProject } = require('../src/generators/python');
|
|
14
|
-
|
|
15
|
-
async function main() {
|
|
16
|
-
console.log(chalk.cyan.bold('š Welcome to Backlist! The Polyglot Backend Generator.'));
|
|
17
|
-
|
|
18
|
-
const answers = await inquirer.prompt([
|
|
19
|
-
// --- General Questions ---
|
|
20
|
-
{
|
|
21
|
-
type: 'input',
|
|
22
|
-
name: 'projectName',
|
|
23
|
-
message: 'Enter a name for your backend directory:',
|
|
24
|
-
default: 'backend',
|
|
25
|
-
validate: input => input ? true : 'Project name cannot be empty.'
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
type: 'list',
|
|
29
|
-
name: 'stack',
|
|
30
|
-
message: 'Select the backend stack:',
|
|
31
|
-
choices: [
|
|
32
|
-
{ name: 'Node.js (TypeScript, Express)', value: 'node-ts-express' },
|
|
33
|
-
{ name: 'C# (ASP.NET Core Web API)', value: 'dotnet-webapi' },
|
|
34
|
-
{ name: 'Java (Spring Boot)', value: 'java-spring' },
|
|
35
|
-
{ name: 'Python (FastAPI)', value: 'python-fastapi' },
|
|
36
|
-
],
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
type: 'input',
|
|
40
|
-
name: 'srcPath',
|
|
41
|
-
message: 'Enter the path to your frontend `src` directory:',
|
|
42
|
-
default: 'src',
|
|
43
|
-
},
|
|
44
|
-
|
|
45
|
-
// --- Node.js Specific Questions ---
|
|
46
|
-
{
|
|
47
|
-
type: 'list',
|
|
48
|
-
name: 'dbType',
|
|
49
|
-
message: 'Select your database type for Node.js:',
|
|
50
|
-
choices: [
|
|
51
|
-
{ name: 'NoSQL (MongoDB with Mongoose)', value: 'mongoose' },
|
|
52
|
-
{ name: 'SQL (PostgreSQL/MySQL with Prisma)', value: 'prisma' },
|
|
53
|
-
],
|
|
54
|
-
when: (answers) => answers.stack === 'node-ts-express'
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
type: 'confirm',
|
|
58
|
-
name: 'addAuth',
|
|
59
|
-
message: 'Add JWT authentication boilerplate?',
|
|
60
|
-
default: true,
|
|
61
|
-
when: (answers) => answers.stack === 'node-ts-express'
|
|
62
|
-
},
|
|
63
|
-
{
|
|
64
|
-
type: 'confirm',
|
|
65
|
-
name: 'addSeeder',
|
|
66
|
-
message: 'Add a database seeder with sample data?',
|
|
67
|
-
default: true,
|
|
68
|
-
// Seeder only makes sense if there's an auth/user model to seed
|
|
69
|
-
when: (answers) => answers.stack === 'node-ts-express' && answers.addAuth
|
|
70
|
-
},
|
|
71
|
-
{
|
|
72
|
-
type: 'checkbox',
|
|
73
|
-
name: 'extraFeatures',
|
|
74
|
-
message: 'Select additional features for Node.js:',
|
|
75
|
-
choices: [
|
|
76
|
-
{ name: 'Docker Support (Dockerfile & docker-compose.yml)', value: 'docker', checked: true },
|
|
77
|
-
{ name: 'API Testing Boilerplate (Jest & Supertest)', value: 'testing', checked: true },
|
|
78
|
-
{ name: 'API Documentation (Swagger UI)', value: 'swagger', checked: true },
|
|
79
|
-
],
|
|
80
|
-
when: (answers) => answers.stack === 'node-ts-express'
|
|
81
|
-
}
|
|
82
|
-
]);
|
|
83
|
-
|
|
84
|
-
const options = {
|
|
85
|
-
...answers,
|
|
86
|
-
projectDir: path.resolve(process.cwd(), answers.projectName),
|
|
87
|
-
frontendSrcDir: path.resolve(process.cwd(), answers.srcPath),
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
try {
|
|
91
|
-
console.log(chalk.blue(`\n⨠Starting backend generation for: ${chalk.bold(options.stack)}`));
|
|
92
|
-
|
|
93
|
-
// --- Dispatcher Logic for ALL Stacks ---
|
|
94
|
-
switch (options.stack) {
|
|
95
|
-
case 'node-ts-express':
|
|
96
|
-
await generateNodeProject(options);
|
|
97
|
-
break;
|
|
98
|
-
|
|
99
|
-
case 'dotnet-webapi':
|
|
100
|
-
if (!await isCommandAvailable('dotnet')) {
|
|
101
|
-
throw new Error('.NET SDK is not installed. Please install it from https://dotnet.microsoft.com/download');
|
|
102
|
-
}
|
|
103
|
-
await generateDotnetProject(options);
|
|
104
|
-
break;
|
|
105
|
-
|
|
106
|
-
case 'java-spring':
|
|
107
|
-
if (!await isCommandAvailable('java')) {
|
|
108
|
-
throw new Error('Java (JDK 17 or newer) is not installed. Please install a JDK to continue.');
|
|
109
|
-
}
|
|
110
|
-
await generateJavaProject(options);
|
|
111
|
-
break;
|
|
112
|
-
|
|
113
|
-
case 'python-fastapi':
|
|
114
|
-
if (!await isCommandAvailable('python')) {
|
|
115
|
-
throw new Error('Python is not installed. Please install Python (3.8+) and pip to continue.');
|
|
116
|
-
}
|
|
117
|
-
await generatePythonProject(options);
|
|
118
|
-
break;
|
|
119
|
-
|
|
120
|
-
default:
|
|
121
|
-
throw new Error(`The selected stack '${options.stack}' is not supported yet.`);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
console.log(chalk.green.bold('\nā
Backend generation complete!'));
|
|
125
|
-
console.log('\nNext Steps:');
|
|
126
|
-
console.log(chalk.cyan(` cd ${options.projectName}`));
|
|
127
|
-
console.log(chalk.cyan(' (Check the generated README.md for instructions)'));
|
|
128
|
-
|
|
129
|
-
} catch (error) {
|
|
130
|
-
console.error(chalk.red.bold('\nā An error occurred during generation:'));
|
|
131
|
-
console.error(error);
|
|
132
|
-
|
|
133
|
-
if (fs.existsSync(options.projectDir)) {
|
|
134
|
-
console.log(chalk.yellow(' -> Cleaning up failed installation...'));
|
|
135
|
-
fs.removeSync(options.projectDir);
|
|
136
|
-
}
|
|
137
|
-
process.exit(1);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
main();
|