openwork-agent 1.0.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.
Files changed (46) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +436 -0
  3. package/package.json +78 -0
  4. package/src/core/TechDetector.js +351 -0
  5. package/src/generators/ProjectGenerator.js +1241 -0
  6. package/src/generators/ProjectGeneratorExtensions.js +14 -0
  7. package/src/generators/TemplateMethods.js +402 -0
  8. package/src/generators/index.js +5 -0
  9. package/src/index.js +152 -0
  10. package/src/main.js +8 -0
  11. package/src/templates/common/README.md.hbs +358 -0
  12. package/src/templates/docker/index.js +518 -0
  13. package/src/templates/docker.js +58 -0
  14. package/src/templates/go/basic/api/routes/user.go.hbs +138 -0
  15. package/src/templates/go/basic/config/config.go.hbs +54 -0
  16. package/src/templates/go/basic/go.mod.hbs +8 -0
  17. package/src/templates/go/basic/main.go.hbs +70 -0
  18. package/src/templates/go/basic/models/user.go.hbs +69 -0
  19. package/src/templates/go/basic/services/user_service.go.hbs +173 -0
  20. package/src/templates/java/basic/src/main/java/com/{{snakeCase projectName}}/{{projectName}}/controller/UserController.java.hbs +91 -0
  21. package/src/templates/java/basic/src/main/java/com/{{snakeCase projectName}}/{{projectName}}/dto/ApiResponse.java.hbs +40 -0
  22. package/src/templates/java/basic/src/main/java/com/{{snakeCase projectName}}/{{projectName}}/model/User.java.hbs +102 -0
  23. package/src/templates/java/basic/src/main/java/com/{{snakeCase projectName}}/{{projectName}}/repository/UserRepository.java.hbs +20 -0
  24. package/src/templates/java/basic/src/main/java/com/{{snakeCase projectName}}/{{projectName}}/service/UserService.java.hbs +65 -0
  25. package/src/templates/java/basic/src/main/java/com/{{snakeCase projectName}}/{{projectName}}/{{pascalCase projectName}}Application.java.hbs +16 -0
  26. package/src/templates/node/basic/src/config/database.ts.hbs +18 -0
  27. package/src/templates/node/basic/src/controllers/UserController.ts.hbs +98 -0
  28. package/src/templates/node/basic/src/index.ts.hbs +45 -0
  29. package/src/templates/node/basic/src/middleware/errorHandler.ts.hbs +33 -0
  30. package/src/templates/node/basic/src/routes/index.ts.hbs +42 -0
  31. package/src/templates/node/basic/src/types/index.ts.hbs +18 -0
  32. package/src/templates/python/basic/config/database.py.hbs +36 -0
  33. package/src/templates/python/basic/main.py.hbs +58 -0
  34. package/src/templates/python/basic/middleware/error_handler.py.hbs +41 -0
  35. package/src/templates/python/basic/models/user.py.hbs +40 -0
  36. package/src/templates/python/basic/routes/__init__.py.hbs +12 -0
  37. package/src/templates/python/basic/routes/users.py.hbs +64 -0
  38. package/src/templates/rust/basic/Cargo.toml.hbs +39 -0
  39. package/src/templates/rust/basic/src/config/database.rs.hbs +27 -0
  40. package/src/templates/rust/basic/src/handlers/user.rs.hbs +130 -0
  41. package/src/templates/rust/basic/src/handlers/user_routes.rs.hbs +15 -0
  42. package/src/templates/rust/basic/src/main.rs.hbs +53 -0
  43. package/src/templates/rust/basic/src/models/mod.rs.hbs +79 -0
  44. package/src/templates/rust/basic/src/schema.rs.hbs +10 -0
  45. package/src/utils/FileManager.js +186 -0
  46. package/src/utils/Templates.js +231 -0
