create-backlist 4.0.0 → 5.0.1

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 CHANGED
@@ -11,7 +11,7 @@ const { generateNodeProject } = require('../src/generators/node');
11
11
  const { generateDotnetProject } = require('../src/generators/dotnet');
12
12
 
13
13
  async function main() {
14
- console.log(chalk.cyan.bold('🚀 Welcome to Backlist! The Intelligent Backend Generator.'));
14
+ console.log(chalk.cyan.bold('🚀 Welcome to Backlist! The Production-Ready Backend Generator.'));
15
15
 
16
16
  const answers = await inquirer.prompt([
17
17
  {
@@ -29,37 +29,91 @@ async function main() {
29
29
  { name: 'Node.js (TypeScript, Express)', value: 'node-ts-express' },
30
30
  { name: 'C# (ASP.NET Core Web API)', value: 'dotnet-webapi' },
31
31
  new inquirer.Separator(),
32
- { name: 'Python (FastAPI) - Coming Soon', disabled: true },
33
- { name: 'Java (Spring Boot) - Coming Soon', disabled: true },
32
+ { name: 'Python (FastAPI) - Coming Soon', disabled: true, value: 'python-fastapi' },
33
+ { name: 'Java (Spring Boot) - Coming Soon', disabled: true, value: 'java-spring' },
34
34
  ],
35
35
  },
36
+ // --- V5.0: Database Choice for Node.js ---
37
+ {
38
+ type: 'list',
39
+ name: 'dbType',
40
+ message: 'Select your database type:',
41
+ choices: [
42
+ { name: 'NoSQL (MongoDB with Mongoose)', value: 'mongoose' },
43
+ { name: 'SQL (PostgreSQL/MySQL with Prisma)', value: 'prisma' },
44
+ ],
45
+ when: (answers) => answers.stack === 'node-ts-express'
46
+ },
36
47
  {
37
48
  type: 'input',
38
49
  name: 'srcPath',
39
50
  message: 'Enter the path to your frontend `src` directory:',
40
51
  default: 'src',
41
52
  },
53
+ // --- V3.0: Auth Boilerplate for Node.js ---
42
54
  {
43
55
  type: 'confirm',
44
56
  name: 'addAuth',
45
- message: 'Do you want to add basic JWT authentication? (generates a User model, login/register routes)',
57
+ message: 'Add JWT authentication boilerplate?',
46
58
  default: true,
47
59
  when: (answers) => answers.stack === 'node-ts-express'
48
60
  },
49
- // --- NEW QUESTION FOR V4.0 ---
61
+ // --- V4.0: Seeder for Node.js ---
50
62
  {
51
63
  type: 'confirm',
52
64
  name: 'addSeeder',
53
- message: 'Do you want to add a database seeder with sample user data?',
65
+ message: 'Add a database seeder with sample data?',
54
66
  default: true,
55
- // Only ask this if Node.js is selected AND authentication is being added
56
- when: (answers) => answers.stack === 'node-ts-express' && answers.addAuth
57
- }
67
+ when: (answers) => answers.addAuth // Seeder is useful when there's an auth/user model
68
+ },
69
+ // --- V5.0: Extra Features for Node.js ---
70
+ {
71
+ type: 'checkbox',
72
+ name: 'extraFeatures',
73
+ message: 'Select additional features to include:',
74
+ choices: [
75
+ { name: 'Docker Support (Dockerfile & docker-compose.yml)', value: 'docker', checked: true },
76
+ { name: 'API Testing Boilerplate (Jest & Supertest)', value: 'testing' },
77
+ { name: 'API Documentation (Swagger UI)', value: 'swagger' },
78
+ ],
79
+ when: (answers) => answers.stack === 'node-ts-express'
80
+ },
81
+ {
82
+ type: 'list',
83
+ name: 'dbType',
84
+ message: 'Select your database type:',
85
+ choices: [
86
+ { name: 'NoSQL (MongoDB with Mongoose)', value: 'mongoose' },
87
+ { name: 'SQL (PostgreSQL/MySQL with Prisma)', value: 'prisma' },
88
+ ],
89
+ when: (answers) => answers.stack === 'node-ts-express'
90
+ },
91
+ {
92
+ type: 'checkbox',
93
+ name: 'extraFeatures',
94
+ message: 'Select additional features to include:',
95
+ choices: [
96
+ { name: 'Docker Support (Dockerfile & docker-compose.yml)', value: 'docker', checked: true },
97
+ // ... other features
98
+ ],
99
+ when: (answers) => answers.stack === 'node-ts-express'
100
+ },
101
+ {
102
+ type: 'list',
103
+ name: 'stack',
104
+ message: 'Select the backend stack:',
105
+ choices: [
106
+ { name: 'Node.js (TypeScript, Express)', value: 'node-ts-express' },
107
+ { name: 'C# (ASP.NET Core Web API)', value: 'dotnet-webapi' },
108
+ new inquirer.Separator(),
109
+ { name: 'Python (FastAPI) - Coming Soon', disabled: true, value: 'python-fastapi' },
110
+ { name: 'Java (Spring Boot)', value: 'java-spring' }, // <-- ENABLED!
111
+ ],
112
+ },
58
113
  ]);
