@adriankulik/create-fullstack-app 1.3.0 → 1.5.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 (35) hide show
  1. package/README.md +25 -9
  2. package/cli/index.js +191 -76
  3. package/cli/logo.js +8 -8
  4. package/package.json +4 -4
  5. package/templates/backend/dotnet/Program.cs +32 -0
  6. package/templates/backend/dotnet/Properties/launchSettings.json +41 -0
  7. package/templates/backend/dotnet/Tests/GlobalUsings.cs +1 -0
  8. package/templates/backend/dotnet/Tests/Tests.csproj +30 -0
  9. package/templates/backend/dotnet/Tests/UnitTest1.cs +43 -0
  10. package/templates/backend/dotnet/Tests/obj/Tests.csproj.nuget.dgspec.json +147 -0
  11. package/templates/backend/dotnet/Tests/obj/Tests.csproj.nuget.g.props +26 -0
  12. package/templates/backend/dotnet/Tests/obj/Tests.csproj.nuget.g.targets +15 -0
  13. package/templates/backend/dotnet/Tests/obj/project.assets.json +7485 -0
  14. package/templates/backend/dotnet/Tests/obj/project.nuget.cache +132 -0
  15. package/templates/backend/dotnet/appsettings.Development.json +8 -0
  16. package/templates/backend/dotnet/appsettings.json +9 -0
  17. package/templates/backend/dotnet/backend.sln +28 -0
  18. package/templates/backend/dotnet/dotnet.csproj +17 -0
  19. package/templates/backend/dotnet/dotnet.http +6 -0
  20. package/templates/backend/dotnet/lint.sh +2 -0
  21. package/templates/backend/dotnet/start.sh +2 -0
  22. package/templates/backend/dotnet/test.sh +2 -0
  23. package/templates/backend/fastapi/main.py +1 -3
  24. package/templates/backend/fastapi/test_main.py +0 -4
  25. package/templates/backend/flask/main.py +1 -3
  26. package/templates/backend/flask/test_main.py +0 -4
  27. package/templates/backend/nodejs/.eslintrc.json +15 -0
  28. package/templates/backend/nodejs/lint.sh +2 -0
  29. package/templates/backend/nodejs/package-lock.json +5227 -0
  30. package/templates/backend/nodejs/package.json +27 -0
  31. package/templates/backend/nodejs/src/index.test.ts +22 -0
  32. package/templates/backend/nodejs/src/index.ts +23 -0
  33. package/templates/backend/nodejs/start.sh +2 -0
  34. package/templates/backend/nodejs/test.sh +2 -0
  35. package/templates/backend/nodejs/tsconfig.json +14 -0
package/README.md CHANGED
@@ -1,13 +1,18 @@
1
1
  # create-fullstack-app
2
2
 
