scaffy-tool 0.2.0 → 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 (45) hide show
  1. package/README.md +20 -25
  2. package/cli.js +14 -35
  3. package/core/detector.js +5 -31
  4. package/core/executor.js +5 -27
  5. package/core/interviewer.js +4 -11
  6. package/core/plugin-validator.js +1 -1
  7. package/core/registry.js +37 -25
  8. package/core/utils.js +2 -19
  9. package/package.json +11 -11
  10. package/registry/go/gin/plugin.json +23 -0
  11. package/registry/go/gin/v1/questions.js +19 -0
  12. package/registry/go/gin/v1/scaffold.js +196 -0
  13. package/registry/index.json +2 -2
  14. package/registry/javascript/expressjs/plugin.json +44 -0
  15. package/registry/javascript/expressjs/v4/questions.js +43 -0
  16. package/registry/javascript/expressjs/v4/scaffold.js +236 -0
  17. package/registry/javascript/nestjs/plugin.json +2 -2
  18. package/registry/javascript/nestjs/v10/questions.js +4 -2
  19. package/registry/javascript/nestjs/v10/scaffold.js +1 -1
  20. package/registry/javascript/nestjs/v11/questions.js +61 -0
  21. package/registry/javascript/nestjs/v11/scaffold.js +94 -0
  22. package/registry/javascript/nextjs/plugin.json +44 -0
  23. package/registry/javascript/nextjs/v14/questions.js +44 -0
  24. package/registry/javascript/nextjs/v14/scaffold.js +57 -0
  25. package/registry/javascript/vuejs/v3/questions.js +4 -2
  26. package/registry/javascript/vuejs/v3/scaffold.js +3 -10
  27. package/registry/php/laravel/plugin.json +2 -2
  28. package/registry/php/laravel/v11/questions.js +1 -1
  29. package/registry/php/laravel/v11/scaffold.js +1 -1
  30. package/registry/php/laravel/v12/questions.js +45 -0
  31. package/registry/php/laravel/v12/scaffold.js +46 -0
  32. package/registry/php/laravel/v13/questions.js +28 -0
  33. package/registry/php/laravel/v13/scaffold.js +46 -0
  34. package/registry/php/laravel/v13/test/.gitkeep +0 -0
  35. package/registry/php/laravel/v13/test/questions.test.js +48 -0
  36. package/registry/php/laravel/v13/test/scaffold.test.js +105 -0
  37. package/registry/php/symfony/plugin.json +35 -0
  38. package/registry/php/symfony/v7/questions.js +32 -0
  39. package/registry/php/symfony/v7/scaffold.js +86 -0
  40. package/registry/python/django/plugin.json +35 -0
  41. package/registry/python/django/v5/questions.js +24 -0
  42. package/registry/python/django/v5/scaffold.js +107 -0
  43. package/registry/python/fastapi/plugin.json +35 -0
  44. package/registry/python/fastapi/v1/questions.js +25 -0
  45. package/registry/python/fastapi/v1/scaffold.js +180 -0
