@adriankulik/create-fullstack-app 1.2.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.
- package/README.md +13 -7
- package/cli/index.js +165 -75
- package/cli/logo.js +11 -0
- package/package.json +4 -4
- package/templates/backend/dotnet/Program.cs +32 -0
- package/templates/backend/dotnet/Properties/launchSettings.json +41 -0
- package/templates/backend/dotnet/Tests/GlobalUsings.cs +1 -0
- package/templates/backend/dotnet/Tests/Tests.csproj +30 -0
- package/templates/backend/dotnet/Tests/UnitTest1.cs +43 -0
- package/templates/backend/dotnet/Tests/obj/Tests.csproj.nuget.dgspec.json +147 -0
- package/templates/backend/dotnet/Tests/obj/Tests.csproj.nuget.g.props +26 -0
- package/templates/backend/dotnet/Tests/obj/Tests.csproj.nuget.g.targets +15 -0
- package/templates/backend/dotnet/Tests/obj/project.assets.json +7485 -0
- package/templates/backend/dotnet/Tests/obj/project.nuget.cache +132 -0
- package/templates/backend/dotnet/appsettings.Development.json +8 -0
- package/templates/backend/dotnet/appsettings.json +9 -0
- package/templates/backend/dotnet/backend.sln +28 -0
- package/templates/backend/dotnet/dotnet.csproj +17 -0
- package/templates/backend/dotnet/dotnet.http +6 -0
- package/templates/backend/dotnet/lint.sh +2 -0
- package/templates/backend/dotnet/start.sh +2 -0
- package/templates/backend/dotnet/test.sh +2 -0
- package/templates/backend/fastapi/main.py +1 -3
- package/templates/backend/fastapi/test_main.py +0 -4
- package/templates/backend/flask/main.py +1 -3
- package/templates/backend/flask/test_main.py +0 -4
- package/templates/backend/nodejs/.eslintrc.json +15 -0
- package/templates/backend/nodejs/lint.sh +2 -0
- package/templates/backend/nodejs/package-lock.json +5227 -0
- package/templates/backend/nodejs/package.json +27 -0
- package/templates/backend/nodejs/src/index.test.ts +22 -0
- package/templates/backend/nodejs/src/index.ts +23 -0
- package/templates/backend/nodejs/start.sh +2 -0
- package/templates/backend/nodejs/test.sh +2 -0
- 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
|
-
|
|
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) -
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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,61 +1,104 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
const prompts = require(
|
|
4
|
-
const pc = require(
|
|
5
|
-
const fs = require(
|
|
6
|
-
const path = require(
|
|
7
|
-
const { execSync } = require(
|
|
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
|
-
|
|
10
|
+
const { asciiArt } = require("./logo");
|
|
11
|
+
console.log(pc.cyan(asciiArt));
|
|
12
|
+
console.log(pc.cyan("\nWelcome to create-fullstack-app!\n"));
|
|
11
13
|
|
|
12
14
|
// Simple argument parsing
|
|
13
15
|
const args = process.argv.slice(2);
|
|
14
|
-
let argProjectName = args[0] && !args[0].startsWith(
|
|
16
|
+
let argProjectName = args[0] && !args[0].startsWith("--") ? args[0] : null;
|
|
15
17
|
let argFrontend = null;
|
|
16
18
|
let argBackend = null;
|
|
17
19
|
|
|
18
20
|
for (let i = 0; i < args.length; i++) {
|
|
19
|
-
if (args[i] ===
|
|
20
|
-
if (args[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
23
|
}
|
|
22
24
|
|
|
23
25
|
const questions = [];
|
|
24
26
|
if (!argProjectName) {
|
|
25
27
|
questions.push({
|
|
26
|
-
type:
|
|
27
|
-
name:
|
|
28
|
-
message:
|
|
29
|
-
initial:
|
|
30
|
-
validate: value =>
|
|
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",
|
|
31
34
|
});
|
|
32
35
|
}
|
|
33
36
|
|
|
34
37
|
if (!argFrontend) {
|
|
35
38
|
questions.push({
|
|
36
|
-
type:
|
|
37
|
-
name:
|
|
38
|
-
message:
|
|
39
|
+
type: "select",
|
|
40
|
+
name: "frontend",
|
|
41
|
+
message: "Which frontend framework would you like to use?",
|
|
39
42
|
choices: [
|
|
40
|
-
{ title:
|
|
41
|
-
{
|
|
42
|
-
|
|
43
|
-
|
|
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
|
+
},
|
|
44
59
|
],
|
|
45
|
-
initial: 0
|
|
60
|
+
initial: 0,
|
|
46
61
|
});
|
|
47
62
|
}
|
|
48
63
|
|
|
49
64
|
if (!argBackend) {
|
|
50
65
|
questions.push({
|
|
51
|
-
type:
|
|
52
|
-
name:
|
|
53
|
-
message:
|
|
66
|
+
type: "select",
|
|
67
|
+
name: "backend",
|
|
68
|
+
message: "Which backend framework would you like to use?",
|
|
54
69
|
choices: [
|
|
55
|
-
{
|
|
56
|
-
|
|
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
|
+
}
|
|
57
100
|
],
|
|
58
|
-
initial: 0
|
|
101
|
+
initial: 0,
|
|
59
102
|
});
|
|
60
103
|
}
|
|
61
104
|
|
|
@@ -66,7 +109,7 @@ async function main() {
|
|
|
66
109
|
const backend = argBackend || response.backend;
|
|
67
110
|
|
|
68
111
|
if (!projectName || !frontend || !backend) {
|
|
69
|
-
console.log(pc.red(
|
|
112
|
+
console.log(pc.red("Setup cancelled."));
|
|
70
113
|
process.exit(1);
|
|
71
114
|
}
|
|
72
115
|
const targetDir = path.resolve(process.cwd(), projectName);
|
|
@@ -79,28 +122,31 @@ async function main() {
|
|
|
79
122
|
console.log(pc.blue(`\nScaffolding project in ${targetDir}...`));
|
|
80
123
|
|
|
81
124
|
// Determine paths to templates relative to the CLI script
|
|
82
|
-
const templatesDir = path.resolve(__dirname,
|
|
83
|
-
const baseDir = path.join(templatesDir,
|
|
84
|
-
const frontendDir = path.join(templatesDir,
|
|
85
|
-
const backendDir = path.join(templatesDir,
|
|
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);
|
|
86
129
|
|
|
87
130
|
try {
|
|
88
131
|
// 1. Copy base template
|
|
89
132
|
await fs.copy(baseDir, targetDir);
|
|
90
|
-
if (fs.existsSync(path.join(targetDir,
|
|
91
|
-
await fs.rename(
|
|
133
|
+
if (fs.existsSync(path.join(targetDir, "gitignore"))) {
|
|
134
|
+
await fs.rename(
|
|
135
|
+
path.join(targetDir, "gitignore"),
|
|
136
|
+
path.join(targetDir, ".gitignore"),
|
|
137
|
+
);
|
|
92
138
|
}
|
|
93
139
|
|
|
94
140
|
// 2. Copy frontend
|
|
95
|
-
const targetFrontend = path.join(targetDir,
|
|
141
|
+
const targetFrontend = path.join(targetDir, "frontend");
|
|
96
142
|
await fs.copy(frontendDir, targetFrontend);
|
|
97
143
|
|
|
98
144
|
// 3. Copy backend
|
|
99
|
-
const targetBackend = path.join(targetDir,
|
|
145
|
+
const targetBackend = path.join(targetDir, "backend");
|
|
100
146
|
await fs.copy(backendDir, targetBackend);
|
|
101
147
|
|
|
102
148
|
// 4. Update package.json name for the frontend
|
|
103
|
-
const pkgPath = path.join(targetFrontend,
|
|
149
|
+
const pkgPath = path.join(targetFrontend, "package.json");
|
|
104
150
|
if (fs.existsSync(pkgPath)) {
|
|
105
151
|
const pkg = await fs.readJson(pkgPath);
|
|
106
152
|
pkg.name = `${projectName}-frontend`;
|
|
@@ -110,11 +156,11 @@ async function main() {
|
|
|
110
156
|
// 5. Make all shell scripts executable
|
|
111
157
|
const makeExecutable = (dir) => {
|
|
112
158
|
const files = fs.readdirSync(dir);
|
|
113
|
-
files.forEach(file => {
|
|
159
|
+
files.forEach((file) => {
|
|
114
160
|
const filePath = path.join(dir, file);
|
|
115
161
|
if (fs.statSync(filePath).isDirectory()) {
|
|
116
162
|
makeExecutable(filePath);
|
|
117
|
-
} else if (file.endsWith(
|
|
163
|
+
} else if (file.endsWith(".sh")) {
|
|
118
164
|
fs.chmodSync(filePath, 0o755);
|
|
119
165
|
}
|
|
120
166
|
});
|
|
@@ -122,58 +168,102 @@ async function main() {
|
|
|
122
168
|
makeExecutable(targetDir);
|
|
123
169
|
|
|
124
170
|
// 6. Install dependencies
|
|
125
|
-
console.log(
|
|
171
|
+
console.log(
|
|
172
|
+
pc.blue("\nInstalling dependencies (this may take a minute)..."),
|
|
173
|
+
);
|
|
126
174
|
|
|
127
175
|
// Frontend installation
|
|
128
|
-
console.log(pc.cyan(
|
|
129
|
-
execSync(
|
|
176
|
+
console.log(pc.cyan(" Installing frontend dependencies..."));
|
|
177
|
+
execSync("npm ci", { cwd: targetFrontend, stdio: "inherit" });
|
|
130
178
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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."));
|
|
145
227
|
}
|
|
146
228
|
|
|
147
229
|
// 7. Initialize Git repository
|
|
148
|
-
console.log(pc.blue(
|
|
230
|
+
console.log(pc.blue("\nInitializing Git repository..."));
|
|
149
231
|
try {
|
|
150
|
-
execSync(
|
|
151
|
-
execSync(
|
|
152
|
-
execSync('git commit -m "Initial commit from create-fullstack-app"', {
|
|
153
|
-
|
|
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."));
|
|
154
239
|
} catch (e) {
|
|
155
|
-
console.log(
|
|
240
|
+
console.log(
|
|
241
|
+
pc.yellow(
|
|
242
|
+
" Could not initialize Git repository automatically. You may need to run git init yourself.",
|
|
243
|
+
),
|
|
244
|
+
);
|
|
156
245
|
}
|
|
157
246
|
|
|
158
247
|
console.log(pc.green(`\nSuccess! Created ${projectName} at ${targetDir}`));
|
|
159
|
-
console.log(
|
|
248
|
+
console.log("\nInside that directory, you can run several commands:\n");
|
|
160
249
|
|
|
161
|
-
console.log(` ${pc.cyan(
|
|
162
|
-
console.log(
|
|
250
|
+
console.log(` ${pc.cyan("./start.sh")}`);
|
|
251
|
+
console.log(
|
|
252
|
+
" Starts both the backend and frontend development servers.\n",
|
|
253
|
+
);
|
|
163
254
|
|
|
164
|
-
console.log(` ${pc.cyan(
|
|
165
|
-
console.log(
|
|
255
|
+
console.log(` ${pc.cyan("./test.sh")}`);
|
|
256
|
+
console.log(" Runs tests for both frontend and backend.\n");
|
|
166
257
|
|
|
167
|
-
console.log(` ${pc.cyan(
|
|
168
|
-
console.log(
|
|
258
|
+
console.log(` ${pc.cyan("./lint.sh")}`);
|
|
259
|
+
console.log(" Lints and formats both frontend and backend code.\n");
|
|
169
260
|
|
|
170
|
-
console.log(
|
|
261
|
+
console.log("\nWe suggest that you begin by typing:\n");
|
|
171
262
|
console.log(pc.cyan(` cd ${projectName}`));
|
|
172
|
-
console.log(pc.cyan(
|
|
173
|
-
console.log(
|
|
174
|
-
|
|
263
|
+
console.log(pc.cyan(" ./start.sh"));
|
|
264
|
+
console.log("\nHappy coding!");
|
|
175
265
|
} catch (error) {
|
|
176
|
-
console.error(pc.red(
|
|
266
|
+
console.error(pc.red("Error scaffolding project:"), error);
|
|
177
267
|
process.exit(1);
|
|
178
268
|
}
|
|
179
269
|
}
|
package/cli/logo.js
ADDED
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "1.
|
|
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.
|
|
25
|
+
"@playwright/test": "^1.60.0",
|
|
26
26
|
"vitest": "^4.1.5",
|
|
27
|
-
"wait-on": "^9.0.
|
|
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": "
|
|
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
|
+
}
|