@dawitworku/projectcli 0.1.3 → 0.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dawitworku/projectcli",
3
- "version": "0.1.3",
3
+ "version": "0.2.0",
4
4
  "description": "Interactive project generator (language -> framework -> create).",
5
5
  "license": "MIT",
6
6
  "type": "commonjs",
package/src/cicd.js ADDED
@@ -0,0 +1,169 @@
1
+ const fs = require("node:fs");
2
+ const path = require("node:path");
3
+
4
+ const GITHUB_ACTIONS_JS = `name: CI
5
+ on: [push, pull_request]
6
+ jobs:
7
+ build:
8
+ runs-on: ubuntu-latest
9
+ steps:
10
+ - uses: actions/checkout@v4
11
+ - uses: actions/setup-node@v4
12
+ with:
13
+ node-version: 20
14
+ cache: 'npm'
15
+ - run: npm ci
16
+ - run: npm run build --if-present
17
+ - run: npm test --if-present
18
+ `;
19
+
20
+ const GITHUB_ACTIONS_PY = `name: CI
21
+ on: [push, pull_request]
22
+ jobs:
23
+ build:
24
+ runs-on: ubuntu-latest
25
+ steps:
26
+ - uses: actions/checkout@v4
27
+ - uses: actions/setup-python@v5
28
+ with:
29
+ python-version: '3.11'
30
+ cache: 'pip'
31
+ - run: pip install -r requirements.txt
32
+ `;
33
+
34
+ const GITHUB_ACTIONS_RUST = `name: CI
35
+ on: [push, pull_request]
36
+ jobs:
37
+ build:
38
+ runs-on: ubuntu-latest
39
+ steps:
40
+ - uses: actions/checkout@v4
41
+ - uses: dtolnay/rust-toolchain@stable
42
+ - run: cargo build --verbose
43
+ - run: cargo test --verbose
44
+ `;
45
+
46
+ const GITHUB_ACTIONS_GO = `name: CI
47
+ on: [push, pull_request]
48
+ jobs:
49
+ build:
50
+ runs-on: ubuntu-latest
51
+ steps:
52
+ - uses: actions/checkout@v4
53
+ - uses: actions/setup-go@v5
54
+ with:
55
+ go-version: '1.21'
56
+ - run: go build -v ./...
57
+ - run: go test -v ./...
58
+ `;
59
+
60
+ function getGitHubAction(language, pm) {
61
+ if (language === "JavaScript" || language === "TypeScript")
62
+ return GITHUB_ACTIONS_JS;
63
+ if (language === "Python") return GITHUB_ACTIONS_PY;
64
+ if (language === "Rust") return GITHUB_ACTIONS_RUST;
65
+ if (language === "Go") return GITHUB_ACTIONS_GO;
66
+ // Default generic
67
+ return `name: CI
68
+ on: [push, pull_request]
69
+ jobs:
70
+ build:
71
+ runs-on: ubuntu-latest
72
+ steps:
73
+ - uses: actions/checkout@v4
74
+ - run: echo "Add build steps here"
75
+ `;
76
+ }
77
+
78
+ const DOCKERFILE_JS = `FROM node:20-alpine AS base
79
+
80
+ FROM base AS deps
81
+ WORKDIR /app
82
+ COPY package.json ./
83
+ RUN npm install
84
+
85
+ FROM base AS runner
86
+ WORKDIR /app
87
+ COPY --from=deps /app/node_modules ./node_modules
88
+ COPY . .
89
+ # RUN npm run build
90
+ CMD ["node", "index.js"]
91
+ `;
92
+
93
+ const DOCKERFILE_PY = `FROM python:3.11-slim
94
+ WORKDIR /app
95
+ COPY requirements.txt .
96
+ RUN pip install -r requirements.txt
97
+ COPY . .
98
+ CMD ["python", "app.py"]
99
+ `;
100
+
101
+ const DOCKERFILE_GO = `FROM golang:1.21-alpine AS builder
102
+ WORKDIR /app
103
+ COPY . .
104
+ RUN go build -o myapp main.go
105
+
106
+ FROM alpine:latest
107
+ WORKDIR /app
108
+ COPY --from=builder /app/myapp .
109
+ CMD ["./myapp"]
110
+ `;
111
+
112
+ const DOCKERFILE_RUST = `FROM rust:1.75 as builder
113
+ WORKDIR /usr/src/myapp
114
+ COPY . .
115
+ RUN cargo install --path .
116
+
117
+ FROM debian:buster-slim
118
+ COPY --from=builder /usr/local/cargo/bin/myapp /usr/local/bin/myapp
119
+ CMD ["myapp"]
120
+ `;
121
+
122
+ function getDockerfile(language) {
123
+ if (language === "JavaScript" || language === "TypeScript")
124
+ return DOCKERFILE_JS;
125
+ if (language === "Python") return DOCKERFILE_PY;
126
+ if (language === "Go") return DOCKERFILE_GO;
127
+ if (language === "Rust") return DOCKERFILE_RUST;
128
+ return `# Dockerfile
129
+ FROM ubuntu:latest
130
+ WORKDIR /app
131
+ COPY . .
132
+ CMD ["echo", "Start..."]
133
+ `;
134
+ }
135
+
136
+ function generateCI(projectRoot, language, pm) {
137
+ const steps = [];
138
+
139
+ // GitHub Actions
140
+ const ghContent = getGitHubAction(language, pm);
141
+ steps.push({
142
+ type: "writeFile",
143
+ path: ".github/workflows/ci.yml",
144
+ content: ghContent,
145
+ });
146
+
147
+ return steps;
148
+ }
149
+
150
+ function generateDocker(projectRoot, language) {
151
+ const content = getDockerfile(language);
152
+ return [
153
+ {
154
+ type: "writeFile",
155
+ path: "Dockerfile",
156
+ content: content,
157
+ },
158
+ {
159
+ type: "writeFile",
160
+ path: ".dockerignore",
161
+ content: "node_modules\ntarget\n.venv\n.git\n",
162
+ },
163
+ ];
164
+ }
165
+
166
+ module.exports = {
167
+ generateCI,
168
+ generateDocker,
169
+ };
@@ -0,0 +1,45 @@
1
+ const DESCRIPTIONS = {
2
+ // JavaScript
3
+ "js.vite.vanilla":
4
+ "Native ESM-based build tool. Best for: fast prototyping, zero-framework learning.",
5
+ "js.vite.react":
6
+ "Most popular UI library. Best for: interactive UIs, large ecosystem, jobs.",
7
+ "js.vite.vue":
8
+ "Progressive framework. Best for: easy learning curve, clean template syntax.",
9
+ "js.vite.svelte":
10
+ "Compiler-based framework. Best for: performance, less boilerplate, true reactivity.",
11
+ "js.nextjs":
12
+ "The React Framework for the Web. Best for: SEO, SSR/SSG, full-stack apps.",
13
+ "js.astro":
14
+ "Content-focused site builder. Best for: blogs, documentation, portfolios (ships less JS).",
15
+ "js.express":
16
+ "Minimalist web framework for Node.js. Best for: REST APIs, learning backend basics.",
17
+ "js.nestjs":
18
+ "Angular-style backend framework. Best for: enterprise, scalable architecture, TypeScript.",
19
+
20
+ // Python
21
+ "py.django":
22
+ "Batteries-included web framework. Best for: rapid dev, CMS, huge ecosystem.",
23
+ "py.flask.basic":
24
+ "Microframework. Best for: simple services, learning, flexibility.",
25
+ "py.fastapi.basic":
26
+ "Modern, fast (high-performance). Best for: APIs with auto-docs (Swagger), async support.",
27
+
28
+ // Rust
29
+ "rs.cargo.bin":
30
+ "Standard Rust binary. Best for: CLI tools, backend services, learning Rust.",
31
+ "rs.tauri":
32
+ "Build smaller, faster, and more secure desktop applications with a web frontend.",
33
+
34
+ // Go
35
+ "go.module.basic":
36
+ "Go's dependency management system. Best for: microservices, high concurrency.",
37
+ };
38
+
39
+ function getDescription(id) {
40
+ return DESCRIPTIONS[id] || "No description available.";
41
+ }
42
+
43
+ module.exports = {
44
+ getDescription,
45
+ };
package/src/index.js CHANGED
@@ -28,6 +28,8 @@ try {
28
28
  const { getLanguages, getFrameworks, getGenerator } = require("./registry");
29
29
  const { runSteps } = require("./run");
30
30
  const { runAdd } = require("./add");
31
+ const { generateCI, generateDocker } = require("./cicd");
32
+ const { getDescription } = require("./descriptions");
31
33
 
32
34
  const RUST_KEYWORDS = new Set(
33
35
  [
@@ -129,6 +131,9 @@ function parseArgs(argv) {
129
131
  framework: undefined,
130
132
  name: undefined,
131
133
  pm: undefined,
134
+ ci: false,
135
+ docker: false,
136
+ learning: false,
132
137
  };
133
138
 
134
139
  const nextValue = (i) => {
@@ -143,6 +148,9 @@ function parseArgs(argv) {
143
148
  else if (a === "--list") out.list = true;
144
149
  else if (a === "--yes" || a === "-y") out.yes = true;
145
150
  else if (a === "--dry-run") out.dryRun = true;
151
+ else if (a === "--ci") out.ci = true;
152
+ else if (a === "--docker") out.docker = true;
153
+ else if (a === "--learning") out.learning = true;
146
154
  else if (a.startsWith("--language="))
147
155
  out.language = a.slice("--language=".length);
148
156
  else if (a === "--language") {
@@ -202,6 +210,9 @@ function printHelp() {
202
210
  console.log(
203
211
  " --pm Package manager for JS/TS (npm|pnpm|yarn|bun)"
204
212
  );
213
+ console.log(" --ci Auto-add GitHub Actions CI");
214
+ console.log(" --docker Auto-add Dockerfile");
215
+ console.log(" --learning Enable learning mode (shows descriptions)");
205
216
  }
206
217
 
207
218
  const BACK = "__back__";
@@ -247,6 +258,18 @@ function printList() {
247
258
  }
248
259
  }
249
260
 
261
+ function showBanner() {
262
+ console.log("");
263
+ console.log(
264
+ gradient.pastel.multiline(
265
+ figlet.textSync("ProjectCLI", { font: "Standard" })
266
+ )
267
+ );
268
+ console.log(chalk.bold.magenta(" The Swiss Army Knife for Developers"));
269
+ console.log(chalk.dim(" v" + readPackageVersion()));
270
+ console.log("");
271
+ }
272
+
250
273
  async function main(options = {}) {
251
274
  if (!prompt) {
252
275
  throw new Error(
@@ -257,7 +280,19 @@ async function main(options = {}) {
257
280
  const argv = options.argv || [];
258
281
  const { cmd, rest } = splitCommand(argv);
259
282
  const args = parseArgs(rest);
283
+
284
+ if (
285
+ !args.list &&
286
+ !args.version &&
287
+ !args.help &&
288
+ rest.length === 0 &&
289
+ cmd === "init"
290
+ ) {
291
+ showBanner();
292
+ }
293
+
260
294
  if (args.help) {
295
+ showBanner();
261
296
  printHelp();
262
297
  return;
263
298
  }
@@ -526,6 +561,20 @@ async function main(options = {}) {
526
561
  `Framework: ${state.framework}`,
527
562
  ];
528
563
  if (state.pm) summaryLines.push(`Package manager: ${state.pm}`);
564
+
565
+ if (args.learning) {
566
+ const desc = getDescription(generator.id);
567
+ console.log(
568
+ chalk.cyan(
569
+ boxen(desc, {
570
+ padding: 1,
571
+ title: `About ${state.framework}`,
572
+ borderStyle: "round",
573
+ })
574
+ )
575
+ );
576
+ }
577
+
529
578
  console.log("\n" + summaryLines.join("\n") + "\n");
530
579
 
531
580
  if (targetExists && args.dryRun) {
@@ -623,6 +672,83 @@ async function main(options = {}) {
623
672
  continue;
624
673
  }
625
674
 
675
+ // Git init
676
+ if (!args.dryRun) {
677
+ let doGit = args.yes;
678
+ if (!doGit) {
679
+ const { git } = await prompt([
680
+ {
681
+ type: "confirm",
682
+ name: "git",
683
+ message: "Initialize a new git repository?",
684
+ default: true,
685
+ },
686
+ ]);
687
+ doGit = git;
688
+ }
689
+
690
+ if (doGit) {
691
+ try {
692
+ await runSteps(
693
+ [
694
+ { program: "git", args: ["init"], cwdFromProjectRoot: true },
695
+ {
696
+ program: "git",
697
+ args: ["add", "."],
698
+ cwdFromProjectRoot: true,
699
+ },
700
+ {
701
+ program: "git",
702
+ args: ["commit", "-m", "Initial commit"],
703
+ cwdFromProjectRoot: true,
704
+ },
705
+ ],
706
+ { projectRoot }
707
+ );
708
+ console.log("Initialized git repository.");
709
+ } catch (e) {
710
+ console.warn("Git init failed (is git installed?):", e.message);
711
+ }
712
+ }
713
+ }
714
+
715
+ // CI/CD & Docker
716
+ if (!args.dryRun) {
717
+ let wantCi = args.ci;
718
+ let wantDocker = args.docker;
719
+
720
+ if (!args.yes) {
721
+ const { extras } = await prompt([
722
+ {
723
+ type: "checkbox",
724
+ name: "extras",
725
+ message: "Extras:",
726
+ choices: [
727
+ { name: "GitHub Actions CI", value: "ci", checked: wantCi },
728
+ { name: "Dockerfile", value: "docker", checked: wantDocker },
729
+ ],
730
+ },
731
+ ]);
732
+ if (extras) {
733
+ wantCi = extras.includes("ci");
734
+ wantDocker = extras.includes("docker");
735
+ }
736
+ }
737
+
738
+ const extraSteps = [];
739
+ if (wantCi) {
740
+ extraSteps.push(...generateCI(projectRoot, state.language, state.pm));
741
+ }
742
+ if (wantDocker) {
743
+ extraSteps.push(...generateDocker(projectRoot, state.language));
744
+ }
745
+
746
+ if (extraSteps.length > 0) {
747
+ console.log("Adding extras...");
748
+ await runSteps(extraSteps, { projectRoot });
749
+ }
750
+ }
751
+
626
752
  console.log(`\nDone. Created project in: ${projectRoot}`);
627
753
  return;
628
754
  }
package/src/libraries.js CHANGED
@@ -137,11 +137,60 @@ const GO = {
137
137
  ],
138
138
  };
139
139
 
140
+ const PHP = {
141
+ "Web Frameworks": [
142
+ { label: "Laravel", packages: ["laravel/framework"] },
143
+ { label: "Symfony", packages: ["symfony/symfony"] },
144
+ { label: "Slim", packages: ["slim/slim"] },
145
+ ],
146
+ Testing: [
147
+ { label: "PHPUnit", packages: [], packagesDev: ["phpunit/phpunit"] },
148
+ { label: "Pest", packages: [], packagesDev: ["pestphp/pest"] },
149
+ ],
150
+ Tools: [
151
+ {
152
+ label: "PHPStudio (CS Fixer)",
153
+ packages: [],
154
+ packagesDev: ["friendsofphp/php-cs-fixer"],
155
+ },
156
+ { label: "Monolog", packages: ["monolog/monolog"] },
157
+ ],
158
+ };
159
+
160
+ const RUBY = {
161
+ Web: [
162
+ { label: "Rails", packages: ["rails"] },
163
+ { label: "Sinatra", packages: ["sinatra"] },
164
+ ],
165
+ Testing: [{ label: "RSpec", packages: ["rspec"] }],
166
+ Tools: [{ label: "Rubocop", packages: ["rubocop"] }],
167
+ };
168
+
169
+ const DART = {
170
+ "Web/App": [{ label: "Shelf (Server)", packages: ["shelf", "shelf_router"] }],
171
+ State: [
172
+ { label: "Provider", packages: ["provider"] },
173
+ { label: "Riverpod", packages: ["flutter_riverpod"] },
174
+ { label: "Bloc", packages: ["flutter_bloc"] },
175
+ ],
176
+ Utils: [
177
+ { label: "Dio (HTTP)", packages: ["dio"] },
178
+ {
179
+ label: "Freezed",
180
+ packages: ["freezed_annotation"],
181
+ packagesDev: ["build_runner", "freezed"],
182
+ },
183
+ ],
184
+ };
185
+
140
186
  function getCatalog(language) {
141
187
  if (language === "JavaScript/TypeScript") return JS_TS;
142
188
  if (language === "Python") return PY;
143
189
  if (language === "Rust") return RUST;
144
190
  if (language === "Go") return GO;
191
+ if (language === "PHP") return PHP;
192
+ if (language === "Ruby") return RUBY;
193
+ if (language === "Dart") return DART;
145
194
  return {};
146
195
  }
147
196
 
package/src/pm.js CHANGED
@@ -50,6 +50,33 @@ function pmAddCommand(pm, packages, { dev = false } = {}) {
50
50
  return { program: "go", args: ["get", ...pkgs] };
51
51
  }
52
52
 
53
+ // PHP
54
+ if (pm === "composer") {
55
+ return {
56
+ program: "composer",
57
+ args: ["require", ...(dev ? ["--dev"] : []), ...pkgs],
58
+ };
59
+ }
60
+
61
+ // Ruby
62
+ if (pm === "bundle") {
63
+ // bundle add pkg
64
+ // dev dependencies often go to "development" or "test" group, but let's just do default add for now
65
+ // or standard "bundle add generic"
66
+ return {
67
+ program: "bundle",
68
+ args: ["add", ...pkgs, ...(dev ? ["--group", "development,test"] : [])],
69
+ };
70
+ }
71
+
72
+ // Dart
73
+ if (pm === "dart") {
74
+ return {
75
+ program: "dart",
76
+ args: ["pub", "add", ...(dev ? ["--dev"] : []), ...pkgs],
77
+ };
78
+ }
79
+
53
80
  return { program: "npm", args: ["install", ...(dev ? ["-D"] : []), ...pkgs] };
54
81
  }
55
82
 
@@ -58,7 +85,7 @@ function pmExecCommand(pm, pkg, pkgArgs) {
58
85
 
59
86
  if (pm === "pnpm") return { program: "pnpm", args: ["dlx", pkg, ...args] };
60
87
  if (pm === "yarn") return { program: "yarn", args: ["dlx", pkg, ...args] };
61
- if (pm === "bun") return { program: "bunx", args: [pkg, ...args] };
88
+ if (pm === "bun") return { program: "bun", args: ["x", pkg, ...args] };
62
89
  if (pm === "mvn")
63
90
  return { program: "mvn", args: ["archetype:generate", ...args] };
64
91
  if (pm === "gradle") return { program: "gradle", args: ["init", ...args] };
package/src/registry.js CHANGED
@@ -429,17 +429,17 @@ const REGISTRY = {
429
429
  type: "writeFile",
430
430
  path: "app.py",
431
431
  content:
432
- "from flask import Flask\n\napp = Flask(__name__)\n\n@app.get('/')\ndef hello():\n return {'status': 'ok'}\n\nif __name__ == '__main__':\n app.run(debug=True)\n",
432
+ "from flask import Flask\\n\\napp = Flask(__name__)\\n\\n@app.get('/')\\ndef hello():\\n return {'status': 'ok'}\\n\\nif __name__ == '__main__':\\n app.run(debug=True)\\n",
433
433
  },
434
434
  {
435
435
  type: "writeFile",
436
436
  path: "requirements.txt",
437
- content: "flask\n",
437
+ content: "flask\\n",
438
438
  },
439
439
  {
440
440
  type: "writeFile",
441
441
  path: ".gitignore",
442
- content: ".venv\n__pycache__\n*.pyc\n.DS_Store\n",
442
+ content: ".venv\\n__pycache__\\n*.pyc\\n.DS_Store\\n",
443
443
  },
444
444
  ],
445
445
  notes: "Writes app.py + requirements.txt (no pip install).",
@@ -453,23 +453,23 @@ const REGISTRY = {
453
453
  type: "writeFile",
454
454
  path: "main.py",
455
455
  content:
456
- "from fastapi import FastAPI\n\napp = FastAPI()\n\n@app.get('/')\ndef root():\n return {'status': 'ok'}\n",
456
+ "from fastapi import FastAPI\\n\\napp = FastAPI()\\n\\n@app.get('/')\\ndef root():\\n return {'status': 'ok'}\\n",
457
457
  },
458
458
  {
459
459
  type: "writeFile",
460
460
  path: "requirements.txt",
461
- content: "fastapi\nuvicorn\n",
461
+ content: "fastapi\\nuvicorn\\n",
462
462
  },
463
463
  {
464
464
  type: "writeFile",
465
465
  path: "README.md",
466
466
  content:
467
- "# FastAPI app\n\nRun:\n\n- python -m venv .venv\n- . .venv/bin/activate\n- pip install -r requirements.txt\n- uvicorn main:app --reload\n",
467
+ "# FastAPI app\\n\\nRun:\\n\\n- python -m venv .venv\\n- . .venv/bin/activate\\n- pip install -r requirements.txt\\n- uvicorn main:app --reload\\n",
468
468
  },
469
469
  {
470
470
  type: "writeFile",
471
471
  path: ".gitignore",
472
- content: ".venv\n__pycache__\n*.pyc\n.DS_Store\n",
472
+ content: ".venv\\n__pycache__\\n*.pyc\\n.DS_Store\\n",
473
473
  },
474
474
  ],
475
475
  notes: "Writes files only (no pip install).",
@@ -480,18 +480,17 @@ const REGISTRY = {
480
480
  commands: (ctx) => {
481
481
  const projectName = getProjectName(ctx);
482
482
  return [
483
- // Pyramid cookiecutter is common, but basic scaffold:
484
483
  { type: "mkdir", path: "." },
485
484
  {
486
485
  type: "writeFile",
487
486
  path: "requirements.txt",
488
- content: "pyramid\nwaitress\n",
487
+ content: "pyramid\\nwaitress\\n",
489
488
  },
490
489
  {
491
490
  type: "writeFile",
492
491
  path: "app.py",
493
492
  content:
494
- "from wsgiref.simple_server import make_server\nfrom pyramid.config import Configurator\nfrom pyramid.response import Response\n\ndef hello_world(request):\n return Response('Hello World!')\n\nif __name__ == '__main__':\n with Configurator() as config:\n config.add_route('hello', '/')\n config.add_view(hello_world, route_name='hello')\n app = config.make_wsgi_app()\n server = make_server('0.0.0.0', 6543, app)\n server.serve_forever()\n",
493
+ "from wsgiref.simple_server import make_server\\nfrom pyramid.config import Configurator\\nfrom pyramid.response import Response\\n\\ndef hello_world(request):\\n return Response('Hello World!')\\n\\nif __name__ == '__main__':\\n with Configurator() as config:\\n config.add_route('hello', '/')\\n config.add_view(hello_world, route_name='hello')\\n app = config.make_wsgi_app()\\n server = make_server('0.0.0.0', 6543, app)\\n server.serve_forever()\\n",
495
494
  },
496
495
  ];
497
496
  },
@@ -534,8 +533,6 @@ const REGISTRY = {
534
533
  commands: (ctx) => {
535
534
  const projectName = getProjectName(ctx);
536
535
  const pm = getPackageManager(ctx); // usually npm for tauri frontend
537
- // Using create-tauri-app via pmExec
538
- // cargo create-tauri-app is also an option but node way is common if JS frontend
539
536
  return [pmExec(pm, "create-tauri-app@latest", [projectName])];
540
537
  },
541
538
  notes: "Cross-platform desktop app",
@@ -551,8 +548,6 @@ const REGISTRY = {
551
548
  args: ["init", "--name", projectName],
552
549
  cwdFromProjectRoot: true,
553
550
  },
554
- // In reality, one would use cargo-leptos or a template.
555
- // Just a stub for now.
556
551
  ];
557
552
  },
558
553
  notes: "Init basic Rust project (Leptos needs template usually)",
@@ -591,7 +586,7 @@ const REGISTRY = {
591
586
  type: "writeFile",
592
587
  path: "main.go",
593
588
  content:
594
- 'package main\n\nimport "fmt"\n\nfunc main() {\n fmt.Println("hello")\n}\n',
589
+ 'package main\\n\\nimport "fmt"\\n\\nfunc main() {\\n fmt.Println("hello")\\n}\\n',
595
590
  },
596
591
  ];
597
592
  },
@@ -783,8 +778,6 @@ int main() {
783
778
  id: "java.springboot.maven",
784
779
  label: "Spring Boot (Maven)",
785
780
  commands: (ctx) => {
786
- // Simple archetype or just hints. Spring Init is complex.
787
- // Let's use maven quickstart for now as basic.
788
781
  const projectName = getProjectName(ctx);
789
782
  return [
790
783
  {
@@ -819,11 +812,8 @@ int main() {
819
812
  "--project-name",
820
813
  projectName,
821
814
  ],
822
- cwd: projectName, // This property needs to be supported in runSteps, handled via chdir logic usually?
823
- // current runSteps implementation supports cwdFromProjectRoot but not arbitrary cwd change easily maybe?
824
- // runSteps implementation check:
825
- // It uses `cwdFromProjectRoot` or defaults to current process.cwd().
826
- // Better to just run gradle init inside the folder.
815
+ cwd: projectName,
816
+ cwdFromProjectRoot: true,
827
817
  },
828
818
  ];
829
819
  },