create-backlist 7.3.0 → 7.4.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/bin/index.js +483 -470
- package/bin/qa.js +103 -0
- package/package.json +7 -3
- package/src/analyzer.js +221 -21
- package/src/env-resolver.js +70 -0
- package/src/generators/dotnet.js +134 -134
- package/src/generators/java.js +248 -248
- package/src/generators/js.js +345 -345
- package/src/generators/nestjs.js +277 -277
- package/src/generators/python.js +86 -86
- package/src/project-detector.js +131 -0
- package/src/qa/qa-engine.js +909 -0
- package/src/templates/dotnet/partials/Dockerfile.ejs +27 -27
- package/src/templates/dotnet/partials/docker-compose.yml.ejs +33 -33
- package/src/templates/js-express/base/server.js +59 -59
- package/src/templates/js-express/partials/Dockerfile.ejs +12 -12
- package/src/templates/js-express/partials/auth.controller.js.ejs +66 -66
- package/src/templates/js-express/partials/auth.middleware.js.ejs +19 -19
- package/src/templates/js-express/partials/auth.routes.js.ejs +9 -9
- package/src/templates/js-express/partials/controller.js.ejs +53 -53
- package/src/templates/js-express/partials/db.js.ejs +19 -19
- package/src/templates/js-express/partials/docker-compose.yml.ejs +46 -46
- package/src/templates/js-express/partials/model.js.ejs +18 -18
- package/src/templates/js-express/partials/package.json.ejs +17 -17
- package/src/templates/js-express/partials/prisma.schema.ejs +21 -21
- package/src/templates/js-express/partials/routes.js.ejs +19 -19
- package/src/templates/js-express/partials/seeder.js.ejs +103 -103
- package/src/templates/js-express/partials/service.js.ejs +51 -51
- package/src/templates/js-express/partials/swagger.js.ejs +30 -30
- package/src/templates/js-express/partials/test.js.ejs +46 -46
- package/src/templates/nestjs/base/app.module.ts +9 -9
- package/src/templates/nestjs/base/main.ts +23 -23
- package/src/templates/nestjs/base/tsconfig.json +21 -21
- package/src/templates/nestjs/partials/auth.controller.ts.ejs +17 -17
- package/src/templates/nestjs/partials/auth.module.ts.ejs +17 -17
- package/src/templates/nestjs/partials/auth.service.ts.ejs +70 -70
- package/src/templates/nestjs/partials/controller.ts.ejs +34 -34
- package/src/templates/nestjs/partials/create-dto.ts.ejs +22 -22
- package/src/templates/nestjs/partials/jwt-guard.ts.ejs +24 -24
- package/src/templates/nestjs/partials/module.ts.ejs +10 -10
- package/src/templates/nestjs/partials/package.json.ejs +27 -27
- package/src/templates/nestjs/partials/prisma.service.ts.ejs +13 -13
- package/src/templates/nestjs/partials/schema.ts.ejs +19 -19
- package/src/templates/nestjs/partials/service.ts.ejs +67 -67
- package/src/templates/nestjs/partials/update-dto.ts.ejs +4 -4
package/src/generators/python.js
CHANGED
|
@@ -1,86 +1,86 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
import { execa } from 'execa';
|
|
3
|
-
import fs from 'fs-extra';
|
|
4
|
-
import path from 'node:path';
|
|
5
|
-
import { analyzeFrontend } from '../analyzer.js';
|
|
6
|
-
import { renderAndWrite, getTemplatePath } from './template.js';
|
|
7
|
-
|
|
8
|
-
export async function generatePythonProject(options) {
|
|
9
|
-
const { projectDir, projectName, frontendSrcDir } = options;
|
|
10
|
-
|
|
11
|
-
try {
|
|
12
|
-
console.log(chalk.blue(' -> Analyzing frontend for Python (FastAPI) backend...'));
|
|
13
|
-
const endpoints = await analyzeFrontend(frontendSrcDir);
|
|
14
|
-
const modelsToGenerate = new Map();
|
|
15
|
-
endpoints.forEach(ep => {
|
|
16
|
-
if (ep.schemaFields && ep.controllerName !== 'Default' && !modelsToGenerate.has(ep.controllerName)) {
|
|
17
|
-
modelsToGenerate.set(ep.controllerName, { name: ep.controllerName, fields: Object.entries(ep.schemaFields).map(([key, type]) => ({ name: key, type })) });
|
|
18
|
-
}
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
if (!modelsToGenerate.has('User')) {
|
|
22
|
-
modelsToGenerate.set('User', { name: 'User', fields: [{ name: 'name', type: 'String' }, { name: 'email', type: 'String' }] });
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
console.log(chalk.blue(' -> Scaffolding Python (FastAPI) project structure...'));
|
|
26
|
-
const appDir = path.join(projectDir, 'app');
|
|
27
|
-
const coreDir = path.join(appDir, 'core');
|
|
28
|
-
const dbDir = path.join(appDir, 'db');
|
|
29
|
-
const modelsDir = path.join(appDir, 'models');
|
|
30
|
-
const schemasDir = path.join(appDir, 'schemas');
|
|
31
|
-
const routesDir = path.join(appDir, 'routers');
|
|
32
|
-
|
|
33
|
-
await fs.ensureDir(appDir);
|
|
34
|
-
await fs.ensureDir(coreDir);
|
|
35
|
-
await fs.ensureDir(dbDir);
|
|
36
|
-
await fs.ensureDir(modelsDir);
|
|
37
|
-
await fs.ensureDir(schemasDir);
|
|
38
|
-
await fs.ensureDir(routesDir);
|
|
39
|
-
|
|
40
|
-
const controllers = Array.from(modelsToGenerate.keys());
|
|
41
|
-
|
|
42
|
-
await renderAndWrite(getTemplatePath('python-fastapi/main.py.ejs'), path.join(projectDir, 'app', 'main.py'), { projectName, controllers });
|
|
43
|
-
await renderAndWrite(getTemplatePath('python-fastapi/requirements.txt.ejs'), path.join(projectDir, 'requirements.txt'), {});
|
|
44
|
-
|
|
45
|
-
await renderAndWrite(getTemplatePath('python-fastapi/app/core/config.py.ejs'), path.join(coreDir, 'config.py'), { projectName });
|
|
46
|
-
await renderAndWrite(getTemplatePath('python-fastapi/app/core/security.py.ejs'), path.join(coreDir, 'security.py'), {});
|
|
47
|
-
|
|
48
|
-
await renderAndWrite(getTemplatePath('python-fastapi/app/db.py.ejs'), path.join(appDir, 'db.py'), {});
|
|
49
|
-
|
|
50
|
-
await renderAndWrite(getTemplatePath('python-fastapi/app/models/user.py.ejs'), path.join(modelsDir, 'user.py'), {});
|
|
51
|
-
await renderAndWrite(getTemplatePath('python-fastapi/app/schemas/user.py.ejs'), path.join(schemasDir, 'user.py'), {});
|
|
52
|
-
|
|
53
|
-
await renderAndWrite(getTemplatePath('python-fastapi/app/routers/auth.py.ejs'), path.join(routesDir, 'auth.py'), {});
|
|
54
|
-
|
|
55
|
-
for (const [modelName, modelData] of modelsToGenerate.entries()) {
|
|
56
|
-
if (modelName.toLowerCase() !== 'user') {
|
|
57
|
-
await renderAndWrite(getTemplatePath('python-fastapi/app/routers/model_routes.py.ejs'), path.join(routesDir, `${modelName.toLowerCase()}_routes.py`), { modelName, schema: modelData });
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
console.log(chalk.magenta(' -> Setting up virtual environment and installing dependencies...'));
|
|
62
|
-
await execa('python', ['-m', 'venv', 'venv'], { cwd: projectDir });
|
|
63
|
-
|
|
64
|
-
const pipPath = process.platform === 'win32' ? path.join('venv', 'Scripts', 'pip') : path.join('venv', 'bin', 'pip');
|
|
65
|
-
await execa(path.join(projectDir, pipPath), ['install', '-r', 'requirements.txt'], { cwd: projectDir });
|
|
66
|
-
|
|
67
|
-
await renderAndWrite(getTemplatePath('python-fastapi/Dockerfile.ejs'), path.join(projectDir, 'Dockerfile'), {});
|
|
68
|
-
await renderAndWrite(getTemplatePath('python-fastapi/docker-compose.yml.ejs'), path.join(projectDir, 'docker-compose.yml'), { projectName });
|
|
69
|
-
|
|
70
|
-
const envContent = `DATABASE_URL="postgresql://postgres:password@db:5432/${projectName}"\nJWT_SECRET="a_very_secret_key_change_this"`;
|
|
71
|
-
await fs.writeFile(path.join(projectDir, '.env'), envContent);
|
|
72
|
-
await fs.writeFile(path.join(projectDir, '.env.example'), envContent);
|
|
73
|
-
|
|
74
|
-
console.log(chalk.green(' -> Python (FastAPI) backend generation is complete!'));
|
|
75
|
-
console.log(chalk.yellow('\nTo run your new Python backend with Docker:'));
|
|
76
|
-
console.log(chalk.cyan(' 1. Make sure Docker Desktop is running.'));
|
|
77
|
-
console.log(chalk.cyan(' 2. Run: `docker-compose up --build`'));
|
|
78
|
-
console.log(chalk.cyan(' 3. API will be available at http://localhost:8000 and docs at http://localhost:8000/docs'));
|
|
79
|
-
|
|
80
|
-
} catch (error) {
|
|
81
|
-
if (error.code === 'ENOENT') {
|
|
82
|
-
throw new Error(`'${error.command}' command not found. Please ensure Python and venv are installed and in your system's PATH.`);
|
|
83
|
-
}
|
|
84
|
-
throw error;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { execa } from 'execa';
|
|
3
|
+
import fs from 'fs-extra';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { analyzeFrontend } from '../analyzer.js';
|
|
6
|
+
import { renderAndWrite, getTemplatePath } from './template.js';
|
|
7
|
+
|
|
8
|
+
export async function generatePythonProject(options) {
|
|
9
|
+
const { projectDir, projectName, frontendSrcDir } = options;
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
console.log(chalk.blue(' -> Analyzing frontend for Python (FastAPI) backend...'));
|
|
13
|
+
const endpoints = await analyzeFrontend(frontendSrcDir);
|
|
14
|
+
const modelsToGenerate = new Map();
|
|
15
|
+
endpoints.forEach(ep => {
|
|
16
|
+
if (ep.schemaFields && ep.controllerName !== 'Default' && !modelsToGenerate.has(ep.controllerName)) {
|
|
17
|
+
modelsToGenerate.set(ep.controllerName, { name: ep.controllerName, fields: Object.entries(ep.schemaFields).map(([key, type]) => ({ name: key, type })) });
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
if (!modelsToGenerate.has('User')) {
|
|
22
|
+
modelsToGenerate.set('User', { name: 'User', fields: [{ name: 'name', type: 'String' }, { name: 'email', type: 'String' }] });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
console.log(chalk.blue(' -> Scaffolding Python (FastAPI) project structure...'));
|
|
26
|
+
const appDir = path.join(projectDir, 'app');
|
|
27
|
+
const coreDir = path.join(appDir, 'core');
|
|
28
|
+
const dbDir = path.join(appDir, 'db');
|
|
29
|
+
const modelsDir = path.join(appDir, 'models');
|
|
30
|
+
const schemasDir = path.join(appDir, 'schemas');
|
|
31
|
+
const routesDir = path.join(appDir, 'routers');
|
|
32
|
+
|
|
33
|
+
await fs.ensureDir(appDir);
|
|
34
|
+
await fs.ensureDir(coreDir);
|
|
35
|
+
await fs.ensureDir(dbDir);
|
|
36
|
+
await fs.ensureDir(modelsDir);
|
|
37
|
+
await fs.ensureDir(schemasDir);
|
|
38
|
+
await fs.ensureDir(routesDir);
|
|
39
|
+
|
|
40
|
+
const controllers = Array.from(modelsToGenerate.keys());
|
|
41
|
+
|
|
42
|
+
await renderAndWrite(getTemplatePath('python-fastapi/main.py.ejs'), path.join(projectDir, 'app', 'main.py'), { projectName, controllers });
|
|
43
|
+
await renderAndWrite(getTemplatePath('python-fastapi/requirements.txt.ejs'), path.join(projectDir, 'requirements.txt'), {});
|
|
44
|
+
|
|
45
|
+
await renderAndWrite(getTemplatePath('python-fastapi/app/core/config.py.ejs'), path.join(coreDir, 'config.py'), { projectName });
|
|
46
|
+
await renderAndWrite(getTemplatePath('python-fastapi/app/core/security.py.ejs'), path.join(coreDir, 'security.py'), {});
|
|
47
|
+
|
|
48
|
+
await renderAndWrite(getTemplatePath('python-fastapi/app/db.py.ejs'), path.join(appDir, 'db.py'), {});
|
|
49
|
+
|
|
50
|
+
await renderAndWrite(getTemplatePath('python-fastapi/app/models/user.py.ejs'), path.join(modelsDir, 'user.py'), {});
|
|
51
|
+
await renderAndWrite(getTemplatePath('python-fastapi/app/schemas/user.py.ejs'), path.join(schemasDir, 'user.py'), {});
|
|
52
|
+
|
|
53
|
+
await renderAndWrite(getTemplatePath('python-fastapi/app/routers/auth.py.ejs'), path.join(routesDir, 'auth.py'), {});
|
|
54
|
+
|
|
55
|
+
for (const [modelName, modelData] of modelsToGenerate.entries()) {
|
|
56
|
+
if (modelName.toLowerCase() !== 'user') {
|
|
57
|
+
await renderAndWrite(getTemplatePath('python-fastapi/app/routers/model_routes.py.ejs'), path.join(routesDir, `${modelName.toLowerCase()}_routes.py`), { modelName, schema: modelData });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
console.log(chalk.magenta(' -> Setting up virtual environment and installing dependencies...'));
|
|
62
|
+
await execa('python', ['-m', 'venv', 'venv'], { cwd: projectDir });
|
|
63
|
+
|
|
64
|
+
const pipPath = process.platform === 'win32' ? path.join('venv', 'Scripts', 'pip') : path.join('venv', 'bin', 'pip');
|
|
65
|
+
await execa(path.join(projectDir, pipPath), ['install', '-r', 'requirements.txt'], { cwd: projectDir });
|
|
66
|
+
|
|
67
|
+
await renderAndWrite(getTemplatePath('python-fastapi/Dockerfile.ejs'), path.join(projectDir, 'Dockerfile'), {});
|
|
68
|
+
await renderAndWrite(getTemplatePath('python-fastapi/docker-compose.yml.ejs'), path.join(projectDir, 'docker-compose.yml'), { projectName });
|
|
69
|
+
|
|
70
|
+
const envContent = `DATABASE_URL="postgresql://postgres:password@db:5432/${projectName}"\nJWT_SECRET="a_very_secret_key_change_this"`;
|
|
71
|
+
await fs.writeFile(path.join(projectDir, '.env'), envContent);
|
|
72
|
+
await fs.writeFile(path.join(projectDir, '.env.example'), envContent);
|
|
73
|
+
|
|
74
|
+
console.log(chalk.green(' -> Python (FastAPI) backend generation is complete!'));
|
|
75
|
+
console.log(chalk.yellow('\nTo run your new Python backend with Docker:'));
|
|
76
|
+
console.log(chalk.cyan(' 1. Make sure Docker Desktop is running.'));
|
|
77
|
+
console.log(chalk.cyan(' 2. Run: `docker-compose up --build`'));
|
|
78
|
+
console.log(chalk.cyan(' 3. API will be available at http://localhost:8000 and docs at http://localhost:8000/docs'));
|
|
79
|
+
|
|
80
|
+
} catch (error) {
|
|
81
|
+
if (error.code === 'ENOENT') {
|
|
82
|
+
throw new Error(`'${error.command}' command not found. Please ensure Python and venv are installed and in your system's PATH.`);
|
|
83
|
+
}
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import fs from "fs-extra";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
const NEXTJS_SCAN_DIRS = [
|
|
5
|
+
"app",
|
|
6
|
+
"pages",
|
|
7
|
+
"src",
|
|
8
|
+
"components",
|
|
9
|
+
"lib",
|
|
10
|
+
"hooks",
|
|
11
|
+
"services",
|
|
12
|
+
"utils",
|
|
13
|
+
"features",
|
|
14
|
+
"modules",
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
const REACT_SCAN_DIRS = ["src"];
|
|
18
|
+
|
|
19
|
+
export async function detectProjectType(projectRoot) {
|
|
20
|
+
const result = {
|
|
21
|
+
type: "unknown",
|
|
22
|
+
scanDirs: [],
|
|
23
|
+
apiRouteDirs: [],
|
|
24
|
+
framework: "unknown",
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const pkgPath = path.join(projectRoot, "package.json");
|
|
28
|
+
let pkg = null;
|
|
29
|
+
if (await fs.pathExists(pkgPath)) {
|
|
30
|
+
try {
|
|
31
|
+
pkg = await fs.readJson(pkgPath);
|
|
32
|
+
} catch {}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const deps = pkg
|
|
36
|
+
? { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) }
|
|
37
|
+
: {};
|
|
38
|
+
|
|
39
|
+
const hasNext = !!deps.next || (await hasNextConfig(projectRoot));
|
|
40
|
+
const hasReact = !!deps.react;
|
|
41
|
+
const hasVite = !!deps.vite;
|
|
42
|
+
|
|
43
|
+
const hasAppDir = await fs.pathExists(path.join(projectRoot, "app"));
|
|
44
|
+
const hasPagesDir = await fs.pathExists(path.join(projectRoot, "pages"));
|
|
45
|
+
const hasSrcApp = await fs.pathExists(path.join(projectRoot, "src", "app"));
|
|
46
|
+
const hasSrcPages = await fs.pathExists(
|
|
47
|
+
path.join(projectRoot, "src", "pages")
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
if (hasNext) {
|
|
51
|
+
result.framework = "next";
|
|
52
|
+
|
|
53
|
+
if ((hasAppDir || hasSrcApp) && (hasPagesDir || hasSrcPages)) {
|
|
54
|
+
result.type = "nextjs-hybrid";
|
|
55
|
+
} else if (hasAppDir || hasSrcApp) {
|
|
56
|
+
result.type = "nextjs-app";
|
|
57
|
+
} else if (hasPagesDir || hasSrcPages) {
|
|
58
|
+
result.type = "nextjs-pages";
|
|
59
|
+
} else {
|
|
60
|
+
result.type = "nextjs-app";
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const candidateDirs = [...NEXTJS_SCAN_DIRS];
|
|
64
|
+
for (const dir of candidateDirs) {
|
|
65
|
+
const abs = path.join(projectRoot, dir);
|
|
66
|
+
if (await fs.pathExists(abs)) {
|
|
67
|
+
result.scanDirs.push(abs);
|
|
68
|
+
}
|
|
69
|
+
const srcNested = path.join(projectRoot, "src", dir);
|
|
70
|
+
if (dir !== "src" && (await fs.pathExists(srcNested))) {
|
|
71
|
+
result.scanDirs.push(srcNested);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (hasAppDir) {
|
|
76
|
+
const appApiDir = path.join(projectRoot, "app", "api");
|
|
77
|
+
if (await fs.pathExists(appApiDir)) result.apiRouteDirs.push(appApiDir);
|
|
78
|
+
result.apiRouteDirs.push(path.join(projectRoot, "app"));
|
|
79
|
+
}
|
|
80
|
+
if (hasSrcApp) {
|
|
81
|
+
const srcAppApiDir = path.join(projectRoot, "src", "app", "api");
|
|
82
|
+
if (await fs.pathExists(srcAppApiDir))
|
|
83
|
+
result.apiRouteDirs.push(srcAppApiDir);
|
|
84
|
+
result.apiRouteDirs.push(path.join(projectRoot, "src", "app"));
|
|
85
|
+
}
|
|
86
|
+
if (hasPagesDir) {
|
|
87
|
+
const pagesApiDir = path.join(projectRoot, "pages", "api");
|
|
88
|
+
if (await fs.pathExists(pagesApiDir))
|
|
89
|
+
result.apiRouteDirs.push(pagesApiDir);
|
|
90
|
+
}
|
|
91
|
+
if (hasSrcPages) {
|
|
92
|
+
const srcPagesApiDir = path.join(projectRoot, "src", "pages", "api");
|
|
93
|
+
if (await fs.pathExists(srcPagesApiDir))
|
|
94
|
+
result.apiRouteDirs.push(srcPagesApiDir);
|
|
95
|
+
}
|
|
96
|
+
} else if (hasReact) {
|
|
97
|
+
result.framework = hasVite ? "vite" : "react";
|
|
98
|
+
result.type = hasVite ? "vite-react" : "react";
|
|
99
|
+
|
|
100
|
+
for (const dir of REACT_SCAN_DIRS) {
|
|
101
|
+
const abs = path.join(projectRoot, dir);
|
|
102
|
+
if (await fs.pathExists(abs)) {
|
|
103
|
+
result.scanDirs.push(abs);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (result.scanDirs.length === 0) {
|
|
109
|
+
const fallback = path.join(projectRoot, "src");
|
|
110
|
+
if (await fs.pathExists(fallback)) {
|
|
111
|
+
result.scanDirs.push(fallback);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
result.scanDirs = [...new Set(result.scanDirs)];
|
|
116
|
+
result.apiRouteDirs = [...new Set(result.apiRouteDirs)];
|
|
117
|
+
|
|
118
|
+
return result;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function hasNextConfig(projectRoot) {
|
|
122
|
+
const configs = [
|
|
123
|
+
"next.config.js",
|
|
124
|
+
"next.config.mjs",
|
|
125
|
+
"next.config.ts",
|
|
126
|
+
];
|
|
127
|
+
for (const cfg of configs) {
|
|
128
|
+
if (await fs.pathExists(path.join(projectRoot, cfg))) return true;
|
|
129
|
+
}
|
|
130
|
+
return false;
|
|
131
|
+
}
|