3
+ [![NPM Downloads](https://img.shields.io/npm/dy/@adriankulik/create-fullstack-app)](https://www.npmjs.com/package/@adriankulik/create-fullstack-app)
4
+
5
+
3
6
  A CLI tool for scaffolding a full-stack web application with your choice of frontend and backend technologies. The generated projects use `npm ci` and pinned Python dependencies to ensure fully deterministic and reproducible installations.
4
7
 
5
8
  ## Prerequisites
6
9
 
7
10
  Before running the CLI or the scaffolded applications, ensure you have the following installed on your system:
11
+
8
12
  - **Node.js**: v20 or newer
9
13
  - **npm**: v10 or newer
10
- - **Python**: v3.11 or newer
14
+ - **Python**: v3.11 or newer _(only required for FastAPI / Flask backends)_
15
+ - **[.NET 9 SDK](https://aka.ms/dotnet/download)** _(only required for the .NET backend — the CLI will offer to install it automatically on macOS/Linux if not found)_
11
16
 
12
17
  ## Quickstart
13
18
 
@@ -17,7 +22,7 @@ To generate a new full-stack application, simply run:
17
22
  npx @adriankulik/create-fullstack-app@latest
18
23
  ```
19
24
 
20
- *(If you are developing locally, run `npm install` in the root directory and execute `node cli/index.js`).*
25
+ _(If you are developing locally, run `npm install` in the root directory and execute `node cli/index.js`)._
21
26
 
22
27
  You will be prompted to choose a project name, your preferred frontend, and backend framework.
23
28
 
@@ -25,16 +30,18 @@ You will be prompted to choose a project name, your preferred frontend, and back
25
30
 
26
31
  - **Frontend Options**:
27
32
  - Next.js (v16.2.4)
28
- - Angular (v21.0.0) - *Configured with Zone.js*
33
+ - Angular (v21.0.0) - _Configured with Zone.js_
29
34
  - Vue (v3.5.33)
30
35
  - Svelte (v5.55.5)
31
- - **Backend Options**:
36
+ - **Backend Options**:
32
37
  - FastAPI (v0.136.1)
33
38
  - Flask (v3.1.3)
39
+ - .NET (v9.0.16)
40
+ - Node.js Express (v4.22.2)
34
41
  - **Opinionated Defaults**: We bake in sensible, predefined choices (e.g., Angular uses `zone.js`, testing is unified under Playwright and framework-native test runners).
35
42
  - **Unified Scripts**: `start.sh`, `test.sh`, and `lint.sh` available out of the box to manage both frontend and backend seamlessly.
36
- - **CI/CD Ready**: Includes a pre-configured `.github/workflows/cli-e2e.yml` that tests both ends. *Tip: To enforce this, enable branch protection in your GitHub repository settings and require the "test" status check to pass.*
37
- - **Developer Experience**: Includes `.vscode/extensions.json` recommending the necessary linters and formatters, and an `AGENTS.md` file providing behavioral guidelines for AI agents (based on [andrej-karpathy-skills](https://github.com/forrestchang/andrej-karpathy-skills)). It is AI development friendly, including an extensive set of agent skills in `.agents/skills` sourced from [claude-skills](https://github.com/alirezarezvani/claude-skills).
43
+ - **CI/CD Ready**: Includes a pre-configured `.github/workflows/cli-e2e.yml` that tests both ends. _Tip: To enforce this, enable branch protection in your GitHub repository settings and require the "test" status check to pass._
44
+ - **Developer Experience**: Includes `.vscode/extensions.json` recommending the necessary linters and formatters, and an `AGENTS.md` file providing behavioral guidelines for AI agents. It is AI development friendly, including an extensive set of agent skills in `.agents/skills`.
38
45
  <details>
39
46
  <summary>Included Agent Skills</summary>
40
47
  <p>senior-frontend, senior-qa, senior-fullstack, accessibility-general</p>
@@ -60,7 +67,7 @@ my-app/
60
67
  └── lint.sh
61
68
  ```
62
69
 
63
- *Note: Depending on your environment, you may need to ensure the generated shell scripts are executable by running `chmod +x *.sh` inside your new project directory.*
70
+ \_Note: Depending on your environment, you may need to ensure the generated shell scripts are executable by running `chmod +x *.sh` inside your new project directory.\
64
71
 
65
72
  ## Local E2E Testing
66
73
 
@@ -85,11 +92,12 @@ Then run it:
85
92
  Future plans for this CLI include:
86
93
 
87
94
  - Adding frontend framework-specific component libraries during the scaffold step.
88
- - Tackling **.NET** as a new backend option.
95
+ - **Spring Boot** backend support (Java, enterprise-ready framework).
96
+ - **Rust (Rocket)** backend support (web framework for Rust with focus on ease of use).
89
97
 
90
98
  ## Acknowledgements
91
99
 
92
- This project redistributes boilerplate code and configuration files generated by the official CLIs and templates of the integrated frameworks. We are grateful to the maintainers of these projects:
100
+ This project redistributes boilerplate code and configuration files generated by the official CLIs and templates of the integrated frameworks. I'm grateful to the maintainers of these projects:
93
101
 
94
102
  - [Next.js](https://nextjs.org/) (MIT License)
95
103
  - [Angular](https://angular.dev/) (MIT License)
@@ -97,3 +105,11 @@ This project redistributes boilerplate code and configuration files generated by
97
105
  - [Svelte](https://svelte.dev/) (MIT License)
98
106
  - [FastAPI](https://fastapi.tiangolo.com/) (MIT License)
99
107
  - [Flask](https://flask.palletsprojects.com/) (BSD 3-Clause License)
108
+ - [.NET](https://dotnet.microsoft.com/) (MIT License)
109
+ - [Express](https://expressjs.com/) (MIT License)
110
+
111
+ ## References
112
+
113
+ - [Shields.io](https://shields.io/badges)
114
+ - [andrej-karpathy-skills](https://github.com/forrestchang/andrej-karpathy-skills)
115
+ - [claude-skills](https://github.com/alirezarezvani/claude-skills)
package/cli/index.js CHANGED
@@ -1,63 +1,95 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const prompts = require('prompts');
4
- const pc = require('picocolors');
5
- const fs = require('fs-extra');
6
- const path = require('path');
7
- const { execSync } = require('child_process');
3
+ const prompts = require("prompts");
4
+ const pc = require("picocolors");
5
+ const fs = require("fs-extra");
6
+ const path = require("path");
7
+ const os = require("os");
8
+ const { execSync } = require("child_process");
8
9
 
9
10
  async function main() {
10
- const { asciiArt } = require('./logo');
11
+ const { asciiArt } = require("./logo");
11
12
  console.log(pc.cyan(asciiArt));
12
- console.log(pc.cyan('\nWelcome to create-fullstack-app!\n'));
13
+ console.log(pc.cyan("\nWelcome to create-fullstack-app!\n"));
13
14
 
14
15
  // Simple argument parsing
15
16
  const args = process.argv.slice(2);
16
- let argProjectName = args[0] && !args[0].startsWith('--') ? args[0] : null;
17
+ let argProjectName = args[0] && !args[0].startsWith("--") ? args[0] : null;
17
18
  let argFrontend = null;
18
19
  let argBackend = null;
19
20
 
20
21
  for (let i = 0; i < args.length; i++) {
21
- if (args[i] === '--frontend' && args[i + 1]) argFrontend = args[i + 1];
22
- if (args[i] === '--backend' && args[i + 1]) argBackend = args[i + 1];
22
+ if (args[i] === "--frontend" && args[i + 1]) argFrontend = args[i + 1];
23
+ if (args[i] === "--backend" && args[i + 1]) argBackend = args[i + 1];
23
24
  }
24
25
 
25
26
  const questions = [];
26
27
  if (!argProjectName) {
27
28
  questions.push({
28
- type: 'text',
29
- name: 'projectName',
30
- message: 'What is your project named?',
31
- initial: 'my-fullstack-app',
32
- validate: value => value.trim().length > 0 ? true : 'Project name cannot be empty'
29
+ type: "text",
30
+ name: "projectName",
31
+ message: "What is your project named?",
32
+ initial: "my-fullstack-app",
33
+ validate: (value) =>
34
+ value.trim().length > 0 ? true : "Project name cannot be empty",
33
35
  });
34
36
  }
35
37
 
36
38
  if (!argFrontend) {
37
39
  questions.push({
38
- type: 'select',
39
- name: 'frontend',
40
- message: 'Which frontend framework would you like to use?',
40
+ type: "select",
41
+ name: "frontend",
42
+ message: "Which frontend framework would you like to use?",
41
43
  choices: [
42
- { title: 'Next.js', value: 'nextjs', description: 'React framework' },
43
- { title: 'Angular', value: 'angular', description: 'Enterprise-grade platform' },
44
- { title: 'Vue', value: 'vue', description: 'Progressive JavaScript framework' },
45
- { title: 'Svelte', value: 'svelte', description: 'Cybernetically enhanced web apps' }
44
+ { title: "Next.js", value: "nextjs", description: "React framework" },
45
+ {
46
+ title: "Angular",
47
+ value: "angular",
48
+ description: "Enterprise-grade platform",
49
+ },
50
+ {
51
+ title: "Vue",
52
+ value: "vue",
53
+ description: "Progressive JavaScript framework",
54
+ },
55
+ {
56
+ title: "Svelte",
57
+ value: "svelte",
58
+ description: "Cybernetically enhanced web apps",
59
+ },
46
60
  ],
47
- initial: 0
61
+ initial: 0,
48
62
  });
49
63
  }
50
64
 
51
65
  if (!argBackend) {
52
66
  questions.push({
53
- type: 'select',
54
- name: 'backend',
55
- message: 'Which backend framework would you like to use?',
67
+ type: "select",
68
+ name: "backend",
69
+ message: "Which backend framework would you like to use?",
56
70
  choices: [
57
- { title: 'FastAPI', value: 'fastapi', description: 'Modern, fast Python web framework' },
58
- { title: 'Flask', value: 'flask', description: 'Lightweight WSGI web application framework' }
71
+ {
72
+ title: "FastAPI",
73
+ value: "fastapi",
74
+ description: "Modern, fast Python web framework",
75
+ },
76
+ {
77
+ title: "Flask",
78
+ value: "flask",
79
+ description: "Lightweight Python WSGI web application framework",
80
+ },
81
+ {
82
+ title: ".NET",
83
+ value: "dotnet",
84
+ description: "Robust, C#, high-performance framework by Microsoft",
85
+ },
86
+ {
87
+ title: "Node.js (Express)",
88
+ value: "nodejs",
89
+ description: "Minimalist web framework for Node.js",
90
+ },
59
91
  ],
60
- initial: 0
92
+ initial: 0,
61
93
  });
62
94
  }
63
95
 
@@ -68,7 +100,7 @@ async function main() {
68
100
  const backend = argBackend || response.backend;
69
101
 
70
102
  if (!projectName || !frontend || !backend) {
71
- console.log(pc.red('Setup cancelled.'));
103
+ console.log(pc.red("Setup cancelled."));
72
104
  process.exit(1);
73
105
  }
74
106
  const targetDir = path.resolve(process.cwd(), projectName);
@@ -81,28 +113,31 @@ async function main() {
81
113
  console.log(pc.blue(`\nScaffolding project in ${targetDir}...`));
82
114
 
83
115
  // Determine paths to templates relative to the CLI script
84
- const templatesDir = path.resolve(__dirname, '../templates');
85
- const baseDir = path.join(templatesDir, 'base');
86
- const frontendDir = path.join(templatesDir, 'frontend', frontend);
87
- const backendDir = path.join(templatesDir, 'backend', backend);
116
+ const templatesDir = path.resolve(__dirname, "../templates");
117
+ const baseDir = path.join(templatesDir, "base");
118
+ const frontendDir = path.join(templatesDir, "frontend", frontend);
119
+ const backendDir = path.join(templatesDir, "backend", backend);
88
120
 
89
121
  try {
90
122
  // 1. Copy base template
91
123
  await fs.copy(baseDir, targetDir);
92
- if (fs.existsSync(path.join(targetDir, 'gitignore'))) {
93
- await fs.rename(path.join(targetDir, 'gitignore'), path.join(targetDir, '.gitignore'));
124
+ if (fs.existsSync(path.join(targetDir, "gitignore"))) {
125
+ await fs.rename(
126
+ path.join(targetDir, "gitignore"),
127
+ path.join(targetDir, ".gitignore"),
128
+ );
94
129
  }
95
130
 
96
131
  // 2. Copy frontend
97
- const targetFrontend = path.join(targetDir, 'frontend');
132
+ const targetFrontend = path.join(targetDir, "frontend");
98
133
  await fs.copy(frontendDir, targetFrontend);
99
134
 
100
135
  // 3. Copy backend
101
- const targetBackend = path.join(targetDir, 'backend');
136
+ const targetBackend = path.join(targetDir, "backend");
102
137
  await fs.copy(backendDir, targetBackend);
103
138
 
104
139
  // 4. Update package.json name for the frontend
105
- const pkgPath = path.join(targetFrontend, 'package.json');
140
+ const pkgPath = path.join(targetFrontend, "package.json");
106
141
  if (fs.existsSync(pkgPath)) {
107
142
  const pkg = await fs.readJson(pkgPath);
108
143
  pkg.name = `${projectName}-frontend`;
@@ -112,11 +147,11 @@ async function main() {
112
147
  // 5. Make all shell scripts executable
113
148
  const makeExecutable = (dir) => {
114
149
  const files = fs.readdirSync(dir);
115
- files.forEach(file => {
150
+ files.forEach((file) => {
116
151
  const filePath = path.join(dir, file);
117
152
  if (fs.statSync(filePath).isDirectory()) {
118
153
  makeExecutable(filePath);
119
- } else if (file.endsWith('.sh')) {
154
+ } else if (file.endsWith(".sh")) {
120
155
  fs.chmodSync(filePath, 0o755);
121
156
  }
122
157
  });
@@ -124,58 +159,138 @@ async function main() {
124
159
  makeExecutable(targetDir);
125
160
 
126
161
  // 6. Install dependencies
127
- console.log(pc.blue('\nInstalling dependencies (this may take a minute)...'));
162
+ console.log(
163
+ pc.blue("\nInstalling dependencies (this may take a minute)..."),
164
+ );
128
165
 
129
166
  // Frontend installation
130
- console.log(pc.cyan(' Installing frontend dependencies...'));
131
- execSync('npm ci', { cwd: targetFrontend, stdio: 'inherit' });
167
+ console.log(pc.cyan(" Installing frontend dependencies..."));
168
+ execSync("npm ci", { cwd: targetFrontend, stdio: "inherit" });
132
169
 
133
- // Backend installation
134
- console.log(pc.cyan(' Setting up backend virtual environment...'));
135
- const pythonCmd = process.platform === 'win32' ? 'python' : 'python3';
136
- try {
137
- execSync(`${pythonCmd} -m venv venv`, { cwd: targetBackend, stdio: 'inherit' });
138
-
139
- const pipPath = process.platform === 'win32'
140
- ? path.join('venv', 'Scripts', 'pip')
141
- : path.join('venv', 'bin', 'pip');
142
-
143
- console.log(pc.cyan(' Installing backend dependencies...'));
144
- execSync(`${pipPath} install -r requirements.txt`, { cwd: targetBackend, stdio: 'inherit' });
145
- } catch (e) {
146
- console.log(pc.yellow(' Could not set up Python virtual environment automatically. Please set it up manually.'));
170
+ if (backend === "fastapi" || backend === "flask") {
171
+ console.log(pc.cyan(" Setting up backend virtual environment..."));
172
+ const pythonCmd = process.platform === "win32" ? "python" : "python3";
173
+ try {
174
+ execSync(`${pythonCmd} -m venv venv`, {
175
+ cwd: targetBackend,
176
+ stdio: "inherit",
177
+ });
178
+
179
+ const pipPath =
180
+ process.platform === "win32"
181
+ ? path.join("venv", "Scripts", "pip")
182
+ : path.join("venv", "bin", "pip");
183
+
184
+ console.log(pc.cyan(" Installing backend dependencies..."));
185
+ execSync(`${pipPath} install -r requirements.txt`, {
186
+ cwd: targetBackend,
187
+ stdio: "inherit",
188
+ });
189
+ } catch (e) {
190
+ console.log(
191
+ pc.yellow(
192
+ " Could not set up Python virtual environment automatically. Please set it up manually.",
193
+ ),
194
+ );
195
+ }
196
+ } else if (backend === "dotnet") {
197
+ // Check if .NET 9+ SDK is available
198
+ let dotnetMajor = 0;
199
+ try {
200
+ const version = execSync("dotnet --version", { stdio: "pipe" }).toString().trim();
201
+ dotnetMajor = parseInt(version.split(".")[0], 10);
202
+ } catch {
203
+ dotnetMajor = 0;
204
+ }
205
+
206
+ if (dotnetMajor < 9) {
207
+ console.log(pc.yellow(`\n .NET 9 SDK is required but ${dotnetMajor > 0 ? `v${dotnetMajor} was found` : "dotnet was not found"} on your system.`));
208
+
209
+ if (process.platform === "win32") {
210
+ console.log(pc.yellow(" Please install the .NET 9 SDK from: https://aka.ms/dotnet/download"));
211
+ } else {
212
+ const { installDotnet } = await prompts({
213
+ type: "confirm",
214
+ name: "installDotnet",
215
+ message: "Would you like to install the .NET 9 SDK automatically? (uses the official Microsoft install script)",
216
+ initial: true,
217
+ });
218
+
219
+ if (installDotnet) {
220
+ console.log(pc.cyan(" Installing .NET 9 SDK (this may take a few minutes)..."));
221
+ try {
222
+ execSync(
223
+ "curl -sSL https://dot.net/v1/dotnet-install.sh | bash -s -- --channel 9.0",
224
+ { stdio: "inherit" }
225
+ );
226
+ // Make the newly installed dotnet available in the current process without a shell restart
227
+ const dotnetRoot = path.join(os.homedir(), ".dotnet");
228
+ process.env.DOTNET_ROOT = dotnetRoot;
229
+ process.env.PATH = `${dotnetRoot}:${process.env.PATH}`;
230
+ console.log(pc.green(" .NET 9 SDK installed successfully."));
231
+ console.log(pc.yellow(` Add ${dotnetRoot} to your PATH permanently by updating your shell profile (e.g. ~/.zshrc).`));
232
+ } catch (e) {
233
+ console.log(pc.yellow(" Could not install .NET SDK automatically. Please install it from https://aka.ms/dotnet/download"));
234
+ }
235
+ } else {
236
+ console.log(pc.yellow(" Skipping .NET SDK installation. Please install .NET 9 SDK from https://aka.ms/dotnet/download"));
237
+ }
238
+ }
239
+ }
240
+
241
+ console.log(pc.cyan(" Restoring .NET dependencies..."));
242
+ try {
243
+ execSync("dotnet restore backend.sln", { cwd: targetBackend, stdio: "inherit" });
244
+ } catch (e) {
245
+ console.log(
246
+ pc.yellow(
247
+ " Could not restore .NET dependencies automatically. Please make sure .NET 9 SDK is installed and in your PATH.",
248
+ ),
249
+ );
250
+ }
251
+ } else if (backend === "nodejs") {
252
+ console.log(pc.cyan(" Installing Node.js backend dependencies..."));
253
+ execSync("npm ci", { cwd: targetBackend, stdio: "inherit" });
147
254
  }
148
255
 
149
256
  // 7. Initialize Git repository
150
- console.log(pc.blue('\nInitializing Git repository...'));
257
+ console.log(pc.blue("\nInitializing Git repository..."));
151
258
  try {
152
- execSync('git init', { cwd: targetDir, stdio: 'ignore' });
153
- execSync('git add .', { cwd: targetDir, stdio: 'ignore' });
154
- execSync('git commit -m "Initial commit from create-fullstack-app"', { cwd: targetDir, stdio: 'ignore' });
155
- console.log(pc.cyan(' Git repository initialized.'));
259
+ execSync("git init", { cwd: targetDir, stdio: "ignore" });
260
+ execSync("git add .", { cwd: targetDir, stdio: "ignore" });
261
+ execSync('git commit -m "Initial commit from create-fullstack-app"', {
262
+ cwd: targetDir,
263
+ stdio: "ignore",
264
+ });
265
+ console.log(pc.cyan(" Git repository initialized."));
156
266
  } catch (e) {
157
- console.log(pc.yellow(' Could not initialize Git repository automatically. You may need to run git init yourself.'));
267
+ console.log(
268
+ pc.yellow(
269
+ " Could not initialize Git repository automatically. You may need to run git init yourself.",
270
+ ),
271
+ );
158
272
  }
159
273
 
160
274
  console.log(pc.green(`\nSuccess! Created ${projectName} at ${targetDir}`));
161
- console.log('\nInside that directory, you can run several commands:\n');
275
+ console.log("\nInside that directory, you can run several commands:\n");
162
276
 
163
- console.log(` ${pc.cyan('./start.sh')}`);
164
- console.log(' Starts both the backend and frontend development servers.\n');
277
+ console.log(` ${pc.cyan("./start.sh")}`);
278
+ console.log(
279
+ " Starts both the backend and frontend development servers.\n",
280
+ );
165
281
 
166
- console.log(` ${pc.cyan('./test.sh')}`);
167
- console.log(' Runs tests for both frontend and backend.\n');
282
+ console.log(` ${pc.cyan("./test.sh")}`);
283
+ console.log(" Runs tests for both frontend and backend.\n");
168
284
 
169
- console.log(` ${pc.cyan('./lint.sh')}`);
170
- console.log(' Lints and formats both frontend and backend code.\n');
285
+ console.log(` ${pc.cyan("./lint.sh")}`);
286
+ console.log(" Lints and formats both frontend and backend code.\n");
171
287
 
172
- console.log('\nWe suggest that you begin by typing:\n');
288
+ console.log("\nWe suggest that you begin by typing:\n");
173
289
  console.log(pc.cyan(` cd ${projectName}`));
174
- console.log(pc.cyan(' ./start.sh'));
175
- console.log('\nHappy coding!');
176
-
290
+ console.log(pc.cyan(" ./start.sh"));
291
+ console.log("\nHappy coding!");
177
292
  } catch (error) {
178
- console.error(pc.red('Error scaffolding project:'), error);
293
+ console.error(pc.red("Error scaffolding project:"), error);
179
294
  process.exit(1);
180
295
  }
181
296
  }
package/cli/logo.js CHANGED
@@ -1,11 +1,11 @@
1
1
  module.exports = {
2
2
  asciiArt: `
3
- ________ ________ ________
4
- |\\ ____\\|\\ _____\\\\ __ \\
5
- \\ \\ \\___|\\ \\ \\__/\\ \\ \\|\\ \\
6
- \\ \\ \\ \\ \\ __\\\\ \\ __ \\
7
- \\ \\ \\____\\ \\ \\_| \\ \\ \\ \\ \\
8
- \\ \\_______\\ \\__\\ \\ \\__\\ \\__\\
9
- \\|_______|\\|__| \\|__|\\|__|
10
- `
3
+ █▒▒ █▒▒▒▒▒▒▒▒ █▒
4
+ █▒▒ █▒▒ █▒▒ █▒ ▒▒
5
+ █▒▒ █▒▒ █▒ █▒▒
6
+ █▒▒ █▒▒▒▒▒▒ █▒▒ █▒▒
7
+ █▒▒ █▒▒ █▒▒▒▒▒▒ █▒▒
8
+ █▒▒ █▒▒ █▒▒ █▒▒ █▒▒
9
+ █▒▒▒▒ █▒▒ █▒▒ █▒▒
10
+ `,
11
11
  };
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.3.0",
6
+ "version": "1.5.0",
7
7
  "description": "A CLI tool for scaffolding a full-stack web application with your choice of frontend and backend technologies.",
8
8
  "main": "cli/index.js",
9
9
  "bin": {
@@ -22,9 +22,9 @@
22
22
  "prompts": "^2.4.2"
23
23
  },
24
24
  "devDependencies": {
25
- "@playwright/test": "^1.59.1",
25
+ "@playwright/test": "^1.60.0",
26
26
  "vitest": "^4.1.5",
27
- "wait-on": "^9.0.5"
27
+ "wait-on": "^9.0.10"
28
28
  },
29
29
  "scripts": {
30
30
  "test": "echo \"Error: no test specified\" && exit 1"
@@ -35,7 +35,7 @@
35
35
  },
36
36
  "keywords": [],
37
37
  "author": "",
38
- "license": "ISC",
38
+ "license": "MIT",
39
39
  "bugs": {
40
40
  "url": "https://github.com/adriankulik/create-fullstack-app/issues"
41
41
  },
@@ -0,0 +1,32 @@
1
+ using Microsoft.AspNetCore.Mvc;
2
+
3
+ var builder = WebApplication.CreateBuilder(args);
4
+
5
+ // Add CORS to allow frontend to access
6
+ builder.Services.AddCors(options =>
7
+ {
8
+ options.AddDefaultPolicy(policy =>
9
+ {
10
+ policy.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod();
11
+ });
12
+ });
13
+
14
+ var app = builder.Build();
15
+
16
+ app.UseCors();
17
+
18
+ app.MapPost("/api/multiply", ([FromBody] MultiplyRequest request) =>
19
+ {
20
+ return new { result = request.Number * 2 };
21
+ });
22
+
23
+
24
+
25
+ app.Run("http://localhost:8000");
26
+
27
+ class MultiplyRequest
28
+ {
29
+ public double Number { get; set; }
30
+ }
31
+
32
+ public partial class Program { }
@@ -0,0 +1,41 @@
1
+ {
2
+ "$schema": "http://json.schemastore.org/launchsettings.json",
3
+ "iisSettings": {
4
+ "windowsAuthentication": false,
5
+ "anonymousAuthentication": true,
6
+ "iisExpress": {
7
+ "applicationUrl": "http://localhost:46754",
8
+ "sslPort": 44359
9
+ }
10
+ },
11
+ "profiles": {
12
+ "http": {
13
+ "commandName": "Project",
14
+ "dotnetRunMessages": true,
15
+ "launchBrowser": true,
16
+ "launchUrl": "weatherforecast",
17
+ "applicationUrl": "http://localhost:5028",
18
+ "environmentVariables": {
19
+ "ASPNETCORE_ENVIRONMENT": "Development"
20
+ }
21
+ },
22
+ "https": {
23
+ "commandName": "Project",
24
+ "dotnetRunMessages": true,
25
+ "launchBrowser": true,
26
+ "launchUrl": "weatherforecast",
27
+ "applicationUrl": "https://localhost:7200;http://localhost:5028",
28
+ "environmentVariables": {
29
+ "ASPNETCORE_ENVIRONMENT": "Development"
30
+ }
31
+ },
32
+ "IIS Express": {
33
+ "commandName": "IISExpress",
34
+ "launchBrowser": true,
35
+ "launchUrl": "weatherforecast",
36
+ "environmentVariables": {
37
+ "ASPNETCORE_ENVIRONMENT": "Development"
38
+ }
39
+ }
40
+ }
41
+ }
@@ -0,0 +1 @@
1
+ global using Xunit;
@@ -0,0 +1,30 @@
1
+ <Project Sdk="Microsoft.NET.Sdk">
2
+
3
+ <PropertyGroup>
4
+ <TargetFramework>net9.0</TargetFramework>
5
+ <ImplicitUsings>enable</ImplicitUsings>
6
+ <Nullable>enable</Nullable>
7
+
8
+ <IsPackable>false</IsPackable>
9
+ <IsTestProject>true</IsTestProject>
10
+ </PropertyGroup>
11
+
12
+ <ItemGroup>
13
+ <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.0" />
14
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0" />
15
+ <PackageReference Include="xunit" Version="2.4.2" />
16
+ <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
17
+ <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
18
+ <PrivateAssets>all</PrivateAssets>
19
+ </PackageReference>
20
+ <PackageReference Include="coverlet.collector" Version="6.0.0">
21
+ <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
22
+ <PrivateAssets>all</PrivateAssets>
23
+ </PackageReference>
24
+ </ItemGroup>
25
+
26
+ <ItemGroup>
27
+ <ProjectReference Include="..\dotnet.csproj" />
28
+ </ItemGroup>
29
+
30
+ </Project>
@@ -0,0 +1,43 @@
1
+ using System.Net.Http.Json;
2
+ using Microsoft.AspNetCore.Mvc.Testing;
3
+
4
+ namespace Tests;
5
+
6
+ public class UnitTest1 : IClassFixture<WebApplicationFactory<Program>>
7
+ {
8
+ private readonly WebApplicationFactory<Program> _factory;
9
+
10
+ public UnitTest1(WebApplicationFactory<Program> factory)
11
+ {
12
+ _factory = factory;
13
+ }
14
+
15
+ [Fact]
16
+ public async Task MultiplyEndpoint_ReturnsDouble()
17
+ {
18
+ var client = _factory.CreateClient();
19
+ var request = new { number = 5 };
20
+
21
+ var response = await client.PostAsJsonAsync("/api/multiply", request);
22
+ response.EnsureSuccessStatusCode();
23
+
24
+ var result = await response.Content.ReadFromJsonAsync<MultiplyResponse>();
25
+ Assert.NotNull(result);
26
+ Assert.Equal(10, result.result);
27
+ }
28
+
29
+ [Fact]
30
+ public async Task MultiplyEndpoint_Returns400_ForInvalidInput()
31
+ {
32
+ var client = _factory.CreateClient();
33
+ var request = new { number = "not a number" };
34
+
35
+ var response = await client.PostAsJsonAsync("/api/multiply", request);
36
+ Assert.Equal(System.Net.HttpStatusCode.BadRequest, response.StatusCode);
37
+ }
38
+
39
+ class MultiplyResponse
40
+ {
41
+ public double result { get; set; }
42
+ }
43
+ }