@@ -0,0 +1,105 @@
1
+ import { describe, it, expect, jest, beforeEach } from '@jest/globals';
2
+
3
+ describe('Laravel v13 scaffold', () => {
4
+ let scaffold;
5
+ let mockUtils;
6
+
7
+ beforeEach(async () => {
8
+ const mod = await import('../scaffold.js');
9
+ scaffold = mod.default;
10
+
11
+ mockUtils = {
12
+ run: jest.fn().mockResolvedValue(),
13
+ runInProject: jest.fn().mockResolvedValue(),
14
+ setEnv: jest.fn(),
15
+ createFile: jest.fn(),
16
+ appendToFile: jest.fn(),
17
+ log: jest.fn(),
18
+ success: jest.fn(),
19
+ warn: jest.fn(),
20
+ error: jest.fn(),
21
+ title: jest.fn(),
22
+ step: jest.fn(),
23
+ divider: jest.fn(),
24
+ };
25
+ });
26
+
27
+ it('runs the laravel v13 installer', async () => {
28
+ await scaffold(
29
+ {
30
+ projectName: 'my-app',
31
+ starterKit: 'none',
32
+ database: 'sqlite',
33
+ docker: false,
34
+ },
35
+ mockUtils
36
+ );
37
+ expect(mockUtils.run).toHaveBeenCalledWith(
38
+ expect.stringContaining('laravel/laravel:^13.0 my-app')
39
+ );
40
+ });
41
+
42
+ it('installs breeze when selected', async () => {
43
+ await scaffold(
44
+ {
45
+ projectName: 'my-app',
46
+ starterKit: 'breeze',
47
+ database: 'sqlite',
48
+ docker: false,
49
+ },
50
+ mockUtils
51
+ );
52
+ expect(mockUtils.runInProject).toHaveBeenCalledWith(
53
+ 'my-app',
54
+ expect.stringContaining('breeze')
55
+ );
56
+ });
57
+
58
+ it('installs jetstream when selected', async () => {
59
+ await scaffold(
60
+ {
61
+ projectName: 'my-app',
62
+ starterKit: 'jetstream',
63
+ database: 'sqlite',
64
+ docker: false,
65
+ },
66
+ mockUtils
67
+ );
68
+ expect(mockUtils.runInProject).toHaveBeenCalledWith(
69
+ 'my-app',
70
+ expect.stringContaining('jetstream')
71
+ );
72
+ });
73
+
74
+ it('sets database env vars', async () => {
75
+ await scaffold(
76
+ {
77
+ projectName: 'my-app',
78
+ starterKit: 'none',
79
+ database: 'mysql',
80
+ docker: false,
81
+ },
82
+ mockUtils
83
+ );
84
+ expect(mockUtils.setEnv).toHaveBeenCalledWith(
85
+ 'my-app',
86
+ expect.objectContaining({ DB_CONNECTION: 'mysql' })
87
+ );
88
+ });
89
+
90
+ it('installs sail when docker is true', async () => {
91
+ await scaffold(
92
+ {
93
+ projectName: 'my-app',
94
+ starterKit: 'none',
95
+ database: 'sqlite',
96
+ docker: true,
97
+ },
98
+ mockUtils
99
+ );
100
+ expect(mockUtils.runInProject).toHaveBeenCalledWith(
101
+ 'my-app',
102
+ expect.stringContaining('sail')
103
+ );
104
+ });
105
+ });
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "Symfony",
3
+ "alias": ["symfony"],
4
+ "language": "php",
5
+ "latest": "v7",
6
+ "versions": ["v7"],
7
+ "description": "High-performance PHP framework for web applications and APIs",
8
+ "requires": [
9
+ {
10
+ "tool": "php",
11
+ "checkCommand": "php --version",
12
+ "parseVersion": "PHP ([0-9]+\\.[0-9]+\\.[0-9]+)",
13
+ "minVersion": "8.2.0",
14
+ "installGuide": {
15
+ "mac": "brew install php",
16
+ "linux": "sudo apt install php8.2",
17
+ "windows": "https://windows.php.net",
18
+ "docs": "https://php.net"
19
+ }
20
+ },
21
+ {
22
+ "tool": "composer",
23
+ "checkCommand": "composer --version",
24
+ "parseVersion": "Composer version ([0-9]+\\.[0-9]+\\.[0-9]+)",
25
+ "minVersion": "2.0.0",
26
+ "installGuide": {
27
+ "mac": "brew install composer",
28
+ "linux": "sudo apt install composer",
29
+ "windows": "https://getcomposer.org/download",
30
+ "docs": "https://getcomposer.org"
31
+ }
32
+ }
33
+ ],
34
+ "maintainer": "community"
35
+ }
@@ -0,0 +1,32 @@
1
+ export default [
2
+ {
3
+ type: 'list',
4
+ name: 'projectType',
5
+ message: 'Project type?',
6
+ choices: [
7
+ { name: 'Web App — full stack with Twig, forms, ORM', value: 'webapp' },
8
+ { name: 'API — REST API with Symfony skeleton', value: 'api' },
9
+ {
10
+ name: 'Microservice — minimal Symfony skeleton',
11
+ value: 'microservice',
12
+ },
13
+ ],
14
+ },
15
+ {
16
+ type: 'list',
17
+ name: 'database',
18
+ message: 'Database?',
19
+ choices: [
20
+ { name: 'MySQL', value: 'mysql' },
21
+ { name: 'PostgreSQL', value: 'postgresql' },
22
+ { name: 'SQLite', value: 'sqlite' },
23
+ { name: 'None', value: 'none' },
24
+ ],
25
+ },
26
+ {
27
+ type: 'confirm',
28
+ name: 'docker',
29
+ message: 'Include Docker config?',
30
+ default: false,
31
+ },
32
+ ];
@@ -0,0 +1,86 @@
1
+ export default async (answers, utils) => {
2
+ const { projectName, projectType, database, docker } = answers;
3
+
4
+ utils.title('Creating Symfony v7 Project');
5
+
6
+ // ─── Step 1: Install ───────────────────────────────────
7
+ utils.step(1, 'Running Symfony installer');
8
+
9
+ if (projectType === 'webapp') {
10
+ await utils.run(
11
+ `composer create-project symfony/website-skeleton:"7.*" ${projectName}`
12
+ );
13
+ } else {
14
+ await utils.run(
15
+ `composer create-project symfony/skeleton:"7.*" ${projectName}`
16
+ );
17
+ }
18
+
19
+ // ─── Step 2: API Platform ──────────────────────────────
20
+ if (projectType === 'api') {
21
+ utils.step(2, 'Installing API Platform');
22
+ await utils.runInProject(projectName, 'composer require api-platform/core');
23
+ }
24
+
25
+ // ─── Step 3: Database ──────────────────────────────────
26
+ if (database !== 'none') {
27
+ utils.step(3, 'Configuring database');
28
+ await utils.runInProject(projectName, 'composer require symfony/orm-pack');
29
+ await utils.runInProject(
30
+ projectName,
31
+ 'composer require --dev symfony/maker-bundle'
32
+ );
33
+
34
+ const dsnMap = {
35
+ mysql: `mysql://root:password@127.0.0.1:3306/${projectName}?serverVersion=8.0`,
36
+ postgresql: `postgresql://root:password@127.0.0.1:5432/${projectName}?serverVersion=15`,
37
+ sqlite: `sqlite:///%kernel.project_dir%/var/data.db`,
38
+ };
39
+
40
+ await utils.setEnv(projectName, {
41
+ DATABASE_URL: dsnMap[database],
42
+ });
43
+ }
44
+
45
+ // ─── Step 4: Docker ────────────────────────────────────
46
+ if (docker) {
47
+ utils.step(4, 'Creating Docker config');
48
+
49
+ const dockerCompose = `version: '3.8'
50
+ services:
51
+ php:
52
+ image: php:8.2-fpm
53
+ volumes:
54
+ - .:/var/www/html
55
+ ports:
56
+ - "9000:9000"
57
+ ${
58
+ database === 'mysql'
59
+ ? ` db:
60
+ image: mysql:8.0
61
+ environment:
62
+ MYSQL_ROOT_PASSWORD: password
63
+ MYSQL_DATABASE: ${projectName}
64
+ ports:
65
+ - "3306:3306"`
66
+ : ''
67
+ }
68
+ ${
69
+ database === 'postgresql'
70
+ ? ` db:
71
+ image: postgres:15
72
+ environment:
73
+ POSTGRES_PASSWORD: password
74
+ POSTGRES_DB: ${projectName}
75
+ ports:
76
+ - "5432:5432"`
77
+ : ''
78
+ }
79
+ `;
80
+ await utils.createFile(`${projectName}/docker-compose.yml`, dockerCompose);
81
+ }
82
+
83
+ utils.success(`Symfony v7 project "${projectName}" created successfully!`);
84
+ utils.log(` cd ${projectName}`);
85
+ utils.log(` symfony server:start`);
86
+ };
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "Django",
3
+ "alias": ["django"],
4
+ "language": "python",
5
+ "latest": "v5",
6
+ "versions": ["v5"],
7
+ "description": "High-level Python web framework for rapid development",
8
+ "requires": [
9
+ {
10
+ "tool": "python",
11
+ "checkCommand": "python3 --version",
12
+ "parseVersion": "Python ([0-9]+\\.[0-9]+\\.[0-9]+)",
13
+ "minVersion": "3.10.0",
14
+ "installGuide": {
15
+ "mac": "brew install python",
16
+ "linux": "sudo apt install python3",
17
+ "windows": "https://python.org/downloads",
18
+ "docs": "https://python.org"
19
+ }
20
+ },
21
+ {
22
+ "tool": "pip",
23
+ "checkCommand": "pip3 --version",
24
+ "parseVersion": "pip ([0-9]+\\.[0-9]+)",
25
+ "minVersion": "22.0.0",
26
+ "installGuide": {
27
+ "mac": "brew install python",
28
+ "linux": "sudo apt install python3-pip",
29
+ "windows": "https://pip.pypa.io/en/stable/installation",
30
+ "docs": "https://pip.pypa.io"
31
+ }
32
+ }
33
+ ],
34
+ "maintainer": "community"
35
+ }
@@ -0,0 +1,24 @@
1
+ export default [
2
+ {
3
+ type: 'list',
4
+ name: 'database',
5
+ message: 'Database?',
6
+ choices: [
7
+ { name: 'SQLite — default, no setup needed', value: 'sqlite' },
8
+ { name: 'PostgreSQL', value: 'postgresql' },
9
+ { name: 'MySQL', value: 'mysql' },
10
+ ],
11
+ },
12
+ {
13
+ type: 'confirm',
14
+ name: 'restFramework',
15
+ message: 'Include Django REST Framework?',
16
+ default: false,
17
+ },
18
+ {
19
+ type: 'confirm',
20
+ name: 'docker',
21
+ message: 'Include Docker config?',
22
+ default: false,
23
+ },
24
+ ];
@@ -0,0 +1,107 @@
1
+ export default async (answers, utils) => {
2
+ const { projectName, database, restFramework, docker } = answers;
3
+
4
+ utils.title('Creating Django v5 Project');
5
+
6
+ // ─── Step 1: Install Django ────────────────────────────
7
+ utils.step(1, 'Installing Django');
8
+ await utils.run('pip3 install "django>=5.0,<6.0"');
9
+
10
+ // ─── Step 2: Create Project ────────────────────────────
11
+ utils.step(2, 'Creating Django project');
12
+ await utils.run(`django-admin startproject ${projectName}`);
13
+
14
+ // ─── Step 3: Database ──────────────────────────────────
15
+ if (database === 'postgresql') {
16
+ utils.step(3, 'Installing PostgreSQL driver');
17
+ await utils.runInProject(projectName, 'pip3 install psycopg2-binary');
18
+ }
19
+
20
+ if (database === 'mysql') {
21
+ utils.step(3, 'Installing MySQL driver');
22
+ await utils.runInProject(projectName, 'pip3 install mysqlclient');
23
+ }
24
+
25
+ // ─── Step 4: REST Framework ────────────────────────────
26
+ if (restFramework) {
27
+ utils.step(4, 'Installing Django REST Framework');
28
+ await utils.runInProject(projectName, 'pip3 install djangorestframework');
29
+
30
+ const settingsPath = `${projectName}/${projectName}/settings.py`;
31
+ await utils.appendToFile(
32
+ settingsPath,
33
+ `\n# Django REST Framework\nINSTALLED_APPS += ['rest_framework']\n`
34
+ );
35
+ }
36
+
37
+ // ─── Step 5: requirements.txt ──────────────────────────
38
+ utils.step(5, 'Generating requirements.txt');
39
+
40
+ const requirements = [`django>=5.0,<6.0`];
41
+ if (database === 'postgresql') requirements.push('psycopg2-binary');
42
+ if (database === 'mysql') requirements.push('mysqlclient');
43
+ if (restFramework) requirements.push('djangorestframework');
44
+
45
+ await utils.createFile(
46
+ `${projectName}/requirements.txt`,
47
+ requirements.join('\n') + '\n'
48
+ );
49
+
50
+ // ─── Step 6: Docker ────────────────────────────────────
51
+ if (docker) {
52
+ utils.step(6, 'Creating Docker config');
53
+
54
+ const dockerfile = `FROM python:3.12-slim
55
+
56
+ WORKDIR /app
57
+
58
+ COPY requirements.txt .
59
+ RUN pip install --no-cache-dir -r requirements.txt
60
+
61
+ COPY . .
62
+
63
+ EXPOSE 8000
64
+ CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]
65
+ `;
66
+
67
+ const dockerCompose = `version: '3.8'
68
+ services:
69
+ web:
70
+ build: .
71
+ ports:
72
+ - "8000:8000"
73
+ volumes:
74
+ - .:/app
75
+ ${
76
+ database === 'postgresql'
77
+ ? ` db:
78
+ image: postgres:15
79
+ environment:
80
+ POSTGRES_PASSWORD: password
81
+ POSTGRES_DB: ${projectName}
82
+ ports:
83
+ - "5432:5432"`
84
+ : ''
85
+ }
86
+ ${
87
+ database === 'mysql'
88
+ ? ` db:
89
+ image: mysql:8.0
90
+ environment:
91
+ MYSQL_ROOT_PASSWORD: password
92
+ MYSQL_DATABASE: ${projectName}
93
+ ports:
94
+ - "3306:3306"`
95
+ : ''
96
+ }
97
+ `;
98
+
99
+ await utils.createFile(`${projectName}/Dockerfile`, dockerfile);
100
+ await utils.createFile(`${projectName}/docker-compose.yml`, dockerCompose);
101
+ }
102
+
103
+ utils.success(`Django v5 project "${projectName}" created successfully!`);
104
+ utils.log(` cd ${projectName}`);
105
+ utils.log(` python manage.py migrate`);
106
+ utils.log(` python manage.py runserver`);
107
+ };
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "FastAPI",
3
+ "alias": ["fastapi"],
4
+ "language": "python",
5
+ "latest": "v1",
6
+ "versions": ["v1"],
7
+ "description": "Modern, fast Python web framework for building APIs",
8
+ "requires": [
9
+ {
10
+ "tool": "python",
11
+ "checkCommand": "python3 --version",
12
+ "parseVersion": "Python ([0-9]+\\.[0-9]+\\.[0-9]+)",
13
+ "minVersion": "3.10.0",
14
+ "installGuide": {
15
+ "mac": "brew install python",
16
+ "linux": "sudo apt install python3",
17
+ "windows": "https://python.org/downloads",
18
+ "docs": "https://python.org"
19
+ }
20
+ },
21
+ {
22
+ "tool": "pip",
23
+ "checkCommand": "pip3 --version",
24
+ "parseVersion": "pip ([0-9]+\\.[0-9]+)",
25
+ "minVersion": "22.0.0",
26
+ "installGuide": {
27
+ "mac": "brew install python",
28
+ "linux": "sudo apt install python3-pip",
29
+ "windows": "https://pip.pypa.io/en/stable/installation",
30
+ "docs": "https://pip.pypa.io"
31
+ }
32
+ }
33
+ ],
34
+ "maintainer": "community"
35
+ }
@@ -0,0 +1,25 @@
1
+ export default [
2
+ {
3
+ type: 'list',
4
+ name: 'database',
5
+ message: 'Database?',
6
+ choices: [
7
+ { name: 'None', value: 'none' },
8
+ { name: 'PostgreSQL — SQLAlchemy + Alembic', value: 'postgresql' },
9
+ { name: 'MySQL — SQLAlchemy + Alembic', value: 'mysql' },
10
+ { name: 'SQLite — SQLAlchemy + Alembic', value: 'sqlite' },
11
+ ],
12
+ },
13
+ {
14
+ type: 'confirm',
15
+ name: 'async',
16
+ message: 'Use async database drivers?',
17
+ default: true,
18
+ },
19
+ {
20
+ type: 'confirm',
21
+ name: 'docker',
22
+ message: 'Include Docker config?',
23
+ default: false,
24
+ },
25
+ ];
@@ -0,0 +1,180 @@
1
+ export default async (answers, utils) => {
2
+ const { projectName, database, async: useAsync, docker } = answers;
3
+
4
+ utils.title('Creating FastAPI Project');
5
+
6
+ // ─── Step 1: Create project folder ────────────────────
7
+ utils.step(1, 'Creating project structure');
8
+ await utils.run(`mkdir ${projectName}`);
9
+ await utils.run(`mkdir ${projectName}/app`);
10
+
11
+ // ─── Step 2: Create virtual environment ───────────────
12
+ utils.step(2, 'Creating virtual environment');
13
+ await utils.runInProject(projectName, 'python3 -m venv venv');
14
+
15
+ // ─── Step 3: Install FastAPI + Uvicorn ────────────────
16
+ utils.step(3, 'Installing FastAPI and Uvicorn');
17
+ await utils.runInProject(
18
+ projectName,
19
+ 'venv/bin/pip install "fastapi>=0.100.0" "uvicorn[standard]"'
20
+ );
21
+
22
+ // ─── Step 4: Database drivers ─────────────────────────
23
+ if (database !== 'none') {
24
+ utils.step(4, 'Installing database dependencies');
25
+ await utils.runInProject(
26
+ projectName,
27
+ 'venv/bin/pip install sqlalchemy alembic'
28
+ );
29
+
30
+ if (database === 'postgresql') {
31
+ const driver = useAsync ? 'asyncpg' : 'psycopg2-binary';
32
+ await utils.runInProject(projectName, `venv/bin/pip install ${driver}`);
33
+ }
34
+
35
+ if (database === 'mysql') {
36
+ const driver = useAsync ? 'aiomysql' : 'mysqlclient';
37
+ await utils.runInProject(projectName, `venv/bin/pip install ${driver}`);
38
+ }
39
+
40
+ if (database === 'sqlite' && useAsync) {
41
+ await utils.runInProject(projectName, 'venv/bin/pip install aiosqlite');
42
+ }
43
+ }
44
+
45
+ // ─── Step 5: Generate main.py ─────────────────────────
46
+ utils.step(5, 'Generating application files');
47
+
48
+ const mainPy = `from fastapi import FastAPI
49
+
50
+ app = FastAPI(title="${projectName}", version="0.1.0")
51
+
52
+
53
+ @app.get("/")
54
+ ${useAsync ? 'async ' : ''}def root():
55
+ return {"message": "Hello from ${projectName}"}
56
+
57
+
58
+ @app.get("/health")
59
+ ${useAsync ? 'async ' : ''}def health():
60
+ return {"status": "ok"}
61
+ `;
62
+
63
+ await utils.createFile(`${projectName}/app/main.py`, mainPy);
64
+
65
+ // ─── Step 6: Generate database.py if needed ───────────
66
+ if (database !== 'none') {
67
+ const dsnMap = {
68
+ postgresql: useAsync
69
+ ? `postgresql+asyncpg://user:password@localhost/${projectName}`
70
+ : `postgresql+psycopg2://user:password@localhost/${projectName}`,
71
+ mysql: useAsync
72
+ ? `mysql+aiomysql://user:password@localhost/${projectName}`
73
+ : `mysql+mysqlclient://user:password@localhost/${projectName}`,
74
+ sqlite: useAsync
75
+ ? `sqlite+aiosqlite:///./data.db`
76
+ : `sqlite:///./data.db`,
77
+ };
78
+
79
+ const databasePy = `from sqlalchemy${useAsync ? '.ext.asyncio' : ''} import create_engine${useAsync ? ', AsyncSession' : ''}, sessionmaker
80
+ from sqlalchemy.orm import DeclarativeBase
81
+
82
+ DATABASE_URL = "${dsnMap[database]}"
83
+
84
+ ${
85
+ useAsync
86
+ ? `engine = create_engine(DATABASE_URL)
87
+ SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)`
88
+ : `engine = create_engine(DATABASE_URL)
89
+ SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)`
90
+ }
91
+
92
+
93
+ class Base(DeclarativeBase):
94
+ pass
95
+ `;
96
+ await utils.createFile(`${projectName}/app/database.py`, databasePy);
97
+ }
98
+
99
+ // ─── Step 7: Generate __init__.py ─────────────────────
100
+ await utils.createFile(`${projectName}/app/__init__.py`, '');
101
+
102
+ // ─── Step 8: requirements.txt ─────────────────────────
103
+ utils.step(6, 'Generating requirements.txt');
104
+
105
+ const requirements = ['fastapi>=0.100.0', 'uvicorn[standard]'];
106
+
107
+ if (database !== 'none') {
108
+ requirements.push('sqlalchemy');
109
+ requirements.push('alembic');
110
+ if (database === 'postgresql')
111
+ requirements.push(useAsync ? 'asyncpg' : 'psycopg2-binary');
112
+ if (database === 'mysql')
113
+ requirements.push(useAsync ? 'aiomysql' : 'mysqlclient');
114
+ if (database === 'sqlite' && useAsync) requirements.push('aiosqlite');
115
+ }
116
+
117
+ await utils.createFile(
118
+ `${projectName}/requirements.txt`,
119
+ requirements.join('\n') + '\n'
120
+ );
121
+
122
+ // ─── Step 9: Docker ───────────────────────────────────
123
+ if (docker) {
124
+ utils.step(7, 'Creating Docker config');
125
+
126
+ const dockerfile = `FROM python:3.12-slim
127
+
128
+ WORKDIR /app
129
+
130
+ COPY requirements.txt .
131
+ RUN pip install --no-cache-dir -r requirements.txt
132
+
133
+ COPY . .
134
+
135
+ EXPOSE 8000
136
+ CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
137
+ `;
138
+
139
+ const dockerCompose = `version: '3.8'
140
+ services:
141
+ web:
142
+ build: .
143
+ ports:
144
+ - "8000:8000"
145
+ volumes:
146
+ - .:/app
147
+ ${
148
+ database === 'postgresql'
149
+ ? ` db:
150
+ image: postgres:15
151
+ environment:
152
+ POSTGRES_PASSWORD: password
153
+ POSTGRES_DB: ${projectName}
154
+ ports:
155
+ - "5432:5432"`
156
+ : ''
157
+ }
158
+ ${
159
+ database === 'mysql'
160
+ ? ` db:
161
+ image: mysql:8.0
162
+ environment:
163
+ MYSQL_ROOT_PASSWORD: password
164
+ MYSQL_DATABASE: ${projectName}
165
+ ports:
166
+ - "3306:3306"`
167
+ : ''
168
+ }
169
+ `;
170
+
171
+ await utils.createFile(`${projectName}/Dockerfile`, dockerfile);
172
+ await utils.createFile(`${projectName}/docker-compose.yml`, dockerCompose);
173
+ }
174
+
175
+ utils.success(`FastAPI project "${projectName}" created successfully!`);
176
+ utils.log(` cd ${projectName}`);
177
+ utils.log(` source venv/bin/activate`);
178
+ utils.log(` uvicorn app.main:app --reload`);
179
+ utils.log(` Docs: http://localhost:8000/docs`);
180
+ };