@adriankulik/create-fullstack-app 1.3.0 → 1.4.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 +13 -7
  2. package/cli/index.js +164 -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
@@ -5,6 +5,7 @@ A CLI tool for scaffolding a full-stack web application with your choice of fron
5
5
  ## Prerequisites
6
6
 
7
7
  Before running the CLI or the scaffolded applications, ensure you have the following installed on your system:
8
+
8
9
  - **Node.js**: v20 or newer
9
10
  - **npm**: v10 or newer
10
11
  - **Python**: v3.11 or newer
@@ -17,7 +18,7 @@ To generate a new full-stack application, simply run:
17
18
  npx @adriankulik/create-fullstack-app@latest
18
19
  ```
19
20
 
20
- *(If you are developing locally, run `npm install` in the root directory and execute `node cli/index.js`).*
21
+ _(If you are developing locally, run `npm install` in the root directory and execute `node cli/index.js`)._
21
22
 
22
23
  You will be prompted to choose a project name, your preferred frontend, and backend framework.
23
24
 
@@ -25,15 +26,18 @@ You will be prompted to choose a project name, your preferred frontend, and back
25
26
 
26
27
  - **Frontend Options**:
27
28
  - Next.js (v16.2.4)
28
- - Angular (v21.0.0) - *Configured with Zone.js*
29
+ - Angular (v21.0.0) - _Configured with Zone.js_
29
30
  - Vue (v3.5.33)
30
31
  - Svelte (v5.55.5)
31
- - **Backend Options**:
32
+ - **Backend Options**:
32
33
  - FastAPI (v0.136.1)
33
34
  - Flask (v3.1.3)
35
+ - .NET (v8.0)
36
+ - Node.js Express (v4.19.2)
37
+ - Rust Rocket (v0.5.0)
34
38
  - **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
39
  - **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.*
40
+ - **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
41
  - **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).
38
42
  <details>
39
43
  <summary>Included Agent Skills</summary>
@@ -60,7 +64,7 @@ my-app/
60
64
  └── lint.sh
61
65
  ```
62
66
 
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.*
67
+ \_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
68
 
65
69
  ## Local E2E Testing
66
70
 
@@ -85,11 +89,10 @@ Then run it:
85
89
  Future plans for this CLI include:
86
90
 
87
91
  - Adding frontend framework-specific component libraries during the scaffold step.
88
- - Tackling **.NET** as a new backend option.
89
92
 
90
93
  ## Acknowledgements
91
94
 
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:
95
+ 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
96
 