59
114
 
60
- // Pass all answers to the options object
61
115
  const options = {
62
- ...answers,
116
+ ...answers,
63
117
  projectDir: path.resolve(process.cwd(), answers.projectName),
64
118
  frontendSrcDir: path.resolve(process.cwd(), answers.srcPath),
65
119
  };
@@ -70,18 +124,19 @@ async function main() {
70
124
  // --- Dispatcher Logic ---
71
125
  switch (options.stack) {
72
126
  case 'node-ts-express':
73
- await generateNodeProject(options); // Pass the entire options object
127
+ await generateNodeProject(options);
74
128
  break;
75
129
 
76
130
  case 'dotnet-webapi':
77
131
  if (!await isCommandAvailable('dotnet')) {
78
132
  throw new Error('.NET SDK is not installed. Please install it from https://dotnet.microsoft.com/download');
79
133
  }
134
+ // Note: The dotnet generator currently only supports basic route generation (v1.0 features).
80
135
  await generateDotnetProject(options);
81
136
  break;
82
137
 
83
138
  default:
84
- throw new Error(`The selected stack '${options.stack}' is not supported.`);
139
+ throw new Error(`The selected stack '${options.stack}' is not supported yet.`);
85
140
  }
86
141
 
87
142
  console.log(chalk.green.bold('\n✅ Backend generation complete!'));
@@ -90,8 +145,9 @@ async function main() {
90
145
  console.log(chalk.cyan(' (Check the generated README.md for instructions)'));
91
146
 
92
147
  } catch (error) {
93
- console.error(chalk.red.bold('\n❌ An error occurred:'));
94
- console.error(chalk.red(` ${error.message}`));
148
+ console.error(chalk.red.bold('\n❌ An error occurred during generation:'));
149
+ // Make sure we print the full error for debugging
150
+ console.error(error);
95
151
 
96
152
  if (fs.existsSync(options.projectDir)) {
97
153
  console.log(chalk.yellow(' -> Cleaning up failed installation...'));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-backlist",
3
- "version": "4.0.0",
3
+ "version": "5.0.1",
4
4
  "description": "An advanced, multi-language backend generator based on frontend analysis.",
5
5
  "type": "commonjs",
6
6
  "bin": {
@@ -18,11 +18,13 @@
18
18
  "dependencies": {
19
19
  "@babel/parser": "^7.22.7",
20
20
  "@babel/traverse": "^7.22.8",
21
+ "axios": "^1.13.1",
21
22
  "chalk": "^4.1.2",
22
23
  "ejs": "^3.1.9",
23
24
  "execa": "^6.1.0",
24
25
  "fs-extra": "^11.1.1",
25
26
  "glob": "^10.3.3",
26
- "inquirer": "^8.2.4"
27
+ "inquirer": "^8.2.4",
28
+ "unzipper": "^0.12.3"
27
29
  }
28
- }
30
+ }
@@ -8,45 +8,126 @@ const { renderAndWrite, getTemplatePath } = require('./template');
8
8
  async function generateDotnetProject(options) {
9
9
  const { projectDir, projectName, frontendSrcDir } = options;
10
10
 
11
- console.log(chalk.blue(' -> Analyzing frontend for API endpoints...'));
12
- const endpoints = await analyzeFrontend(frontendSrcDir);
13
-
14
- const controllers = endpoints.reduce((acc, ep) => {
15
- (acc[ep.controllerName] = acc[ep.controllerName] || []).push(ep);
16
- return acc;
17
- }, {});
18
-
19
- if (Object.keys(controllers).length > 0) {
20
- console.log(chalk.green(` -> Found endpoints for ${Object.keys(controllers).length} controllers.`));
21
- } else {
22
- console.log(chalk.yellow(' -> No API endpoints found. A basic project will be created.'));
23
- }
24
-
25
- console.log(chalk.blue(' -> Scaffolding .NET Core Web API project...'));
26
- await execa('dotnet', ['new', 'webapi', '-n', projectName, '-o', projectDir, '--no-https']);
27
-
28
- await fs.remove(path.join(projectDir, 'Controllers', 'WeatherForecastController.cs'));
29
- await fs.remove(path.join(projectDir, 'WeatherForecast.cs'));
30
-
31
- console.log(chalk.blue(' -> Generating custom controllers...'));
32
- for (const controllerName of Object.keys(controllers)) {
33
- if (controllerName === 'Default') continue; // Skip if no proper controller name was found
34
- await renderAndWrite(
35
- getTemplatePath('dotnet/partials/Controller.cs.ejs'),
36
- path.join(projectDir, 'Controllers', `${controllerName}Controller.cs`),
37
- {
38
- projectName,
39
- controllerName,
40
- endpoints: controllers[controllerName]
11
+ try {
12
+ // --- Step 1: Analysis & Model Identification ---
13
+ console.log(chalk.blue(' -> Analyzing frontend for C# backend...'));
14
+ const endpoints = await analyzeFrontend(frontendSrcDir);
15
+ const modelsToGenerate = new Map();
16
+ endpoints.forEach(ep => {
17
+ // For C#, we create a model if schemaFields exist for any endpoint related to a controller
18
+ if (ep.schemaFields && ep.controllerName !== 'Default' && !modelsToGenerate.has(ep.controllerName)) {
19
+ modelsToGenerate.set(ep.controllerName, {
20
+ name: ep.controllerName,
21
+ fields: Object.entries(ep.schemaFields).map(([key, type]) => ({ name: key, type }))
22
+ });
41
23
  }
24
+ });
25
+
26
+ if (modelsToGenerate.size > 0) {
27
+ console.log(chalk.green(` -> Identified ${modelsToGenerate.size} models/controllers to generate.`));
28
+ } else {
29
+ console.log(chalk.yellow(' -> No API calls with body data found. A basic API project will be created without models.'));
30
+ }
31
+
32
+ // --- Step 2: Create Base .NET Project using `dotnet new` ---
33
+ console.log(chalk.blue(' -> Scaffolding .NET Core Web API project...'));
34
+ await execa('dotnet', ['new', 'webapi', '-n', projectName, '-o', projectDir, '--no-https']);
35
+
36
+ // --- Step 3: Add Required NuGet Packages ---
37
+ if (modelsToGenerate.size > 0) {
38
+ console.log(chalk.blue(' -> Adding NuGet packages (Entity Framework Core)...'));
39
+ const packages = [
40
+ 'Microsoft.EntityFrameworkCore.Design',
41
+ 'Microsoft.EntityFrameworkCore.InMemory' // Using InMemory for a simple, runnable setup
42
+ // For a real DB, a user would add: 'Npgsql.EntityFrameworkCore.PostgreSQL' or 'Microsoft.EntityFrameworkCore.SqlServer'
43
+ ];
44
+ for (const pkg of packages) {
45
+ await execa('dotnet', ['add', 'package', pkg], { cwd: projectDir });
46
+ }
47
+ }
48
+
49
+ // --- Step 4: Generate Models and DbContext from Templates ---
50
+ if (modelsToGenerate.size > 0) {
51
+ console.log(chalk.blue(' -> Generating EF Core models and DbContext...'));
52
+ const modelsDir = path.join(projectDir, 'Models');
53
+ const dataDir = path.join(projectDir, 'Data');
54
+ await fs.ensureDir(modelsDir);
55
+ await fs.ensureDir(dataDir);
56
+
57
+ for (const [modelName, modelData] of modelsToGenerate.entries()) {
58
+ await renderAndWrite(
59
+ getTemplatePath('dotnet/partials/Model.cs.ejs'),
60
+ path.join(modelsDir, `${modelName}.cs`),
61
+ { projectName, modelName, model: modelData }
62
+ );
63
+ }
64
+
65
+ await renderAndWrite(
66
+ getTemplatePath('dotnet/partials/DbContext.cs.ejs'),
67
+ path.join(dataDir, 'ApplicationDbContext.cs'),
68
+ { projectName, modelsToGenerate: Array.from(modelsToGenerate.values()) }
69
+ );
70
+ }
71
+
72
+ // --- Step 5: Configure Services in Program.cs ---
73
+ console.log(chalk.blue(' -> Configuring services in Program.cs...'));
74
+ const programCsPath = path.join(projectDir, 'Program.cs');
75
+ let programCsContent = await fs.readFile(programCsPath, 'utf-8');
76
+
77
+ let usingStatements = 'using Microsoft.EntityFrameworkCore;\nusing '+projectName+'.Data;\n';
78
+ programCsContent = usingStatements + programCsContent;
79
+
80
+ let dbContextService = `// Configure the database context\nbuilder.Services.AddDbContext<ApplicationDbContext>(opt => opt.UseInMemoryDatabase("MyDb"));`;
81
+ programCsContent = programCsContent.replace('builder.Services.AddControllers();', `builder.Services.AddControllers();\n\n${dbContextService}`);
82
+
83
+ // Enable CORS to allow frontend communication
84
+ const corsPolicy = `
85
+ builder.Services.AddCors(options =>
86
+ {
87
+ options.AddDefaultPolicy(
88
+ policy =>
89
+ {
90
+ policy.WithOrigins("http://localhost:3000", "http://localhost:5173") // Common frontend dev ports
91
+ .AllowAnyHeader()
92
+ .AllowAnyMethod();
93
+ });
94
+ });`;
95
+ programCsContent = programCsContent.replace('var app = builder.Build();', `${corsPolicy}\n\nvar app = builder.Build();\n\napp.UseCors();`);
96
+
97
+ await fs.writeFile(programCsPath, programCsContent);
98
+
99
+ // --- Step 6: Generate Controllers with full CRUD ---
100
+ console.log(chalk.blue(' -> Generating controllers with CRUD logic...'));
101
+ await fs.remove(path.join(projectDir, 'Controllers', 'WeatherForecastController.cs'));
102
+ await fs.remove(path.join(projectDir, 'WeatherForecast.cs'));
103
+
104
+ const controllersToGenerate = new Set(Array.from(modelsToGenerate.keys()));
105
+ // Also add controllers for endpoints that didn't have a body but were detected
106
+ endpoints.forEach(ep => {
107
+ if (ep.controllerName !== 'Default') controllersToGenerate.add(ep.controllerName);
108
+ });
109
+
110
+ for (const controllerName of controllersToGenerate) {
111
+ await renderAndWrite(
112
+ getTemplatePath('dotnet/partials/Controller.cs.ejs'),
113
+ path.join(projectDir, 'Controllers', `${controllerName}Controller.cs`),
114
+ { projectName, controllerName }
115
+ );
116
+ }
117
+
118
+ // --- Step 7: Generate README ---
119
+ await renderAndWrite(
120
+ getTemplatePath('dotnet/partials/README.md.ejs'),
121
+ path.join(projectDir, 'README.md'),
122
+ { projectName }
42
123
  );
43
- }
44
124
 
45
- await renderAndWrite(
46
- getTemplatePath('dotnet/partials/README.md.ejs'),
47
- path.join(projectDir, 'README.md'),
48
- { projectName }
49
- );
125
+ console.log(chalk.green(' -> C# backend generation is complete!'));
126
+
127
+ } catch (error) {
128
+ // Re-throw the error to be caught by the main CLI handler
129
+ throw error;
130
+ }
50
131
  }
51
132
 
52
133
  module.exports = { generateDotnetProject };
@@ -0,0 +1,100 @@
1
+ const chalk = require('chalk');
2
+ const { execa } = require('execa');
3
+ const fs = require('fs-extra');
4
+ const path = require('path');
5
+ const axios = require('axios'); // For making HTTP requests
6
+ const unzipper = require('unzipper'); // For extracting .zip files
7
+ const { analyzeFrontend } = require('../analyzer');
8
+ const { renderAndWrite, getTemplatePath } = require('./template');
9
+
10
+ async function generateJavaProject(options) {
11
+ const { projectDir, projectName, frontendSrcDir } = options;
12
+ const group = 'com.backlist.generated'; // A default Java group ID
13
+
14
+ try {
15
+ // --- Step 1: Download Base Project from Spring Initializr ---
16
+ console.log(chalk.blue(' -> Contacting Spring Initializr to download a base Spring Boot project...'));
17
+
18
+ // Define standard dependencies for a web API
19
+ const dependencies = 'web,data-jpa,lombok,postgresql'; // Using PostgreSQL as an example DB driver
20
+ const springInitializrUrl = `https://start.spring.io/starter.zip?type=maven-project&language=java&bootVersion=3.2.0&groupId=${group}&artifactId=${projectName}&name=${projectName}&dependencies=${dependencies}`;
21
+
22
+ const response = await axios({
23
+ url: springInitializrUrl,
24
+ method: 'GET',
25
+ responseType: 'stream'
26
+ });
27
+
28
+ // --- Step 2: Unzip the Downloaded Project ---
29
+ console.log(chalk.blue(' -> Unzipping the Spring Boot project...'));
30
+ await new Promise((resolve, reject) => {
31
+ const stream = response.data.pipe(unzipper.Extract({ path: projectDir }));
32
+ stream.on('finish', () => {
33
+ console.log(chalk.gray(' -> Project unzipped successfully.'));
34
+ resolve();
35
+ });
36
+ stream.on('error', reject);
37
+ });
38
+
39
+ // --- Step 3: Analyze Frontend ---
40
+ const endpoints = await analyzeFrontend(frontendSrcDir);
41
+ const modelsToGenerate = new Map();
42
+ endpoints.forEach(ep => {
43
+ if (ep.schemaFields && ep.controllerName !== 'Default' && !modelsToGenerate.has(ep.controllerName)) {
44
+ modelsToGenerate.set(ep.controllerName, { name: ep.controllerName, fields: Object.entries(ep.schemaFields).map(([key, type]) => ({ name: key, type })) });
45
+ }
46
+ });
47
+
48
+ // --- Step 4: Generate Java Entities and Controllers ---
49
+ if (modelsToGenerate.size > 0) {
50
+ console.log(chalk.blue(' -> Generating Java entities and controllers...'));
51
+
52
+ const javaSrcPath = path.join(projectDir, 'src', 'main', 'java', ...group.split('.'), projectName);
53
+ const entityDir = path.join(javaSrcPath, 'model');
54
+ const controllerDir = path.join(javaSrcPath, 'controller');
55
+ await fs.ensureDir(entityDir);
56
+ await fs.ensureDir(controllerDir);
57
+
58
+ for (const [modelName, modelData] of modelsToGenerate.entries()) {
59
+ await renderAndWrite(
60
+ getTemplatePath('java-spring/partials/Entity.java.ejs'),
61
+ path.join(entityDir, `${modelName}.java`),
62
+ { group, projectName, modelName, model: modelData }
63
+ );
64
+ await renderAndWrite(
65
+ getTemplatePath('java-spring/partials/Controller.java.ejs'),
66
+ path.join(controllerDir, `${modelName}Controller.java`),
67
+ { group, projectName, controllerName: modelName }
68
+ );
69
+ }
70
+ }
71
+
72
+ // --- Step 5: Configure application.properties ---
73
+ console.log(chalk.blue(' -> Configuring database in application.properties...'));
74
+ const propsPath = path.join(projectDir, 'src', 'main', 'resources', 'application.properties');
75
+ const dbProps = [
76
+ `\n\n# --- Auto-generated by create-backlist ---`,
77
+ `# --- Database Configuration (PostgreSQL) ---`,
78
+ `spring.datasource.url=jdbc:postgresql://localhost:5432/${projectName}`,
79
+ `spring.datasource.username=postgres`,
80
+ `spring.datasource.password=password`,
81
+ `spring.jpa.hibernate.ddl-auto=update`,
82
+ `spring.jpa.show-sql=true`,
83
+ ];
84
+ await fs.appendFile(propsPath, dbProps.join('\n'));
85
+
86
+ console.log(chalk.green(' -> Java (Spring Boot) backend generation is complete!'));
87
+ console.log(chalk.yellow('\nTo run your new Java backend:'));
88
+ console.log(chalk.cyan(' 1. Open the project in a Java IDE (like IntelliJ IDEA or VS Code).'));
89
+ console.log(chalk.cyan(' 2. Run the main application file.'));
90
+
91
+
92
+ } catch (error) {
93
+ if (error.response) {
94
+ throw new Error(`Failed to download from Spring Initializr. Status: ${error.response.status}`);
95
+ }
96
+ throw error;
97
+ }
98
+ }
99
+
100
+ module.exports = { generateJavaProject };