@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.
- package/README.md +25 -9
- package/cli/index.js +191 -76
- package/cli/logo.js +8 -8
- 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
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
# create-fullstack-app
|
|
2
2
|
|
|
3
|
+
[](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
|
-
|
|
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) -
|
|
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.
|
|
37
|
-
- **Developer Experience**: Includes `.vscode/extensions.json` recommending the necessary linters and formatters, and an `AGENTS.md` file providing behavioral guidelines for AI agents
|
|
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
|
-
|
|
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
|
-
-
|
|
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.
|
|
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(
|
|
4
|
-
const pc = require(
|
|
5
|
-
const fs = require(
|
|
6
|
-
const path = require(
|
|
7
|
-
const
|
|
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(
|
|
11
|
+
const { asciiArt } = require("./logo");
|
|
11
12
|
console.log(pc.cyan(asciiArt));
|
|
12
|
-
console.log(pc.cyan(
|
|
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(
|
|
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] ===
|
|
22
|
-
if (args[i] ===
|
|
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:
|
|
29
|
-
name:
|
|
30
|
-
message:
|
|
31
|
-
initial:
|
|
32
|
-
validate: value =>
|
|
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:
|
|
39
|
-
name:
|
|
40
|
-
message:
|
|
40
|
+
type: "select",
|
|
41
|
+
name: "frontend",
|
|
42
|
+
message: "Which frontend framework would you like to use?",
|
|
41
43
|
choices: [
|
|
42
|
-
{ title:
|
|
43
|
-
{
|
|
44
|
-
|
|
45
|
-
|
|
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:
|
|
54
|
-
name:
|
|
55
|
-
message:
|
|
67
|
+
type: "select",
|
|
68
|
+
name: "backend",
|
|
69
|
+
message: "Which backend framework would you like to use?",
|
|
56
70
|
choices: [
|
|
57
|
-
{
|
|
58
|
-
|
|
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(
|
|
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,
|
|
85
|
-
const baseDir = path.join(templatesDir,
|
|
86
|
-
const frontendDir = path.join(templatesDir,
|
|
87
|
-
const backendDir = path.join(templatesDir,
|
|
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,
|
|
93
|
-
await fs.rename(
|
|
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,
|
|
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,
|
|
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,
|
|
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(
|
|
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(
|
|
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(
|
|
131
|
-
execSync(
|
|
167
|
+
console.log(pc.cyan(" Installing frontend dependencies..."));
|
|
168
|
+
execSync("npm ci", { cwd: targetFrontend, stdio: "inherit" });
|
|
132
169
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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(
|
|
257
|
+
console.log(pc.blue("\nInitializing Git repository..."));
|
|
151
258
|
try {
|
|
152
|
-
execSync(
|
|
153
|
-
execSync(
|
|
154
|
-
execSync('git commit -m "Initial commit from create-fullstack-app"', {
|
|
155
|
-
|
|
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(
|
|
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(
|
|
275
|
+
console.log("\nInside that directory, you can run several commands:\n");
|
|
162
276
|
|
|
163
|
-
console.log(` ${pc.cyan(
|
|
164
|
-
console.log(
|
|
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(
|
|
167
|
-
console.log(
|
|
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(
|
|
170
|
-
console.log(
|
|
285
|
+
console.log(` ${pc.cyan("./lint.sh")}`);
|
|
286
|
+
console.log(" Lints and formats both frontend and backend code.\n");
|
|
171
287
|
|
|
172
|
-
console.log(
|
|
288
|
+
console.log("\nWe suggest that you begin by typing:\n");
|
|
173
289
|
console.log(pc.cyan(` cd ${projectName}`));
|
|
174
|
-
console.log(pc.cyan(
|
|
175
|
-
console.log(
|
|
176
|
-
|
|
290
|
+
console.log(pc.cyan(" ./start.sh"));
|
|
291
|
+
console.log("\nHappy coding!");
|
|
177
292
|
} catch (error) {
|
|
178
|
-
console.error(pc.red(
|
|
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.
|
|
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.
|
|
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>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
|
+
}
|