@@ -0,0 +1,1241 @@
1
+ const inquirer = require('inquirer');
2
+ const chalk = require('chalk');
3
+ const ora = require('ora');
4
+ const fs = require('fs-extra');
5
+ const path = require('path');
6
+
7
+ const FileManager = require('../utils/FileManager');
8
+ const TechDetector = require('../core/TechDetector');
9
+
10
+ class ProjectGenerator {
11
+ constructor() {
12
+ this.fileManager = new FileManager();
13
+ this.techDetector = new TechDetector();
14
+ this.templatesPath = path.join(__dirname, '../templates');
15
+ }
16
+
17
+ async promptForConfiguration(config) {
18
+ const questions = [];
19
+
20
+ // Technology selection
21
+ if (!config.technology) {
22
+ const technologies = this.techDetector.getAvailableTechnologies();
23
+ const recommended = this.techDetector.getRecommendedTechStack();
24
+
25
+ questions.push({
26
+ type: 'list',
27
+ name: 'technology',
28
+ message: 'Choose your technology stack:',
29
+ choices: [
30
+ ...technologies.map(tech => ({
31
+ name: `${tech.toUpperCase()} - ${recommended[tech]?.reason || 'Popular backend technology'}`,
32
+ value: tech
33
+ })),
34
+ new inquirer.Separator()
35
+ ]
36
+ });
37
+ }
38
+
39
+ // Framework selection (after technology is known)
40
+ const tech = config.technology || (await inquirer.prompt([{
41
+ type: 'list',
42
+ name: 'technology',
43
+ message: 'Choose your technology stack:',
44
+ choices: this.techDetector.getAvailableTechnologies()
45
+ }])).technology;
46
+
47
+ const frameworks = this.techDetector.getFrameworksForTechnology(tech);
48
+ if (frameworks.length > 0 && !config.framework) {
49
+ questions.push({
50
+ type: 'list',
51
+ name: 'framework',
52
+ message: `Choose ${tech} framework:`,
53
+ choices: [
54
+ ...frameworks.map(fw => fw.toUpperCase()),
55
+ { name: 'None/Custom', value: null }
56
+ ]
57
+ });
58
+ }
59
+
60
+ // Database selection
61
+ if (!config.database) {
62
+ const databases = this.techDetector.getAvailableDatabases();
63
+ questions.push({
64
+ type: 'list',
65
+ name: 'database',
66
+ message: 'Choose your database:',
67
+ choices: [
68
+ ...databases.map(db => db.toUpperCase()),
69
+ { name: 'None/No database', value: null }
70
+ ]
71
+ });
72
+ }
73
+
74
+ // Additional options
75
+ if (!config.template) {
76
+ questions.push({
77
+ type: 'list',
78
+ name: 'template',
79
+ message: 'Choose project template:',
80
+ choices: [
81
+ { name: 'Basic REST API', value: 'api' },
82
+ { name: 'Full CRUD App', value: 'crud' },
83
+ { name: 'Microservice', value: 'microservice' },
84
+ { name: 'GraphQL API', value: 'graphql' },
85
+ { name: 'Minimal', value: 'minimal' }
86
+ ]
87
+ });
88
+ }
89
+
90
+ questions.push(
91
+ {
92
+ type: 'confirm',
93
+ name: 'includeDocker',
94
+ message: 'Include Docker configuration?',
95
+ default: true
96
+ },
97
+ {
98
+ type: 'confirm',
99
+ name: 'includeTests',
100
+ message: 'Include test setup?',
101
+ default: true
102
+ },
103
+ {
104
+ type: 'confirm',
105
+ name: 'includeCI',
106
+ message: 'Include CI/CD configuration?',
107
+ default: false
108
+ },
109
+ {
110
+ type: 'input',
111
+ name: 'author',
112
+ message: 'Author name:',
113
+ default: 'OpenWork Agent'
114
+ },
115
+ {
116
+ type: 'input',
117
+ name: 'description',
118
+ message: 'Project description:',
119
+ default: `${config.projectName} - Backend application`
120
+ }
121
+ );
122
+
123
+ const answers = await inquirer.prompt(questions);
124
+ return { ...config, ...answers };
125
+ }
126
+
127
+ async createProject(config) {
128
+ const spinner = ora('Creating project...').start();
129
+
130
+ try {
131
+ const projectPath = path.resolve(process.cwd(), config.projectName);
132
+
133
+ // Check if directory already exists
134
+ if (await fs.pathExists(projectPath)) {
135
+ throw new Error(`Directory ${config.projectName} already exists`);
136
+ }
137
+
138
+ // Create project directory
139
+ await fs.ensureDir(projectPath);
140
+ spinner.text = 'Setting up project structure...';
141
+
142
+ // Generate project structure
143
+ await this.generateProjectStructure(projectPath, config);
144
+
145
+ // Copy template files
146
+ await this.copyTemplateFiles(projectPath, config);
147
+
148
+ // Generate configuration files
149
+ await this.generateConfiguration(projectPath, config);
150
+
151
+ // Generate Docker files if requested
152
+ if (config.includeDocker) {
153
+ await this.generateDockerFiles(projectPath, config);
154
+ }
155
+
156
+ // Generate CI/CD if requested
157
+ if (config.includeCI) {
158
+ await this.generateCIFiles(projectPath, config);
159
+ }
160
+
161
+ spinner.succeed(`Project ${config.projectName} created successfully!`);
162
+
163
+ // Show next steps
164
+ this.showNextSteps(config);
165
+
166
+ } catch (error) {
167
+ spinner.fail('Failed to create project');
168
+ throw error;
169
+ }
170
+ }
171
+
172
+ async generateProjectStructure(projectPath, config) {
173
+ const structure = this.getProjectStructure(config);
174
+
175
+ for (const dir of structure.directories) {
176
+ await fs.ensureDir(path.join(projectPath, dir));
177
+ }
178
+
179
+ // Create basic files
180
+ for (const [filename, content] of Object.entries(structure.files || {})) {
181
+ await fs.writeFile(
182
+ path.join(projectPath, filename),
183
+ typeof content === 'function' ? content(config) : content
184
+ );
185
+ }
186
+ }
187
+
188
+ getProjectStructure(config) {
189
+ const baseStructure = {
190
+ directories: [
191
+ 'src',
192
+ 'src/controllers',
193
+ 'src/models',
194
+ 'src/routes',
195
+ 'src/middleware',
196
+ 'src/services',
197
+ 'src/utils',
198
+ 'src/config',
199
+ 'tests',
200
+ 'docs'
201
+ ],
202
+ files: {
203
+ '.gitignore': this.generateGitignore(config),
204
+ 'README.md': () => this.generateReadme(config)
205
+ }
206
+ };
207
+
208
+ // Add technology-specific structure
209
+ const techStructure = this.getTechnologySpecificStructure(config);
210
+
211
+ return {
212
+ directories: [...baseStructure.directories, ...techStructure.directories],
213
+ files: { ...baseStructure.files, ...techStructure.files }
214
+ };
215
+ }
216
+
217
+ getTechnologySpecificStructure(config) {
218
+ const structures = {
219
+ node: {
220
+ directories: ['src/types', 'src/interfaces', 'src/dto'],
221
+ files: {
222
+ 'package.json': () => this.generatePackageJSON(config),
223
+ 'tsconfig.json': () => this.generateTsConfig(config),
224
+ '.env.example': this.generateEnvExample(config)
225
+ }
226
+ },
227
+ python: {
228
+ directories: ['src/api', 'src/db', 'migrations'],
229
+ files: {
230
+ 'requirements.txt': () => this.generateRequirements(config),
231
+ 'setup.py': () => this.generateSetupPy(config),
232
+ '.env.example': this.generateEnvExample(config)
233
+ }
234
+ },
235
+ java: {
236
+ directories: ['src/main/java', 'src/main/resources', 'src/test/java'],
237
+ files: {
238
+ 'pom.xml': () => this.generatePomXML(config),
239
+ 'src/main/resources/application.properties': () => this.generateApplicationProperties(config)
240
+ }
241
+ },
242
+ go: {
243
+ directories: ['internal', 'pkg', 'cmd', 'api'],
244
+ files: {
245
+ 'go.mod': () => this.generateGoMod(config),
246
+ 'main.go': () => this.generateMainGo(config)
247
+ }
248
+ },
249
+ rust: {
250
+ directories: ['src/bin', 'tests'],
251
+ files: {
252
+ 'Cargo.toml': () => this.generateCargoToml(config),
253
+ 'src/main.rs': () => this.generateMainRs(config)
254
+ }
255
+ },
256
+ php: {
257
+ directories: ['app/Http/Controllers', 'app/Models', 'database/migrations', 'routes'],
258
+ files: {
259
+ 'composer.json': () => this.generateComposerJSON(config),
260
+ '.env.example': this.generateEnvExample(config)
261
+ }
262
+ }
263
+ };
264
+
265
+ return structures[config.technology] || { directories: [], files: {} };
266
+ }
267
+
268
+ async copyTemplateFiles(projectPath, config) {
269
+ const templatePath = path.join(this.templatesPath, config.technology);
270
+
271
+ if (!await fs.pathExists(templatePath)) {
272
+ console.log(chalk.yellow(`Warning: No templates found for ${config.technology}`));
273
+ return;
274
+ }
275
+
276
+ const frameworkPath = config.framework ?
277
+ path.join(templatePath, config.framework) :
278
+ path.join(templatePath, 'basic');
279
+
280
+ if (await fs.pathExists(frameworkPath)) {
281
+ await this.fileManager.copyDirectory(frameworkPath, projectPath, config);
282
+ }
283
+ }
284
+
285
+ async generateConfiguration(projectPath, config) {
286
+ // Generate config files
287
+ const configPath = path.join(projectPath, 'src/config');
288
+
289
+ switch (config.technology) {
290
+ case 'node':
291
+ await this.generateNodeConfig(configPath, config);
292
+ break;
293
+ case 'python':
294
+ await this.generatePythonConfig(configPath, config);
295
+ break;
296
+ case 'java':
297
+ await this.generateJavaConfig(projectPath, config);
298
+ break;
299
+ case 'go':
300
+ await this.generateGoConfig(projectPath, config);
301
+ break;
302
+ case 'rust':
303
+ await this.generateRustConfig(projectPath, config);
304
+ break;
305
+ case 'php':
306
+ await this.generatePhpConfig(projectPath, config);
307
+ break;
308
+ }
309
+ }
310
+
311
+ async generateDockerFiles(projectPath, config) {
312
+ const dockerConfigs = {
313
+ node: this.generateDockerfileNode,
314
+ python: this.generateDockerfilePython,
315
+ java: this.generateDockerfileJava,
316
+ go: this.generateDockerfileGo,
317
+ rust: this.generateDockerfileRust,
318
+ php: this.generateDockerfilePhp
319
+ };
320
+
321
+ const dockerfile = dockerConfigs[config.technology];
322
+ if (dockerfile) {
323
+ await fs.writeFile(
324
+ path.join(projectPath, 'Dockerfile'),
325
+ dockerfile.call(this, config)
326
+ );
327
+ }
328
+
329
+ // Generate docker-compose.yml if database is selected
330
+ if (config.database) {
331
+ await fs.writeFile(
332
+ path.join(projectPath, 'docker-compose.yml'),
333
+ this.generateDockerCompose(config)
334
+ );
335
+ }
336
+
337
+ // Generate .dockerignore
338
+ await fs.writeFile(
339
+ path.join(projectPath, '.dockerignore'),
340
+ this.generateDockerignore(config)
341
+ );
342
+ }
343
+
344
+ getSetupCommands(technology) {
345
+ const commands = {
346
+ node: ['npm install', 'npm run dev'],
347
+ python: ['python -m venv venv', 'source venv/bin/activate', 'pip install -r requirements.txt', 'uvicorn main:app --reload'],
348
+ java: ['mvn clean install', 'mvn spring-boot:run'],
349
+ go: ['go mod download', 'go run main.go'],
350
+ rust: ['cargo build', 'cargo run'],
351
+ php: ['composer install', 'php artisan serve']
352
+ };
353
+
354
+ return commands[technology] || ['echo "Setup commands not defined for this technology"'];
355
+ }
356
+
357
+ showNextSteps(config) {
358
+ console.log(chalk.cyan('\n📋 Next Steps:'));
359
+ console.log(chalk.white(` cd ${config.projectName}`));
360
+
361
+ this.getSetupCommands(config.technology).forEach(cmd => {
362
+ console.log(chalk.white(` ${cmd}`));
363
+ });
364
+
365
+ if (config.includeDocker) {
366
+ console.log(chalk.white(' docker-compose up'));
367
+ }
368
+
369
+ console.log(chalk.green('\n✨ Happy coding!'));
370
+ }
371
+
372
+ // Template generation methods
373
+ generatePackageJSON(config) {
374
+ return JSON.stringify({
375
+ name: config.projectName,
376
+ version: '1.0.0',
377
+ description: config.description,
378
+ main: 'dist/index.js',
379
+ scripts: {
380
+ dev: 'nodemon src/index.ts',
381
+ build: 'tsc',
382
+ start: 'node dist/index.js',
383
+ test: 'jest',
384
+ 'test:watch': 'jest --watch'
385
+ },
386
+ dependencies: this.getNodeDependencies(config),
387
+ devDependencies: this.getNodeDevDependencies(config),
388
+ author: config.author,
389
+ license: 'MIT'
390
+ }, null, 2);
391
+ }
392
+
393
+ getNodeDependencies(config) {
394
+ const deps = {
395
+ express: ['express', 'cors', 'helmet'],
396
+ nestjs: ['@nestjs/core', '@nestjs/common', '@nestjs/platform-express'],
397
+ fastify: ['fastify', '@fastify/cors']
398
+ };
399
+
400
+ const dbDeps = {
401
+ mongodb: ['mongoose'],
402
+ postgresql: ['pg', 'typeorm'],
403
+ mysql: ['mysql2', 'typeorm'],
404
+ sqlite: ['sqlite3', 'typeorm']
405
+ };
406
+
407
+ const frameworkDeps = deps[config.framework] || deps.express;
408
+ const databaseDeps = config.database ? dbDeps[config.database] : [];
409
+
410
+ return [...new Set([...frameworkDeps, ...databaseDeps, 'dotenv'])];
411
+ }
412
+
413
+ getNodeDevDependencies(config) {
414
+ return [
415
+ '@types/node',
416
+ 'typescript',
417
+ 'ts-node',
418
+ 'nodemon',
419
+ 'jest',
420
+ '@types/jest',
421
+ 'ts-jest'
422
+ ];
423
+ }
424
+
425
+ generateTsConfig(config) {
426
+ return JSON.stringify({
427
+ compilerOptions: {
428
+ target: 'ES2020',
429
+ module: 'commonjs',
430
+ lib: ['ES2020'],
431
+ outDir: './dist',
432
+ rootDir: './src',
433
+ strict: true,
434
+ esModuleInterop: true,
435
+ skipLibCheck: true,
436
+ forceConsistentCasingInFileNames: true,
437
+ resolveJsonModule: true,
438
+ declaration: true,
439
+ declarationMap: true,
440
+ sourceMap: true
441
+ },
442
+ include: ['src/**/*'],
443
+ exclude: ['node_modules', 'dist', 'tests']
444
+ }, null, 2);
445
+ }
446
+
447
+ generateGitignore(config) {
448
+ return `# Dependencies
449
+ node_modules/
450
+ npm-debug.log*
451
+ yarn-debug.log*
452
+ yarn-error.log*
453
+
454
+ # Build output
455
+ dist/
456
+ build/
457
+
458
+ # Environment variables
459
+ .env
460
+ .env.local
461
+ .env.development.local
462
+ .env.test.local
463
+ .env.production.local
464
+
465
+ # IDE files
466
+ .vscode/
467
+ .idea/
468
+ *.swp
469
+ *.swo
470
+
471
+ # OS files
472
+ .DS_Store
473
+ Thumbs.db
474
+
475
+ # Logs
476
+ logs/
477
+ *.log
478
+
479
+ # Coverage reports
480
+ coverage/
481
+ .nyc_output/
482
+
483
+ # Database
484
+ *.db
485
+ *.sqlite
486
+
487
+ # Temporary files
488
+ tmp/
489
+ temp/`;
490
+ }
491
+
492
+ generateEnvExample(config) {
493
+ return `# Server Configuration
494
+ PORT=3000
495
+ NODE_ENV=development
496
+
497
+ # Database Configuration
498
+ DATABASE_URL=
499
+ DB_NAME=
500
+
501
+ # Security
502
+ JWT_SECRET=your-secret-key-here
503
+ JWT_EXPIRES_IN=7d
504
+
505
+ # CORS
506
+ CORS_ORIGINS=http://localhost:3000,http://localhost:3001
507
+
508
+ # Logging
509
+ LOG_LEVEL=info
510
+
511
+ # External Services
512
+ # API_KEY=
513
+ # WEBHOOK_URL=`;
514
+ }
515
+
516
+ generateCargoToml(config) {
517
+ return `[package]
518
+ name = "${config.projectName}"
519
+ version = "0.1.0"
520
+ edition = "2021"
521
+ authors = ["${config.author || 'OpenWork Agent'}"]
522
+ description = "${config.description || 'Rust backend application'}"
523
+
524
+ [dependencies]
525
+ tokio = { version = "1.0", features = ["full"] }
526
+ serde = { version = "1.0", features = ["derive"] }
527
+ serde_json = "1.0"
528
+ actix-web = "4.0"
529
+ actix-cors = "0.6"
530
+ dotenv = "0.15"
531
+ env_logger = "0.10"
532
+ log = "0.4"
533
+
534
+ [dev-dependencies]
535
+ tokio-test = "0.4"
536
+ `;
537
+ }
538
+
539
+ generateGoMod(config) {
540
+ return `module ${config.projectName}
541
+
542
+ go 1.21
543
+
544
+ require (
545
+ github.com/gin-gonic/gin v1.9.1
546
+ github.com/joho/godotenv v1.4.0
547
+ github.com/gin-contrib/cors v1.4.0
548
+ gorm.io/gorm v1.25.4
549
+ gorm.io/driver/postgres v1.5.2
550
+ )`;
551
+ }
552
+
553
+ generateReadme(config) {
554
+ return `# ${config.projectName}
555
+
556
+ ${config.description || 'Backend application'}
557
+
558
+ ## Getting Started
559
+
560
+ ### Prerequisites
561
+ - Node.js 14+
562
+ - npm or yarn
563
+
564
+ ### Installation
565
+
566
+ \`\`\`bash
567
+ npm install
568
+ \`\`\`
569
+
570
+ ### Development
571
+
572
+ \`\`\`bash
573
+ npm run dev
574
+ \`\`\`
575
+
576
+ ### Build
577
+
578
+ \`\`\`bash
579
+ npm run build
580
+ \`\`\`
581
+
582
+ ### Test
583
+
584
+ \`\`\`bash
585
+ npm test
586
+ \`\`\`
587
+
588
+ ## API Documentation
589
+
590
+ Visit \`http://localhost:3000/api-docs\` for API documentation.
591
+
592
+ ## License
593
+
594
+ MIT
595
+ `;
596
+ }
597
+
598
+ // Additional methods for completeness
599
+ generateRequirements(config) {
600
+ const requirements = [
601
+ 'fastapi==0.104.1',
602
+ 'uvicorn[standard]==0.24.0',
603
+ 'pydantic==2.5.0',
604
+ 'python-dotenv==1.0.0'
605
+ ];
606
+
607
+ if (config.database === 'postgresql') {
608
+ requirements.push('asyncpg==0.29.0', 'sqlalchemy==2.0.23');
609
+ } else if (config.database === 'mongodb') {
610
+ requirements.push('motor==3.3.2', 'pymongo==4.6.0');
611
+ }
612
+
613
+ return requirements.join('\n');
614
+ }
615
+
616
+ generateSetupPy(config) {
617
+ return `from setuptools import setup, find_packages
618
+
619
+ setup(
620
+ name="${config.projectName}",
621
+ version="1.0.0",
622
+ description="${config.description || 'Python backend application'}",
623
+ author="${config.author || 'OpenWork Agent'}",
624
+ packages=find_packages(),
625
+ install_requires=open('requirements.txt').read().splitlines(),
626
+ python_requires=">=3.8",
627
+ )`;
628
+ }
629
+
630
+ generatePomXML(config) {
631
+ return `<?xml version="1.0" encoding="UTF-8"?>
632
+ <project xmlns="http://maven.apache.org/POM/4.0.0"
633
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
634
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
635
+ http://maven.apache.org/xsd/maven-4.0.0.xsd">
636
+ <modelVersion>4.0.0</modelVersion>
637
+
638
+ <groupId>com.openwork</groupId>
639
+ <artifactId>${config.projectName}</artifactId>
640
+ <version>1.0.0</version>
641
+ <packaging>jar</packaging>
642
+
643
+ <name>${config.projectName}</name>
644
+ <description>${config.description || 'Java backend application'}</description>
645
+
646
+ <parent>
647
+ <groupId>org.springframework.boot</groupId>
648
+ <artifactId>spring-boot-starter-parent</artifactId>
649
+ <version>3.1.5</version>
650
+ </parent>
651
+
652
+ <properties>
653
+ <java.version>17</java.version>
654
+ </properties>
655
+
656
+ <dependencies>
657
+ <dependency>
658
+ <groupId>org.springframework.boot</groupId>
659
+ <artifactId>spring-boot-starter-web</artifactId>
660
+ </dependency>
661
+ <dependency>
662
+ <groupId>org.springframework.boot</groupId>
663
+ <artifactId>spring-boot-starter-data-jpa</artifactId>
664
+ </dependency>
665
+ <dependency>
666
+ <groupId>org.springframework.boot</groupId>
667
+ <artifactId>spring-boot-starter-test</artifactId>
668
+ <scope>test</scope>
669
+ </dependency>
670
+ </dependencies>
671
+
672
+ <build>
673
+ <plugins>
674
+ <plugin>
675
+ <groupId>org.springframework.boot</groupId>
676
+ <artifactId>spring-boot-maven-plugin</artifactId>
677
+ </plugin>
678
+ </plugins>
679
+ </build>
680
+ </project>`;
681
+ }
682
+
683
+ generateApplicationProperties(config) {
684
+ return `# Server Configuration
685
+ server.port=8080
686
+ server.servlet.context-path=/
687
+
688
+ # Database Configuration
689
+ spring.datasource.url=jdbc:postgresql://localhost:5432/${config.projectName}
690
+ spring.datasource.username=postgres
691
+ spring.datasource.password=password
692
+ spring.datasource.driver-class-name=org.postgresql.Driver
693
+
694
+ # JPA Configuration
695
+ spring.jpa.hibernate.ddl-auto=update
696
+ spring.jpa.show-sql=true
697
+ spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
698
+
699
+ # Logging
700
+ logging.level.com.openwork=INFO
701
+ logging.level.org.springframework.web=WARN`;
702
+ }
703
+
704
+ generateComposerJSON(config) {
705
+ return JSON.stringify({
706
+ name: config.projectName,
707
+ description: config.description || 'PHP backend application',
708
+ type: 'project',
709
+ require: {
710
+ php: '^8.1',
711
+ 'laravel/framework': '^10.0'
712
+ },
713
+ requireDev: {
714
+ 'phpunit/phpunit': '^10.0'
715
+ },
716
+ autoload: {
717
+ psr4: {
718
+ 'App\\\\': 'app/'
719
+ }
720
+ },
721
+ authors: [
722
+ {
723
+ name: config.author || 'OpenWork Agent'
724
+ }
725
+ ],
726
+ minimumStability: 'stable',
727
+ preferStable: true
728
+ }, null, 2);
729
+ }
730
+
731
+ generateMainGo(config) {
732
+ return `package main
733
+
734
+ import (
735
+ "log"
736
+ "net/http"
737
+ "github.com/gin-gonic/gin"
738
+ "github.com/gin-contrib/cors"
739
+ "github.com/joho/godotenv"
740
+ )
741
+
742
+ func main() {
743
+ // Load environment variables
744
+ if err := godotenv.Load(); err != nil {
745
+ log.Println("No .env file found")
746
+ }
747
+
748
+ // Initialize Gin router
749
+ r := gin.Default()
750
+
751
+ // Configure CORS
752
+ r.Use(cors.Default())
753
+
754
+ // Health check endpoint
755
+ r.GET("/health", func(c *gin.Context) {
756
+ c.JSON(http.StatusOK, gin.H{
757
+ "status": "ok",
758
+ "service": "${config.projectName}",
759
+ })
760
+ })
761
+
762
+ // Start server
763
+ log.Println("Server starting on :8080")
764
+ if err := r.Run(":8080"); err != nil {
765
+ log.Fatal("Failed to start server:", err)
766
+ }
767
+ }`;
768
+ }
769
+
770
+ generateMainRs(config) {
771
+ return `use actix_web::{get, App, HttpServer, Responder, HttpResponse};
772
+ use std::env;
773
+
774
+ #[get("/health")]
775
+ async fn health() -> impl Responder {
776
+ HttpResponse::Ok().json(serde_json::json!({
777
+ "status": "ok",
778
+ "service": "${config.projectName}"
779
+ }))
780
+ }
781
+
782
+ #[actix_web::main]
783
+ async fn main() -> std::io::Result<()> {
784
+ // Load environment variables
785
+ dotenv::dotenv().ok();
786
+
787
+ let port = env::var("PORT").unwrap_or_else(|_| "8080".to_string());
788
+ let bind_address = format!("0.0.0.0:{}", port);
789
+
790
+ HttpServer::new(|| {
791
+ App::new()
792
+ .service(health)
793
+ })
794
+ .bind(&bind_address)?
795
+ .run()
796
+ .await
797
+ }`;
798
+ }
799
+
800
+ // Docker generation methods
801
+ generateDockerfileNode(config) {
802
+ return `FROM node:18-alpine
803
+
804
+ WORKDIR /app
805
+
806
+ COPY package*.json ./
807
+ RUN npm ci --only=production
808
+
809
+ COPY . .
810
+ RUN npm run build
811
+
812
+ EXPOSE 3000
813
+
814
+ CMD ["npm", "start"]`;
815
+ }
816
+
817
+ generateDockerfilePython(config) {
818
+ return `FROM python:3.11-slim
819
+
820
+ WORKDIR /app
821
+
822
+ COPY requirements.txt .
823
+ RUN pip install --no-cache-dir -r requirements.txt
824
+
825
+ COPY . .
826
+
827
+ EXPOSE 8000
828
+
829
+ CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]`;
830
+ }
831
+
832
+ generateDockerfileJava(config) {
833
+ return `FROM maven:3.9-openjdk-17
834
+
835
+ WORKDIR /app
836
+ COPY pom.xml .
837
+ RUN mvn dependency:go-offline
838
+
839
+ COPY . .
840
+ RUN mvn clean package -DskipTests
841
+
842
+ EXPOSE 8080
843
+
844
+ CMD ["java", "-jar", "target/${config.projectName}-1.0.0.jar"]`;
845
+ }
846
+
847
+ generateDockerfileGo(config) {
848
+ return `FROM golang:1.21-alpine AS builder
849
+
850
+ WORKDIR /app
851
+ COPY go.mod go.sum ./
852
+ RUN go mod download
853
+
854
+ COPY . .
855
+ RUN CGO_ENABLED=0 go build -o main .
856
+
857
+ FROM alpine:latest
858
+ RUN apk --no-cache add ca-certificates
859
+ WORKDIR /app
860
+ COPY --from=builder /app/main .
861
+
862
+ EXPOSE 8080
863
+
864
+ CMD ["./main"]`;
865
+ }
866
+
867
+ generateDockerfileRust(config) {
868
+ return `FROM rust:1.73-alpine AS builder
869
+
870
+ WORKDIR /app
871
+ COPY Cargo.toml Cargo.lock ./
872
+ RUN mkdir src && echo "fn main() {}" > src/main.rs
873
+ RUN cargo build --release
874
+ RUN rm -rf src
875
+
876
+ COPY . .
877
+ RUN touch src/main.rs && cargo build --release
878
+
879
+ FROM alpine:latest
880
+ RUN apk --no-cache add ca-certificates
881
+ WORKDIR /app
882
+ COPY --from=builder /app/target/release/${config.projectName} .
883
+
884
+ EXPOSE 8080
885
+
886
+ CMD ["./${config.projectName}"]`;
887
+ }
888
+
889
+ generateDockerfilePhp(config) {
890
+ return `FROM php:8.1-fpm-alpine
891
+
892
+ WORKDIR /app
893
+
894
+ RUN docker-php-ext-install pdo pdo_mysql
895
+
896
+ COPY . .
897
+
898
+ EXPOSE 9000
899
+
900
+ CMD ["php-fpm"]`;
901
+ }
902
+
903
+ generateDockerCompose(config) {
904
+ const dbServices = {
905
+ mongodb: `
906
+ mongodb:
907
+ image: mongo:7.0
908
+ ports:
909
+ - "27017:27017"
910
+ volumes:
911
+ - mongodb_data:/data/db`,
912
+ postgresql: `
913
+ postgres:
914
+ image: postgres:15
915
+ ports:
916
+ - "5432:5432"
917
+ environment:
918
+ POSTGRES_DB: ${config.projectName}
919
+ POSTGRES_USER: postgres
920
+ POSTGRES_PASSWORD: password
921
+ volumes:
922
+ - postgres_data:/var/lib/postgresql/data`,
923
+ mysql: `
924
+ mysql:
925
+ image: mysql:8.0
926
+ ports:
927
+ - "3306:3306"
928
+ environment:
929
+ MYSQL_DATABASE: ${config.projectName}
930
+ MYSQL_USER: user
931
+ MYSQL_PASSWORD: password
932
+ MYSQL_ROOT_PASSWORD: rootpassword
933
+ volumes:
934
+ - mysql_data:/var/lib/mysql`,
935
+ redis: `
936
+ redis:
937
+ image: redis:7.2-alpine
938
+ ports:
939
+ - "6379:6379"
940
+ volumes:
941
+ - redis_data:/data`
942
+ };
943
+
944
+ const dbService = config.database ? dbServices[config.database] : '';
945
+
946
+ return `version: '3.8'
947
+
948
+ services:
949
+ api:
950
+ build: .
951
+ ports:
952
+ - "3000:3000"
953
+ depends_on:
954
+ ${config.database ? ` - ${config.database}` : ''}
955
+ environment:
956
+ - DATABASE_URL=${config.database ? `${config.database}://localhost:27017/${config.projectName}` : ''}
957
+ - NODE_ENV=development
958
+ volumes:
959
+ - .:/app
960
+ - /app/node_modules
961
+ ${dbService}
962
+
963
+ volumes:
964
+ ${config.database ? ` ${config.database}_data:` : ''}`;
965
+ }
966
+
967
+ generateDockerignore(config) {
968
+ return `node_modules
969
+ npm-debug.log
970
+ dist
971
+ .env
972
+ .env.local
973
+ .env.development.local
974
+ .env.test.local
975
+ .env.production.local
976
+ coverage
977
+ .nyc_output
978
+ .DS_Store
979
+ Thumbs.db`;
980
+ }
981
+
982
+ async generateNodeConfig(configPath, config) {
983
+ const configContent = `
984
+ module.exports = {
985
+ port: process.env.PORT || 3000,
986
+ mongodb: {
987
+ uri: process.env.DATABASE_URL || 'mongodb://localhost:27017/${config.projectName}',
988
+ options: {
989
+ useNewUrlParser: true,
990
+ useUnifiedTopology: true,
991
+ }
992
+ },
993
+ jwt: {
994
+ secret: process.env.JWT_SECRET || 'your-secret-key',
995
+ expiresIn: process.env.JWT_EXPIRES_IN || '7d'
996
+ },
997
+ cors: {
998
+ origin: process.env.CORS_ORIGINS ? process.env.CORS_ORIGINS.split(',') : ['http://localhost:3000'],
999
+ credentials: true
1000
+ },
1001
+ logging: {
1002
+ level: process.env.LOG_LEVEL || 'info'
1003
+ }
1004
+ };`;
1005
+
1006
+ await fs.writeFile(path.join(configPath, 'index.js'), configContent);
1007
+ }
1008
+
1009
+ async generatePythonConfig(configPath, config) {
1010
+ const configContent = `
1011
+ import os
1012
+ from typing import List
1013
+
1014
+ class Config:
1015
+ SECRET_KEY = os.environ.get('SECRET_KEY') or 'dev-secret-key'
1016
+ DATABASE_URL = os.environ.get('DATABASE_URL') or 'sqlite:///app.db'
1017
+ SQLALCHEMY_TRACK_MODIFICATIONS = False
1018
+ CORS_ORIGINS = os.environ.get('CORS_ORIGINS', 'http://localhost:3000').split(',')
1019
+ `;
1020
+
1021
+ await fs.writeFile(path.join(configPath, '__init__.py'), configContent);
1022
+ }
1023
+
1024
+ async generateJavaConfig(projectPath, config) {
1025
+ // Spring Boot config is already in application.properties
1026
+ const configDir = path.join(projectPath, 'src/main/java/com/openwork/config');
1027
+ await fs.ensureDir(configDir);
1028
+
1029
+ const configContent = `package com.openwork.config;
1030
+
1031
+ import org.springframework.boot.context.properties.ConfigurationProperties;
1032
+ import org.springframework.stereotype.Component;
1033
+
1034
+ @Component
1035
+ @ConfigurationProperties(prefix = "app")
1036
+ public class AppConfig {
1037
+ private String name = "${config.projectName}";
1038
+ private String version = "1.0.0";
1039
+
1040
+ // getters and setters
1041
+ public String getName() { return name; }
1042
+ public void setName(String name) { this.name = name; }
1043
+
1044
+ public String getVersion() { return version; }
1045
+ public void setVersion(String version) { this.version = version; }
1046
+ }`;
1047
+
1048
+ await fs.writeFile(path.join(configDir, 'AppConfig.java'), configContent);
1049
+ }
1050
+
1051
+ async generateGoConfig(projectPath, config) {
1052
+ const configDir = path.join(projectPath, 'config');
1053
+ await fs.ensureDir(configDir);
1054
+
1055
+ const configContent = `package config
1056
+
1057
+ import (
1058
+ "os"
1059
+ "fmt"
1060
+ )
1061
+
1062
+ type Config struct {
1063
+ Port string
1064
+ DatabaseURL string
1065
+ JWTSecret string
1066
+ CORSOrigins []string
1067
+ }
1068
+
1069
+ func LoadConfig() *Config {
1070
+ return &Config{
1071
+ Port: getEnv("PORT", "8080"),
1072
+ DatabaseURL: getEnv("DATABASE_URL", "postgres://user:password@localhost/${config.projectName}?sslmode=disable"),
1073
+ JWTSecret: getEnv("JWT_SECRET", "your-secret-key"),
1074
+ CORSOrigins: []string{getEnv("CORS_ORIGINS", "http://localhost:3000")},
1075
+ }
1076
+ }
1077
+
1078
+ func getEnv(key, defaultValue string) string {
1079
+ if value := os.Getenv(key); value != "" {
1080
+ return value
1081
+ }
1082
+ return defaultValue
1083
+ }`;
1084
+
1085
+ await fs.writeFile(path.join(configDir, 'config.go'), configContent);
1086
+ }
1087
+
1088
+ async generateRustConfig(projectPath, config) {
1089
+ const configDir = path.join(projectPath, 'src/config');
1090
+ await fs.ensureDir(configDir);
1091
+
1092
+ const configContent = `use std::env;
1093
+
1094
+ pub struct Config {
1095
+ pub port: u16,
1096
+ pub database_url: String,
1097
+ pub jwt_secret: String,
1098
+ }
1099
+
1100
+ impl Config {
1101
+ pub fn from_env() -> Self {
1102
+ Config {
1103
+ port: env::var("PORT")
1104
+ .unwrap_or_else(|_| "8080".to_string())
1105
+ .parse()
1106
+ .unwrap_or(8080),
1107
+ database_url: env::var("DATABASE_URL")
1108
+ .unwrap_or_else(|_| format!("sqlite://{}", "${config.projectName}")),
1109
+ jwt_secret: env::var("JWT_SECRET")
1110
+ .unwrap_or_else(|_| "your-secret-key".to_string()),
1111
+ }
1112
+ }
1113
+ }`;
1114
+
1115
+ await fs.writeFile(path.join(configDir, 'mod.rs'), configContent);
1116
+ await fs.writeFile(path.join(configDir, 'lib.rs'), configContent);
1117
+ }
1118
+
1119
+ async generatePhpConfig(projectPath, config) {
1120
+ const configDir = path.join(projectPath, 'config');
1121
+ await fs.ensureDir(configDir);
1122
+
1123
+ const configContent = `<?php
1124
+
1125
+ return [
1126
+ 'name' => env('APP_NAME', '${config.projectName}'),
1127
+ 'env' => env('APP_ENV', 'development'),
1128
+ 'debug' => (bool) env('APP_DEBUG', true),
1129
+ 'url' => env('APP_URL', 'http://localhost'),
1130
+
1131
+ 'database' => [
1132
+ 'driver' => env('DB_CONNECTION', 'mysql'),
1133
+ 'host' => env('DB_HOST', '127.0.0.1'),
1134
+ 'port' => env('DB_PORT', '3306'),
1135
+ 'database' => env('DB_DATABASE', '${config.projectName}'),
1136
+ 'username' => env('DB_USERNAME', 'root'),
1137
+ 'password' => env('DB_PASSWORD', ''),
1138
+ ],
1139
+
1140
+ 'cors' => [
1141
+ 'paths' => ['api/*'],
1142
+ 'allowed_methods' => ['*'],
1143
+ 'allowed_origins' => ['*'],
1144
+ 'allowed_headers' => ['*'],
1145
+ ],
1146
+ ];`;
1147
+
1148
+ await fs.writeFile(path.join(configDir, 'app.php'), configContent);
1149
+ }
1150
+
1151
+ async generateCIFiles(projectPath, config) {
1152
+ const githubDir = path.join(projectPath, '.github/workflows');
1153
+ await fs.ensureDir(githubDir);
1154
+
1155
+ const ciContent = `name: CI/CD Pipeline
1156
+
1157
+ on:
1158
+ push:
1159
+ branches: [ main, develop ]
1160
+ pull_request:
1161
+ branches: [ main ]
1162
+
1163
+ jobs:
1164
+ test:
1165
+ runs-on: ubuntu-latest
1166
+
1167
+ services:
1168
+ ${config.database ? this.getDatabaseService(config.database) : ''}
1169
+
1170
+ steps:
1171
+ - uses: actions/checkout@v3
1172
+
1173
+ - name: Setup Node.js
1174
+ uses: actions/setup-node@v3
1175
+ with:
1176
+ node-version: '18'
1177
+ cache: 'npm'
1178
+
1179
+ - name: Install dependencies
1180
+ run: npm ci
1181
+
1182
+ - name: Run tests
1183
+ run: npm test
1184
+
1185
+ - name: Build
1186
+ run: npm run build
1187
+
1188
+ deploy:
1189
+ needs: test
1190
+ runs-on: ubuntu-latest
1191
+ if: github.ref == 'refs/heads/main'
1192
+
1193
+ steps:
1194
+ - name: Deploy to production
1195
+ run: echo "Deploy step would go here"`;
1196
+
1197
+ await fs.writeFile(path.join(githubDir, 'ci.yml'), ciContent);
1198
+ }
1199
+
1200
+ getDatabaseService(database) {
1201
+ const services = {
1202
+ mongodb: `mongodb:
1203
+ image: mongo:7.0
1204
+ ports:
1205
+ - 27017:27017
1206
+ options: >-
1207
+ --health-cmd "mongosh --eval 'db.runCommand({ping: 1})'"
1208
+ --health-interval 10s
1209
+ --health-timeout 5s
1210
+ --health-retries 5`,
1211
+ postgresql: `postgres:
1212
+ image: postgres:15
1213
+ env:
1214
+ POSTGRES_PASSWORD: postgres
1215
+ POSTGRES_DB: test
1216
+ options: >-
1217
+ --health-cmd pg_isready
1218
+ --health-interval 10s
1219
+ --health-timeout 5s
1220
+ --health-retries 5
1221
+ ports:
1222
+ - 5432:5432`,
1223
+ mysql: `mysql:
1224
+ image: mysql:8.0
1225
+ env:
1226
+ MYSQL_ROOT_PASSWORD: password
1227
+ MYSQL_DATABASE: test
1228
+ options: >-
1229
+ --health-cmd "mysqladmin ping"
1230
+ --health-interval 10s
1231
+ --health-timeout 5s
1232
+ --health-retries 5
1233
+ ports:
1234
+ - 3306:3306`
1235
+ };
1236
+
1237
+ return services[database] || '';
1238
+ }
1239
+ }
1240
+
1241
+ module.exports = ProjectGenerator;