projects-init-cli 1.1.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 +127 -0
- package/dist/generator.js +59 -0
- package/dist/index.js +32 -0
- package/dist/prompts.js +156 -0
- package/dist/templates/backend/cdk.js +211 -0
- package/dist/templates/backend/express.js +427 -0
- package/dist/templates/backend/fastapi.js +259 -0
- package/dist/templates/backend/nestjs.js +478 -0
- package/dist/templates/backend/sam.js +258 -0
- package/dist/templates/frontend/html.js +141 -0
- package/dist/templates/frontend/index.js +9 -0
- package/dist/templates/frontend/nextjs.js +240 -0
- package/dist/templates/frontend/react.js +249 -0
- package/dist/templates/index.js +341 -0
- package/dist/types.js +2 -0
- package/package.json +52 -0
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.generateExpress = generateExpress;
|
|
7
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
async function generateExpress(backendPath, config) {
|
|
10
|
+
const dependencies = {
|
|
11
|
+
express: '^4.21.1',
|
|
12
|
+
cors: '^2.8.5',
|
|
13
|
+
dotenv: '^16.4.5'
|
|
14
|
+
};
|
|
15
|
+
const devDependencies = {
|
|
16
|
+
'@types/express': '^5.0.0',
|
|
17
|
+
'@types/cors': '^2.8.17',
|
|
18
|
+
'@types/node': '^22.7.5',
|
|
19
|
+
typescript: '^5.6.3',
|
|
20
|
+
'ts-node': '^10.9.2',
|
|
21
|
+
nodemon: '^3.1.7',
|
|
22
|
+
'vitest': '^2.1.3',
|
|
23
|
+
'@vitest/ui': '^2.1.3',
|
|
24
|
+
'supertest': '^7.0.0',
|
|
25
|
+
'@types/supertest': '^6.0.2',
|
|
26
|
+
'@vitest/coverage-v8': '^2.1.3'
|
|
27
|
+
};
|
|
28
|
+
// Add database dependencies
|
|
29
|
+
if (config.storage === 'local-json') {
|
|
30
|
+
// No additional dependencies needed for JSON file storage
|
|
31
|
+
}
|
|
32
|
+
else if (config.storage === 'local-sqlite') {
|
|
33
|
+
if (config.databaseType === 'sql' && config.sqlOption === 'prisma') {
|
|
34
|
+
dependencies['@prisma/client'] = '^6.0.1';
|
|
35
|
+
devDependencies['prisma'] = '^6.0.1';
|
|
36
|
+
}
|
|
37
|
+
else if (config.databaseType === 'sql') {
|
|
38
|
+
dependencies['better-sqlite3'] = '^11.7.0';
|
|
39
|
+
devDependencies['@types/better-sqlite3'] = '^7.6.9';
|
|
40
|
+
}
|
|
41
|
+
else if (config.databaseType === 'nosql' && config.nosqlOption === 'mongodb') {
|
|
42
|
+
dependencies['mongoose'] = '^8.8.4';
|
|
43
|
+
}
|
|
44
|
+
else if (config.databaseType === 'nosql' && config.nosqlOption === 'dynamodb') {
|
|
45
|
+
dependencies['@aws-sdk/client-dynamodb'] = '^3.699.0';
|
|
46
|
+
dependencies['@aws-sdk/lib-dynamodb'] = '^3.699.0';
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
else if (config.storage === 'external-url') {
|
|
50
|
+
if (config.databaseType === 'sql' && config.sqlOption === 'prisma') {
|
|
51
|
+
dependencies['@prisma/client'] = '^6.0.1';
|
|
52
|
+
devDependencies['prisma'] = '^6.0.1';
|
|
53
|
+
}
|
|
54
|
+
else if (config.databaseType === 'sql') {
|
|
55
|
+
dependencies['pg'] = '^8.13.1';
|
|
56
|
+
devDependencies['@types/pg'] = '^8.11.10';
|
|
57
|
+
}
|
|
58
|
+
else if (config.databaseType === 'nosql' && config.nosqlOption === 'mongodb') {
|
|
59
|
+
dependencies['mongoose'] = '^8.8.4';
|
|
60
|
+
}
|
|
61
|
+
else if (config.databaseType === 'nosql' && config.nosqlOption === 'dynamodb') {
|
|
62
|
+
dependencies['@aws-sdk/client-dynamodb'] = '^3.699.0';
|
|
63
|
+
dependencies['@aws-sdk/lib-dynamodb'] = '^3.699.0';
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// Add API type dependencies
|
|
67
|
+
if (config.apiType === 'graphql') {
|
|
68
|
+
dependencies['graphql'] = '^16.9.0';
|
|
69
|
+
dependencies['express-graphql'] = '^0.12.0';
|
|
70
|
+
devDependencies['@types/express-graphql'] = '^0.12.0';
|
|
71
|
+
}
|
|
72
|
+
const packageJson = {
|
|
73
|
+
name: `${config.projectName}-backend`,
|
|
74
|
+
version: '1.0.0',
|
|
75
|
+
main: 'dist/index.js',
|
|
76
|
+
scripts: {
|
|
77
|
+
dev: 'nodemon --exec ts-node src/index.ts',
|
|
78
|
+
build: 'tsc',
|
|
79
|
+
start: 'node dist/index.js',
|
|
80
|
+
test: 'vitest',
|
|
81
|
+
'test:ui': 'vitest --ui',
|
|
82
|
+
'test:coverage': 'vitest --coverage'
|
|
83
|
+
},
|
|
84
|
+
dependencies,
|
|
85
|
+
devDependencies
|
|
86
|
+
};
|
|
87
|
+
await fs_extra_1.default.writeJSON(path_1.default.join(backendPath, 'package.json'), packageJson, { spaces: 2 });
|
|
88
|
+
// Create TypeScript config
|
|
89
|
+
const tsconfig = {
|
|
90
|
+
compilerOptions: {
|
|
91
|
+
target: 'ES2020',
|
|
92
|
+
module: 'commonjs',
|
|
93
|
+
lib: ['ES2020'],
|
|
94
|
+
outDir: './dist',
|
|
95
|
+
rootDir: './src',
|
|
96
|
+
strict: true,
|
|
97
|
+
esModuleInterop: true,
|
|
98
|
+
skipLibCheck: true,
|
|
99
|
+
forceConsistentCasingInFileNames: true,
|
|
100
|
+
resolveJsonModule: true,
|
|
101
|
+
moduleResolution: 'node'
|
|
102
|
+
},
|
|
103
|
+
include: ['src/**/*'],
|
|
104
|
+
exclude: ['node_modules', 'dist']
|
|
105
|
+
};
|
|
106
|
+
await fs_extra_1.default.writeJSON(path_1.default.join(backendPath, 'tsconfig.json'), tsconfig, { spaces: 2 });
|
|
107
|
+
// Create Vitest config
|
|
108
|
+
const vitestConfig = `import { defineConfig } from 'vitest/config'
|
|
109
|
+
|
|
110
|
+
export default defineConfig({
|
|
111
|
+
test: {
|
|
112
|
+
globals: true,
|
|
113
|
+
environment: 'node',
|
|
114
|
+
coverage: {
|
|
115
|
+
provider: 'v8',
|
|
116
|
+
reporter: ['text', 'json', 'html'],
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
})
|
|
120
|
+
`;
|
|
121
|
+
await fs_extra_1.default.writeFile(path_1.default.join(backendPath, 'vitest.config.ts'), vitestConfig);
|
|
122
|
+
// Create src directory
|
|
123
|
+
const srcPath = path_1.default.join(backendPath, 'src');
|
|
124
|
+
await fs_extra_1.default.ensureDir(srcPath);
|
|
125
|
+
// Create index.ts
|
|
126
|
+
let indexContent = `import express from 'express';
|
|
127
|
+
import cors from 'cors';
|
|
128
|
+
import dotenv from 'dotenv';
|
|
129
|
+
|
|
130
|
+
dotenv.config();
|
|
131
|
+
|
|
132
|
+
const app = express();
|
|
133
|
+
const PORT = process.env.PORT || 3001;
|
|
134
|
+
|
|
135
|
+
app.use(cors());
|
|
136
|
+
app.use(express.json());
|
|
137
|
+
|
|
138
|
+
// Health check endpoint
|
|
139
|
+
app.get('/health', (req, res) => {
|
|
140
|
+
res.json({ status: 'ok', message: 'Server is running' });
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
`;
|
|
144
|
+
if (config.apiType === 'graphql') {
|
|
145
|
+
indexContent += `import { graphqlHTTP } from 'express-graphql';
|
|
146
|
+
import { buildSchema } from 'graphql';
|
|
147
|
+
|
|
148
|
+
// GraphQL schema
|
|
149
|
+
const schema = buildSchema(\`
|
|
150
|
+
type Query {
|
|
151
|
+
hello: String
|
|
152
|
+
}
|
|
153
|
+
\`);
|
|
154
|
+
|
|
155
|
+
// Root resolver
|
|
156
|
+
const root = {
|
|
157
|
+
hello: () => 'Hello from GraphQL!',
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
app.use('/graphql', graphqlHTTP({
|
|
161
|
+
schema: schema,
|
|
162
|
+
rootValue: root,
|
|
163
|
+
graphiql: true,
|
|
164
|
+
}));
|
|
165
|
+
|
|
166
|
+
`;
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
indexContent += `// REST API routes
|
|
170
|
+
app.get('/api', (req, res) => {
|
|
171
|
+
res.json({ message: 'Welcome to the REST API' });
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
`;
|
|
175
|
+
}
|
|
176
|
+
indexContent += `const server = app.listen(PORT, () => {
|
|
177
|
+
console.log(\`Server is running on http://localhost:\${PORT}\`);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
export default app;
|
|
181
|
+
`;
|
|
182
|
+
await fs_extra_1.default.writeFile(path_1.default.join(srcPath, 'index.ts'), indexContent);
|
|
183
|
+
// Create .env.example
|
|
184
|
+
let envExample = `PORT=3001
|
|
185
|
+
`;
|
|
186
|
+
if (config.storage === 'external-url' && config.databaseUrl) {
|
|
187
|
+
envExample += `DATABASE_URL=${config.databaseUrl}\n`;
|
|
188
|
+
}
|
|
189
|
+
await fs_extra_1.default.writeFile(path_1.default.join(backendPath, '.env.example'), envExample);
|
|
190
|
+
// Create database setup if needed
|
|
191
|
+
if (config.storage === 'local-json') {
|
|
192
|
+
await generateJSONFileSetup(srcPath, config);
|
|
193
|
+
}
|
|
194
|
+
else if (config.databaseType === 'sql' && config.sqlOption === 'prisma') {
|
|
195
|
+
await generatePrismaConfig(backendPath, config);
|
|
196
|
+
}
|
|
197
|
+
else if (config.databaseType === 'sql' && config.storage === 'local-sqlite') {
|
|
198
|
+
await generateSQLiteSetup(srcPath, config);
|
|
199
|
+
}
|
|
200
|
+
else if (config.databaseType === 'nosql' && config.nosqlOption === 'mongodb') {
|
|
201
|
+
await generateMongoDBSetup(srcPath, config);
|
|
202
|
+
}
|
|
203
|
+
else if (config.databaseType === 'nosql' && config.nosqlOption === 'dynamodb') {
|
|
204
|
+
await generateDynamoDBSetup(srcPath, config);
|
|
205
|
+
}
|
|
206
|
+
// Create test files
|
|
207
|
+
const testPath = path_1.default.join(srcPath, '__tests__');
|
|
208
|
+
await fs_extra_1.default.ensureDir(testPath);
|
|
209
|
+
const testFile = `import { describe, it, expect, beforeAll, afterAll } from 'vitest'
|
|
210
|
+
import request from 'supertest'
|
|
211
|
+
import app from '../index'
|
|
212
|
+
|
|
213
|
+
describe('API Tests', () => {
|
|
214
|
+
it('should return health check', async () => {
|
|
215
|
+
const res = await request(app).get('/health')
|
|
216
|
+
expect(res.status).toBe(200)
|
|
217
|
+
expect(res.body.status).toBe('ok')
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
${config.apiType === 'rest' ? ` it('should return API welcome message', async () => {
|
|
221
|
+
const res = await request(app).get('/api')
|
|
222
|
+
expect(res.status).toBe(200)
|
|
223
|
+
expect(res.body.message).toContain('REST API')
|
|
224
|
+
})` : ` it('should handle GraphQL query', async () => {
|
|
225
|
+
const res = await request(app)
|
|
226
|
+
.post('/graphql')
|
|
227
|
+
.send({ query: '{ hello }' })
|
|
228
|
+
expect(res.status).toBe(200)
|
|
229
|
+
expect(res.body.data.hello).toBe('Hello from GraphQL!')
|
|
230
|
+
})`}
|
|
231
|
+
})
|
|
232
|
+
`;
|
|
233
|
+
await fs_extra_1.default.writeFile(path_1.default.join(testPath, 'api.test.ts'), testFile);
|
|
234
|
+
// Create deployment files
|
|
235
|
+
await generateBackendDeployment(backendPath, config);
|
|
236
|
+
}
|
|
237
|
+
async function generateBackendDeployment(backendPath, config) {
|
|
238
|
+
// Render configuration
|
|
239
|
+
const renderYaml = `services:
|
|
240
|
+
- type: web
|
|
241
|
+
name: ${config.projectName}-backend
|
|
242
|
+
env: node
|
|
243
|
+
buildCommand: npm install && npm run build
|
|
244
|
+
startCommand: npm start
|
|
245
|
+
envVars:
|
|
246
|
+
- key: NODE_ENV
|
|
247
|
+
value: production
|
|
248
|
+
- key: PORT
|
|
249
|
+
value: 3001
|
|
250
|
+
`;
|
|
251
|
+
await fs_extra_1.default.writeFile(path_1.default.join(backendPath, 'render.yaml'), renderYaml);
|
|
252
|
+
}
|
|
253
|
+
async function generatePrismaConfig(backendPath, config) {
|
|
254
|
+
const prismaPath = path_1.default.join(backendPath, 'prisma');
|
|
255
|
+
await fs_extra_1.default.ensureDir(prismaPath);
|
|
256
|
+
let datasource = '';
|
|
257
|
+
if (config.storage === 'local-sqlite') {
|
|
258
|
+
datasource = `datasource db {
|
|
259
|
+
provider = "sqlite"
|
|
260
|
+
url = "file:./dev.db"
|
|
261
|
+
}`;
|
|
262
|
+
}
|
|
263
|
+
else if (config.storage === 'external-url') {
|
|
264
|
+
datasource = `datasource db {
|
|
265
|
+
provider = "postgresql"
|
|
266
|
+
url = env("DATABASE_URL")
|
|
267
|
+
}`;
|
|
268
|
+
}
|
|
269
|
+
const schema = `${datasource}
|
|
270
|
+
|
|
271
|
+
generator client {
|
|
272
|
+
provider = "prisma-client-js"
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
model User {
|
|
276
|
+
id Int @id @default(autoincrement())
|
|
277
|
+
email String @unique
|
|
278
|
+
name String?
|
|
279
|
+
createdAt DateTime @default(now())
|
|
280
|
+
updatedAt DateTime @updatedAt
|
|
281
|
+
}
|
|
282
|
+
`;
|
|
283
|
+
await fs_extra_1.default.writeFile(path_1.default.join(prismaPath, 'schema.prisma'), schema);
|
|
284
|
+
}
|
|
285
|
+
async function generateSQLiteSetup(srcPath, config) {
|
|
286
|
+
const dbContent = `import Database from 'better-sqlite3';
|
|
287
|
+
import path from 'path';
|
|
288
|
+
|
|
289
|
+
const dbPath = path.join(process.cwd(), 'database.db');
|
|
290
|
+
const db = new Database(dbPath);
|
|
291
|
+
|
|
292
|
+
// Initialize database
|
|
293
|
+
db.exec(\`
|
|
294
|
+
CREATE TABLE IF NOT EXISTS users (
|
|
295
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
296
|
+
email TEXT UNIQUE NOT NULL,
|
|
297
|
+
name TEXT,
|
|
298
|
+
createdAt DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
299
|
+
)
|
|
300
|
+
\`);
|
|
301
|
+
|
|
302
|
+
export default db;
|
|
303
|
+
`;
|
|
304
|
+
await fs_extra_1.default.writeFile(path_1.default.join(srcPath, 'db.ts'), dbContent);
|
|
305
|
+
}
|
|
306
|
+
async function generateMongoDBSetup(srcPath, config) {
|
|
307
|
+
const dbContent = `import mongoose from 'mongoose';
|
|
308
|
+
|
|
309
|
+
const connectDB = async () => {
|
|
310
|
+
try {
|
|
311
|
+
const mongoURI = process.env.DATABASE_URL || 'mongodb://localhost:27017/${config.projectName}';
|
|
312
|
+
await mongoose.connect(mongoURI);
|
|
313
|
+
console.log('MongoDB connected');
|
|
314
|
+
} catch (error) {
|
|
315
|
+
console.error('MongoDB connection error:', error);
|
|
316
|
+
process.exit(1);
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
// User schema example
|
|
321
|
+
const userSchema = new mongoose.Schema({
|
|
322
|
+
email: { type: String, required: true, unique: true },
|
|
323
|
+
name: String,
|
|
324
|
+
createdAt: { type: Date, default: Date.now }
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
export const User = mongoose.model('User', userSchema);
|
|
328
|
+
export default connectDB;
|
|
329
|
+
`;
|
|
330
|
+
await fs_extra_1.default.writeFile(path_1.default.join(srcPath, 'db.ts'), dbContent);
|
|
331
|
+
}
|
|
332
|
+
async function generateDynamoDBSetup(srcPath, config) {
|
|
333
|
+
const dbContent = `import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
|
|
334
|
+
import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb';
|
|
335
|
+
|
|
336
|
+
const client = new DynamoDBClient({
|
|
337
|
+
region: process.env.AWS_REGION || 'us-east-1',
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
export const docClient = DynamoDBDocumentClient.from(client);
|
|
341
|
+
|
|
342
|
+
export default docClient;
|
|
343
|
+
`;
|
|
344
|
+
await fs_extra_1.default.writeFile(path_1.default.join(srcPath, 'db.ts'), dbContent);
|
|
345
|
+
}
|
|
346
|
+
async function generateJSONFileSetup(srcPath, config) {
|
|
347
|
+
const backendPath = path_1.default.dirname(path_1.default.dirname(srcPath));
|
|
348
|
+
const dbContent = `import fs from 'fs-extra';
|
|
349
|
+
import path from 'path';
|
|
350
|
+
|
|
351
|
+
const DB_FILE = path.join(process.cwd(), 'data', 'database.json');
|
|
352
|
+
|
|
353
|
+
// Ensure data directory exists
|
|
354
|
+
const dataDir = path.dirname(DB_FILE);
|
|
355
|
+
fs.ensureDirSync(dataDir);
|
|
356
|
+
|
|
357
|
+
// Initialize database file if it doesn't exist
|
|
358
|
+
if (!fs.existsSync(DB_FILE)) {
|
|
359
|
+
fs.writeJSONSync(DB_FILE, { users: [] }, { spaces: 2 });
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Read database
|
|
363
|
+
export const readDB = (): any => {
|
|
364
|
+
try {
|
|
365
|
+
return fs.readJSONSync(DB_FILE);
|
|
366
|
+
} catch (error) {
|
|
367
|
+
console.error('Error reading database:', error);
|
|
368
|
+
return { users: [] };
|
|
369
|
+
}
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
// Write database
|
|
373
|
+
export const writeDB = (data: any): void => {
|
|
374
|
+
try {
|
|
375
|
+
fs.writeJSONSync(DB_FILE, data, { spaces: 2 });
|
|
376
|
+
} catch (error) {
|
|
377
|
+
console.error('Error writing database:', error);
|
|
378
|
+
throw error;
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
// Helper functions
|
|
383
|
+
export const getUsers = () => {
|
|
384
|
+
const db = readDB();
|
|
385
|
+
return db.users || [];
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
export const addUser = (user: any) => {
|
|
389
|
+
const db = readDB();
|
|
390
|
+
if (!db.users) db.users = [];
|
|
391
|
+
const newUser = { ...user, id: db.users.length + 1, createdAt: new Date().toISOString() };
|
|
392
|
+
db.users.push(newUser);
|
|
393
|
+
writeDB(db);
|
|
394
|
+
return newUser;
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
export const getUserById = (id: number) => {
|
|
398
|
+
const db = readDB();
|
|
399
|
+
return db.users?.find((u: any) => u.id === id);
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
export const updateUser = (id: number, updates: any) => {
|
|
403
|
+
const db = readDB();
|
|
404
|
+
const userIndex = db.users?.findIndex((u: any) => u.id === id);
|
|
405
|
+
if (userIndex !== -1 && userIndex !== undefined) {
|
|
406
|
+
db.users[userIndex] = { ...db.users[userIndex], ...updates, updatedAt: new Date().toISOString() };
|
|
407
|
+
writeDB(db);
|
|
408
|
+
return db.users[userIndex];
|
|
409
|
+
}
|
|
410
|
+
return null;
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
export const deleteUser = (id: number) => {
|
|
414
|
+
const db = readDB();
|
|
415
|
+
db.users = db.users?.filter((u: any) => u.id !== id) || [];
|
|
416
|
+
writeDB(db);
|
|
417
|
+
return true;
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
export default { readDB, writeDB, getUsers, addUser, getUserById, updateUser, deleteUser };
|
|
421
|
+
`;
|
|
422
|
+
await fs_extra_1.default.writeFile(path_1.default.join(srcPath, 'db.ts'), dbContent);
|
|
423
|
+
// Create initial data directory and file
|
|
424
|
+
const dataPath = path_1.default.join(backendPath, 'data');
|
|
425
|
+
await fs_extra_1.default.ensureDir(dataPath);
|
|
426
|
+
await fs_extra_1.default.writeJSON(path_1.default.join(dataPath, 'database.json'), { users: [] }, { spaces: 2 });
|
|
427
|
+
}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.generateFastAPI = generateFastAPI;
|
|
7
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
async function generateFastAPI(backendPath, config) {
|
|
10
|
+
const dependencies = [
|
|
11
|
+
'fastapi==0.115.6',
|
|
12
|
+
'uvicorn[standard]==0.32.1',
|
|
13
|
+
'python-dotenv==1.0.1',
|
|
14
|
+
'pydantic==2.10.3',
|
|
15
|
+
'pydantic-settings==2.6.1'
|
|
16
|
+
];
|
|
17
|
+
// Add database dependencies
|
|
18
|
+
if (config.storage === 'local-json') {
|
|
19
|
+
// No additional dependencies needed for JSON file storage (uses built-in json module)
|
|
20
|
+
}
|
|
21
|
+
else if (config.storage === 'local-sqlite') {
|
|
22
|
+
if (config.databaseType === 'sql') {
|
|
23
|
+
dependencies.push('sqlalchemy==2.0.36');
|
|
24
|
+
}
|
|
25
|
+
else if (config.databaseType === 'nosql' && config.nosqlOption === 'mongodb') {
|
|
26
|
+
dependencies.push('motor==3.6.0');
|
|
27
|
+
dependencies.push('pymongo==4.10.1');
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
else if (config.storage === 'external-url') {
|
|
31
|
+
if (config.databaseType === 'sql') {
|
|
32
|
+
dependencies.push('sqlalchemy==2.0.36');
|
|
33
|
+
dependencies.push('psycopg2-binary==2.9.10');
|
|
34
|
+
}
|
|
35
|
+
else if (config.databaseType === 'nosql' && config.nosqlOption === 'mongodb') {
|
|
36
|
+
dependencies.push('motor==3.6.0');
|
|
37
|
+
dependencies.push('pymongo==4.10.1');
|
|
38
|
+
}
|
|
39
|
+
else if (config.databaseType === 'nosql' && config.nosqlOption === 'dynamodb') {
|
|
40
|
+
dependencies.push('boto3==1.35.47');
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
// Create requirements.txt
|
|
44
|
+
await fs_extra_1.default.writeFile(path_1.default.join(backendPath, 'requirements.txt'), dependencies.join('\n'));
|
|
45
|
+
// Create main.py
|
|
46
|
+
const srcPath = path_1.default.join(backendPath, 'src');
|
|
47
|
+
await fs_extra_1.default.ensureDir(srcPath);
|
|
48
|
+
let mainContent = `from fastapi import FastAPI
|
|
49
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
50
|
+
from dotenv import load_dotenv
|
|
51
|
+
import os
|
|
52
|
+
|
|
53
|
+
load_dotenv()
|
|
54
|
+
|
|
55
|
+
app = FastAPI(title="${config.projectName} API")
|
|
56
|
+
|
|
57
|
+
app.add_middleware(
|
|
58
|
+
CORSMiddleware,
|
|
59
|
+
allow_origins=["*"],
|
|
60
|
+
allow_credentials=True,
|
|
61
|
+
allow_methods=["*"],
|
|
62
|
+
allow_headers=["*"],
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
@app.get("/")
|
|
66
|
+
def read_root():
|
|
67
|
+
return {"message": "Welcome to ${config.projectName} API"}
|
|
68
|
+
|
|
69
|
+
@app.get("/health")
|
|
70
|
+
def health_check():
|
|
71
|
+
return {"status": "ok", "message": "Server is running"}
|
|
72
|
+
|
|
73
|
+
if __name__ == "__main__":
|
|
74
|
+
import uvicorn
|
|
75
|
+
port = int(os.getenv("PORT", 3001))
|
|
76
|
+
uvicorn.run(app, host="0.0.0.0", port=port)
|
|
77
|
+
`;
|
|
78
|
+
await fs_extra_1.default.writeFile(path_1.default.join(srcPath, 'main.py'), mainContent);
|
|
79
|
+
// Create .env.example
|
|
80
|
+
let envExample = `PORT=3001
|
|
81
|
+
`;
|
|
82
|
+
if (config.storage === 'external-url' && config.databaseUrl) {
|
|
83
|
+
envExample += `DATABASE_URL=${config.databaseUrl}\n`;
|
|
84
|
+
}
|
|
85
|
+
await fs_extra_1.default.writeFile(path_1.default.join(backendPath, '.env.example'), envExample);
|
|
86
|
+
// Create database setup if needed
|
|
87
|
+
if (config.storage === 'local-json') {
|
|
88
|
+
await generateJSONFileSetup(srcPath, config);
|
|
89
|
+
}
|
|
90
|
+
else if (config.databaseType === 'sql') {
|
|
91
|
+
await generateSQLAlchemySetup(srcPath, config);
|
|
92
|
+
}
|
|
93
|
+
else if (config.databaseType === 'nosql' && config.nosqlOption === 'mongodb') {
|
|
94
|
+
await generateMongoDBSetup(srcPath, config);
|
|
95
|
+
}
|
|
96
|
+
else if (config.databaseType === 'nosql' && config.nosqlOption === 'dynamodb') {
|
|
97
|
+
await generateDynamoDBSetup(srcPath, config);
|
|
98
|
+
}
|
|
99
|
+
// Create deployment files
|
|
100
|
+
await generateBackendDeployment(backendPath, config);
|
|
101
|
+
}
|
|
102
|
+
async function generateBackendDeployment(backendPath, config) {
|
|
103
|
+
// Render configuration
|
|
104
|
+
const renderYaml = `services:
|
|
105
|
+
- type: web
|
|
106
|
+
name: ${config.projectName}-backend
|
|
107
|
+
env: python
|
|
108
|
+
buildCommand: pip install -r requirements.txt
|
|
109
|
+
startCommand: uvicorn src.main:app --host 0.0.0.0 --port \$PORT
|
|
110
|
+
envVars:
|
|
111
|
+
- key: PORT
|
|
112
|
+
value: 3001
|
|
113
|
+
`;
|
|
114
|
+
await fs_extra_1.default.writeFile(path_1.default.join(backendPath, 'render.yaml'), renderYaml);
|
|
115
|
+
}
|
|
116
|
+
async function generateSQLAlchemySetup(srcPath, config) {
|
|
117
|
+
const dbUrlLine = config.storage === 'local-sqlite'
|
|
118
|
+
? 'DATABASE_URL = "sqlite:///./database.db"'
|
|
119
|
+
: 'DATABASE_URL = os.getenv("DATABASE_URL", "postgresql://user:password@localhost/dbname")';
|
|
120
|
+
const dbContent = `from sqlalchemy import create_engine, Column, Integer, String, DateTime
|
|
121
|
+
from sqlalchemy.ext.declarative import declarative_base
|
|
122
|
+
from sqlalchemy.orm import sessionmaker
|
|
123
|
+
import os
|
|
124
|
+
from datetime import datetime
|
|
125
|
+
|
|
126
|
+
${dbUrlLine}
|
|
127
|
+
|
|
128
|
+
engine = create_engine(DATABASE_URL)
|
|
129
|
+
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
|
130
|
+
Base = declarative_base()
|
|
131
|
+
|
|
132
|
+
class User(Base):
|
|
133
|
+
__tablename__ = "users"
|
|
134
|
+
|
|
135
|
+
id = Column(Integer, primary_key=True, index=True)
|
|
136
|
+
email = Column(String, unique=True, index=True)
|
|
137
|
+
name = Column(String, nullable=True)
|
|
138
|
+
created_at = Column(DateTime, default=datetime.utcnow)
|
|
139
|
+
|
|
140
|
+
Base.metadata.create_all(bind=engine)
|
|
141
|
+
|
|
142
|
+
def get_db():
|
|
143
|
+
db = SessionLocal()
|
|
144
|
+
try:
|
|
145
|
+
yield db
|
|
146
|
+
finally:
|
|
147
|
+
db.close()
|
|
148
|
+
`;
|
|
149
|
+
await fs_extra_1.default.writeFile(path_1.default.join(srcPath, 'database.py'), dbContent);
|
|
150
|
+
}
|
|
151
|
+
async function generateMongoDBSetup(srcPath, config) {
|
|
152
|
+
const dbContent = `from motor.motor_asyncio import AsyncIOMotorClient
|
|
153
|
+
import os
|
|
154
|
+
|
|
155
|
+
DATABASE_URL = os.getenv("DATABASE_URL", "mongodb://localhost:27017")
|
|
156
|
+
DATABASE_NAME = "${config.projectName}"
|
|
157
|
+
|
|
158
|
+
client = AsyncIOMotorClient(DATABASE_URL)
|
|
159
|
+
db = client[DATABASE_NAME]
|
|
160
|
+
|
|
161
|
+
# Collections
|
|
162
|
+
users_collection = db["users"]
|
|
163
|
+
`;
|
|
164
|
+
await fs_extra_1.default.writeFile(path_1.default.join(srcPath, 'database.py'), dbContent);
|
|
165
|
+
}
|
|
166
|
+
async function generateDynamoDBSetup(srcPath, config) {
|
|
167
|
+
const dbContent = `import boto3
|
|
168
|
+
import os
|
|
169
|
+
|
|
170
|
+
dynamodb = boto3.resource(
|
|
171
|
+
'dynamodb',
|
|
172
|
+
region_name=os.getenv('AWS_REGION', 'us-east-1')
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
# Table references
|
|
176
|
+
users_table = dynamodb.Table('users')
|
|
177
|
+
`;
|
|
178
|
+
await fs_extra_1.default.writeFile(path_1.default.join(srcPath, 'database.py'), dbContent);
|
|
179
|
+
}
|
|
180
|
+
async function generateJSONFileSetup(srcPath, config) {
|
|
181
|
+
const backendPath = path_1.default.dirname(path_1.default.dirname(srcPath));
|
|
182
|
+
const dbContent = `import json
|
|
183
|
+
import os
|
|
184
|
+
from pathlib import Path
|
|
185
|
+
from typing import Dict, List, Any, Optional
|
|
186
|
+
|
|
187
|
+
DB_FILE = Path(__file__).parent.parent / 'data' / 'database.json'
|
|
188
|
+
|
|
189
|
+
# Ensure data directory exists
|
|
190
|
+
DB_FILE.parent.mkdir(parents=True, exist_ok=True)
|
|
191
|
+
|
|
192
|
+
# Initialize database file if it doesn't exist
|
|
193
|
+
if not DB_FILE.exists():
|
|
194
|
+
with open(DB_FILE, 'w') as f:
|
|
195
|
+
json.dump({'users': []}, f, indent=2)
|
|
196
|
+
|
|
197
|
+
def read_db() -> Dict[str, Any]:
|
|
198
|
+
"""Read the database from JSON file."""
|
|
199
|
+
try:
|
|
200
|
+
with open(DB_FILE, 'r') as f:
|
|
201
|
+
return json.load(f)
|
|
202
|
+
except (FileNotFoundError, json.JSONDecodeError):
|
|
203
|
+
return {'users': []}
|
|
204
|
+
|
|
205
|
+
def write_db(data: Dict[str, Any]) -> None:
|
|
206
|
+
"""Write data to the database JSON file."""
|
|
207
|
+
with open(DB_FILE, 'w') as f:
|
|
208
|
+
json.dump(data, f, indent=2)
|
|
209
|
+
|
|
210
|
+
def get_users() -> List[Dict[str, Any]]:
|
|
211
|
+
"""Get all users from the database."""
|
|
212
|
+
db = read_db()
|
|
213
|
+
return db.get('users', [])
|
|
214
|
+
|
|
215
|
+
def add_user(user: Dict[str, Any]) -> Dict[str, Any]:
|
|
216
|
+
"""Add a new user to the database."""
|
|
217
|
+
db = read_db()
|
|
218
|
+
users = db.get('users', [])
|
|
219
|
+
new_user = {
|
|
220
|
+
**user,
|
|
221
|
+
'id': len(users) + 1,
|
|
222
|
+
'created_at': str(Path(__file__).stat().st_mtime)
|
|
223
|
+
}
|
|
224
|
+
users.append(new_user)
|
|
225
|
+
db['users'] = users
|
|
226
|
+
write_db(db)
|
|
227
|
+
return new_user
|
|
228
|
+
|
|
229
|
+
def get_user_by_id(user_id: int) -> Optional[Dict[str, Any]]:
|
|
230
|
+
"""Get a user by ID."""
|
|
231
|
+
users = get_users()
|
|
232
|
+
return next((u for u in users if u.get('id') == user_id), None)
|
|
233
|
+
|
|
234
|
+
def update_user(user_id: int, updates: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
235
|
+
"""Update a user by ID."""
|
|
236
|
+
db = read_db()
|
|
237
|
+
users = db.get('users', [])
|
|
238
|
+
for i, user in enumerate(users):
|
|
239
|
+
if user.get('id') == user_id:
|
|
240
|
+
users[i] = {**user, **updates, 'updated_at': str(Path(__file__).stat().st_mtime)}
|
|
241
|
+
db['users'] = users
|
|
242
|
+
write_db(db)
|
|
243
|
+
return users[i]
|
|
244
|
+
return None
|
|
245
|
+
|
|
246
|
+
def delete_user(user_id: int) -> bool:
|
|
247
|
+
"""Delete a user by ID."""
|
|
248
|
+
db = read_db()
|
|
249
|
+
users = db.get('users', [])
|
|
250
|
+
db['users'] = [u for u in users if u.get('id') != user_id]
|
|
251
|
+
write_db(db)
|
|
252
|
+
return True
|
|
253
|
+
`;
|
|
254
|
+
await fs_extra_1.default.writeFile(path_1.default.join(srcPath, 'database.py'), dbContent);
|
|
255
|
+
// Create initial data directory and file
|
|
256
|
+
const dataPath = path_1.default.join(backendPath, 'data');
|
|
257
|
+
await fs_extra_1.default.ensureDir(dataPath);
|
|
258
|
+
await fs_extra_1.default.writeJSON(path_1.default.join(dataPath, 'database.json'), { users: [] }, { spaces: 2 });
|
|
259
|
+
}
|