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.
Files changed (58) hide show
  1. package/bin/backlist.js +227 -0
  2. package/package.json +10 -4
  3. package/src/analyzer.js +210 -89
  4. package/src/db/prisma.ts +4 -0
  5. package/src/generators/dotnet.js +120 -94
  6. package/src/generators/java.js +205 -75
  7. package/src/generators/node.js +262 -85
  8. package/src/generators/python.js +54 -25
  9. package/src/generators/template.js +38 -2
  10. package/src/scanner/index.js +99 -0
  11. package/src/templates/dotnet/partials/Controller.cs.ejs +7 -14
  12. package/src/templates/dotnet/partials/Dto.cs.ejs +8 -0
  13. package/src/templates/java-spring/partials/ApplicationSeeder.java.ejs +30 -0
  14. package/src/templates/java-spring/partials/AuthController.java.ejs +62 -0
  15. package/src/templates/java-spring/partials/Controller.java.ejs +40 -50
  16. package/src/templates/java-spring/partials/Dockerfile.ejs +16 -0
  17. package/src/templates/java-spring/partials/Entity.java.ejs +16 -15
  18. package/src/templates/java-spring/partials/JwtAuthFilter.java.ejs +66 -0
  19. package/src/templates/java-spring/partials/JwtService.java.ejs +58 -0
  20. package/src/templates/java-spring/partials/Repository.java.ejs +9 -3
  21. package/src/templates/java-spring/partials/SecurityConfig.java.ejs +44 -0
  22. package/src/templates/java-spring/partials/Service.java.ejs +69 -0
  23. package/src/templates/java-spring/partials/User.java.ejs +33 -0
  24. package/src/templates/java-spring/partials/UserDetailsServiceImpl.java.ejs +33 -0
  25. package/src/templates/java-spring/partials/UserRepository.java.ejs +20 -0
  26. package/src/templates/java-spring/partials/docker-compose.yml.ejs +35 -0
  27. package/src/templates/node-ts-express/base/server.ts +12 -5
  28. package/src/templates/node-ts-express/base/tsconfig.json +13 -3
  29. package/src/templates/node-ts-express/partials/ApiDocs.ts.ejs +17 -7
  30. package/src/templates/node-ts-express/partials/App.test.ts.ejs +27 -27
  31. package/src/templates/node-ts-express/partials/Auth.controller.ts.ejs +56 -62
  32. package/src/templates/node-ts-express/partials/Auth.middleware.ts.ejs +21 -10
  33. package/src/templates/node-ts-express/partials/Controller.ts.ejs +40 -40
  34. package/src/templates/node-ts-express/partials/DbContext.cs.ejs +3 -3
  35. package/src/templates/node-ts-express/partials/Dockerfile.ejs +9 -11
  36. package/src/templates/node-ts-express/partials/Model.cs.ejs +25 -7
  37. package/src/templates/node-ts-express/partials/Model.ts.ejs +20 -12
  38. package/src/templates/node-ts-express/partials/PrismaController.ts.ejs +72 -55
  39. package/src/templates/node-ts-express/partials/PrismaSchema.prisma.ejs +27 -12
  40. package/src/templates/node-ts-express/partials/README.md.ejs +9 -12
  41. package/src/templates/node-ts-express/partials/Seeder.ts.ejs +44 -64
  42. package/src/templates/node-ts-express/partials/docker-compose.yml.ejs +31 -16
  43. package/src/templates/node-ts-express/partials/package.json.ejs +3 -1
  44. package/src/templates/node-ts-express/partials/prismaClient.ts.ejs +4 -0
  45. package/src/templates/node-ts-express/partials/routes.ts.ejs +35 -24
  46. package/src/templates/python-fastapi/Dockerfile.ejs +8 -0
  47. package/src/templates/python-fastapi/app/core/config.py.ejs +8 -0
  48. package/src/templates/python-fastapi/app/core/security.py.ejs +8 -0
  49. package/src/templates/python-fastapi/app/db.py.ejs +7 -0
  50. package/src/templates/python-fastapi/app/main.py.ejs +24 -0
  51. package/src/templates/python-fastapi/app/models/user.py.ejs +9 -0
  52. package/src/templates/python-fastapi/app/routers/auth.py.ejs +33 -0
  53. package/src/templates/python-fastapi/app/routers/model_routes.py.ejs +72 -0
  54. package/src/templates/python-fastapi/app/schemas/user.py.ejs +16 -0
  55. package/src/templates/python-fastapi/docker-compose.yml.ejs +19 -0
  56. package/src/templates/python-fastapi/requirements.txt.ejs +5 -1
  57. package/src/utils.js +19 -4
  58. 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:
@@ -1,4 +1,8 @@
1
1
  fastapi
2
2
  uvicorn[standard]
3
+ python-dotenv
4
+ sqlalchemy
5
+ psycopg2-binary
3
6
  pydantic
4
- # Add other dependencies like 'sqlalchemy', 'psycopg2-binary' if using a database
7
+ passlib[bcrypt]
8
+ python-jose[cryptography]
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
- // Using a harmless version command to check for presence
6
- const checkCommand = command === 'java' ? '-version' : '--version';
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();