94
97
  - [Next.js](https://nextjs.org/) (MIT License)
95
98
  - [Angular](https://angular.dev/) (MIT License)
@@ -97,3 +100,6 @@ This project redistributes boilerplate code and configuration files generated by
97
100
  - [Svelte](https://svelte.dev/) (MIT License)
98
101
  - [FastAPI](https://fastapi.tiangolo.com/) (MIT License)
99
102
  - [Flask](https://flask.palletsprojects.com/) (BSD 3-Clause License)
103
+ - [.NET](https://dotnet.microsoft.com/) (MIT License)
104
+ - [Express](https://expressjs.com/) (MIT License)
105
+ - [Rocket](https://rocket.rs/) (MIT License)
package/cli/index.js CHANGED
@@ -1,63 +1,104 @@
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 { execSync } = require("child_process");
8
8
 
9
9
  async function main() {
10
- const { asciiArt } = require('./logo');
10
+ const { asciiArt } = require("./logo");
11
11
  console.log(pc.cyan(asciiArt));
12
- console.log(pc.cyan('\nWelcome to create-fullstack-app!\n'));
12
+ console.log(pc.cyan("\nWelcome to create-fullstack-app!\n"));
13
13
 
14
14
  // Simple argument parsing
15
15
  const args = process.argv.slice(2);
16
- let argProjectName = args[0] && !args[0].startsWith('--') ? args[0] : null;
16
+ let argProjectName = args[0] && !args[0].startsWith("--") ? args[0] : null;
17
17
  let argFrontend = null;
18
18
  let argBackend = null;
19
19
 
20
20
  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];
21
+ if (args[i] === "--frontend" && args[i + 1]) argFrontend = args[i + 1];
22
+ if (args[i] === "--backend" && args[i + 1]) argBackend = args[i + 1];
23
23
  }
24
24
 
25
25
  const questions = [];
26
26
  if (!argProjectName) {
27
27
  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'
28
+ type: "text",
29
+ name: "projectName",
30
+ message: "What is your project named?",
31
+ initial: "my-fullstack-app",
32
+ validate: (value) =>
33
+ value.trim().length > 0 ? true : "Project name cannot be empty",
33
34
  });
34
35
  }
35
36
 
36
37
  if (!argFrontend) {
37
38
  questions.push({
38
- type: 'select',
39
- name: 'frontend',
40
- message: 'Which frontend framework would you like to use?',
39
+ type: "select",
40
+ name: "frontend",
41
+ message: "Which frontend framework would you like to use?",
41
42
  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' }
43
+ { title: "Next.js", value: "nextjs", description: "React framework" },
44
+ {
45
+ title: "Angular",
46
+ value: "angular",
47
+ description: "Enterprise-grade platform",
48
+ },
49
+ {
50
+ title: "Vue",
51
+ value: "vue",
52
+ description: "Progressive JavaScript framework",
53
+ },
54
+ {
55
+ title: "Svelte",
56
+ value: "svelte",
57
+ description: "Cybernetically enhanced web apps",
58
+ },
46
59
  ],
47
- initial: 0
60
+ initial: 0,
48
61
  });
49
62
  }
50
63
 
51
64
  if (!argBackend) {
52
65
  questions.push({
53
- type: 'select',
54
- name: 'backend',
55
- message: 'Which backend framework would you like to use?',
66
+ type: "select",
67
+ name: "backend",
68
+ message: "Which backend framework would you like to use?",
56
69
  choices: [
57
- { title: 'FastAPI', value: 'fastapi', description: 'Modern, fast Python web framework' },
58
- { title: 'Flask', value: 'flask', description: 'Lightweight WSGI web application framework' }
70
+ {
71
+ title: "FastAPI",
72
+ value: "fastapi",
73
+ description: "Modern, fast Python web framework",
74
+ },
75
+ {
76
+ title: "Flask",
77
+ value: "flask",
78
+ description: "Lightweight Python WSGI web application framework",
79
+ },
80
+ {
81
+ title: ".NET",
82
+ value: "dotnet",
83
+ description: "Robust, C#, high-performance framework by Microsoft",
84
+ },
85
+ {
86
+ title: "Spring Boot",
87
+ value: "spring-boot",
88
+ description: "Powerful, Java, enterprise-ready framework",
89
+ },
90
+ {
91
+ title: "Node.js (Express)",
92
+ value: "nodejs",
93
+ description: "Minimalist web framework for Node.js",
94
+ },
95
+ {
96
+ title: "Rust (Rocket)",
97
+ value: "rust",
98
+ description: "Web framework for Rust with focus on ease of use",
99
+ }
59
100
  ],
60
- initial: 0
101
+ initial: 0,
61
102
  });
62
103
  }
63
104
 
@@ -68,7 +109,7 @@ async function main() {
68
109
  const backend = argBackend || response.backend;
69
110
 
70
111
  if (!projectName || !frontend || !backend) {
71
- console.log(pc.red('Setup cancelled.'));
112
+ console.log(pc.red("Setup cancelled."));
72
113
  process.exit(1);
73
114
  }
74
115
  const targetDir = path.resolve(process.cwd(), projectName);
@@ -81,28 +122,31 @@ async function main() {
81
122
  console.log(pc.blue(`\nScaffolding project in ${targetDir}...`));
82
123
 
83
124
  // 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);
125
+ const templatesDir = path.resolve(__dirname, "../templates");
126
+ const baseDir = path.join(templatesDir, "base");
127
+ const frontendDir = path.join(templatesDir, "frontend", frontend);
128
+ const backendDir = path.join(templatesDir, "backend", backend);
88
129
 
89
130
  try {
90
131
  // 1. Copy base template
91
132
  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'));
133
+ if (fs.existsSync(path.join(targetDir, "gitignore"))) {
134
+ await fs.rename(
135
+ path.join(targetDir, "gitignore"),
136
+ path.join(targetDir, ".gitignore"),
137
+ );
94
138
  }
95
139
 
96
140
  // 2. Copy frontend
97
- const targetFrontend = path.join(targetDir, 'frontend');
141
+ const targetFrontend = path.join(targetDir, "frontend");
98
142
  await fs.copy(frontendDir, targetFrontend);
99
143
 
100
144
  // 3. Copy backend
101
- const targetBackend = path.join(targetDir, 'backend');
145
+ const targetBackend = path.join(targetDir, "backend");
102
146
  await fs.copy(backendDir, targetBackend);
103
147
 
104
148
  // 4. Update package.json name for the frontend
105
- const pkgPath = path.join(targetFrontend, 'package.json');
149
+ const pkgPath = path.join(targetFrontend, "package.json");
106
150
  if (fs.existsSync(pkgPath)) {
107
151
  const pkg = await fs.readJson(pkgPath);
108
152
  pkg.name = `${projectName}-frontend`;
@@ -112,11 +156,11 @@ async function main() {
112
156
  // 5. Make all shell scripts executable
113
157
  const makeExecutable = (dir) => {
114
158
  const files = fs.readdirSync(dir);
115
- files.forEach(file => {
159
+ files.forEach((file) => {
116
160
  const filePath = path.join(dir, file);
117
161
  if (fs.statSync(filePath).isDirectory()) {
118
162
  makeExecutable(filePath);
119
- } else if (file.endsWith('.sh')) {
163
+ } else if (file.endsWith(".sh")) {
120
164
  fs.chmodSync(filePath, 0o755);
121
165
  }
122
166
  });
@@ -124,58 +168,102 @@ async function main() {
124
168
  makeExecutable(targetDir);
125
169
 
126
170
  // 6. Install dependencies
127
- console.log(pc.blue('\nInstalling dependencies (this may take a minute)...'));
171
+ console.log(
172
+ pc.blue("\nInstalling dependencies (this may take a minute)..."),
173
+ );
128
174
 
129
175
  // Frontend installation
130
- console.log(pc.cyan(' Installing frontend dependencies...'));
131
- execSync('npm ci', { cwd: targetFrontend, stdio: 'inherit' });
176
+ console.log(pc.cyan(" Installing frontend dependencies..."));
177
+ execSync("npm ci", { cwd: targetFrontend, stdio: "inherit" });
132
178
 
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.'));
179
+ if (backend === "fastapi" || backend === "flask") {
180
+ console.log(pc.cyan(" Setting up backend virtual environment..."));
181
+ const pythonCmd = process.platform === "win32" ? "python" : "python3";
182
+ try {
183
+ execSync(`${pythonCmd} -m venv venv`, {
184
+ cwd: targetBackend,
185
+ stdio: "inherit",
186
+ });
187
+
188
+ const pipPath =
189
+ process.platform === "win32"
190
+ ? path.join("venv", "Scripts", "pip")
191
+ : path.join("venv", "bin", "pip");
192
+
193
+ console.log(pc.cyan(" Installing backend dependencies..."));
194
+ execSync(`${pipPath} install -r requirements.txt`, {
195
+ cwd: targetBackend,
196
+ stdio: "inherit",
197
+ });
198
+ } catch (e) {
199
+ console.log(
200
+ pc.yellow(
201
+ " Could not set up Python virtual environment automatically. Please set it up manually.",
202
+ ),
203
+ );
204
+ }
205
+ } else if (backend === "dotnet") {
206
+ console.log(pc.cyan(" Restoring .NET dependencies..."));
207
+ try {
208
+ execSync("dotnet restore backend.sln", { cwd: targetBackend, stdio: "inherit" });
209
+ } catch (e) {
210
+ console.log(
211
+ pc.yellow(
212
+ " Could not restore .NET dependencies automatically. Please make sure .NET SDK is installed.",
213
+ ),
214
+ );
215
+ }
216
+ } else if (backend === "spring-boot") {
217
+ console.log(
218
+ pc.cyan(
219
+ " Spring Boot project created. Dependencies will be downloaded via Maven wrapper on first run.",
220
+ ),
221
+ );
222
+ } else if (backend === "nodejs") {
223
+ console.log(pc.cyan(" Installing Node.js backend dependencies..."));
224
+ execSync("npm ci", { cwd: targetBackend, stdio: "inherit" });
225
+ } else if (backend === "rust") {
226
+ console.log(pc.cyan(" Rust backend created. Cargo will manage dependencies automatically on first run."));
147
227
  }
148
228
 
149
229
  // 7. Initialize Git repository
150
- console.log(pc.blue('\nInitializing Git repository...'));
230
+ console.log(pc.blue("\nInitializing Git repository..."));
151
231
  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.'));
232
+ execSync("git init", { cwd: targetDir, stdio: "ignore" });
233
+ execSync("git add .", { cwd: targetDir, stdio: "ignore" });
234
+ execSync('git commit -m "Initial commit from create-fullstack-app"', {
235
+ cwd: targetDir,
236
+ stdio: "ignore",
237
+ });
238
+ console.log(pc.cyan(" Git repository initialized."));
156
239
  } catch (e) {
157
- console.log(pc.yellow(' Could not initialize Git repository automatically. You may need to run git init yourself.'));
240
+ console.log(
241
+ pc.yellow(
242
+ " Could not initialize Git repository automatically. You may need to run git init yourself.",
243
+ ),
244
+ );
158
245
  }
159
246
 
160
247
  console.log(pc.green(`\nSuccess! Created ${projectName} at ${targetDir}`));
161
- console.log('\nInside that directory, you can run several commands:\n');
248
+ console.log("\nInside that directory, you can run several commands:\n");
162
249
 
163
- console.log(` ${pc.cyan('./start.sh')}`);
164
- console.log(' Starts both the backend and frontend development servers.\n');
250
+ console.log(` ${pc.cyan("./start.sh")}`);
251
+ console.log(
252
+ " Starts both the backend and frontend development servers.\n",
253
+ );
165
254
 
166
- console.log(` ${pc.cyan('./test.sh')}`);
167
- console.log(' Runs tests for both frontend and backend.\n');
255
+ console.log(` ${pc.cyan("./test.sh")}`);
256
+ console.log(" Runs tests for both frontend and backend.\n");
168
257
 
169
- console.log(` ${pc.cyan('./lint.sh')}`);
170
- console.log(' Lints and formats both frontend and backend code.\n');
258
+ console.log(` ${pc.cyan("./lint.sh")}`);
259
+ console.log(" Lints and formats both frontend and backend code.\n");
171
260
 
172
- console.log('\nWe suggest that you begin by typing:\n');
261
+ console.log("\nWe suggest that you begin by typing:\n");
173
262
  console.log(pc.cyan(` cd ${projectName}`));
174
- console.log(pc.cyan(' ./start.sh'));
175
- console.log('\nHappy coding!');
176
-
263
+ console.log(pc.cyan(" ./start.sh"));
264
+ console.log("\nHappy coding!");
177
265
  } catch (error) {
178
- console.error(pc.red('Error scaffolding project:'), error);
266
+ console.error(pc.red("Error scaffolding project:"), error);
179
267
  process.exit(1);
180
268
  }
181
269
  }
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.4.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>net8.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="8.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
+ }