@dawitworku/projectcli 0.2.0 โ 0.2.1
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 +73 -38
- package/package.json +11 -2
- package/src/config.js +34 -0
- package/src/index.js +214 -5
- package/src/preflight.js +25 -0
- package/src/registry.js +6 -0
- package/src/remote.js +39 -0
- package/src/settings.js +45 -0
package/README.md
CHANGED
|
@@ -1,75 +1,110 @@
|
|
|
1
|
-
#
|
|
1
|
+
# ProjectCLI ๐
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> **The Swiss Army Knife for Project Scaffolding.**
|
|
4
|
+
> Bootstrapping new projects shouldn't require memorizing 50 different CLI commands.
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
[](https://badge.fury.io/js/@dawitworku%2Fprojectcli)
|
|
8
|
+
[](http://makeapullrequest.com)
|
|
6
9
|
|
|
7
|
-
|
|
10
|
+
**ProjectCLI** is an interactive, cross-language project generator. Instead of remembering usage for `create-react-app`, `cargo new`, `poetry new`, `laravel new`, `rails new`, etc., just run `projectcli`.
|
|
8
11
|
|
|
9
|
-
|
|
12
|
+
We handle the complexity of calling the official CLIs for you.
|
|
13
|
+
|
|
14
|
+
## โจ Features
|
|
15
|
+
|
|
16
|
+
- **Multi-Language Support**: Rust, Go, Python, JavaScript, TypeScript, PHP, Java, C#, Ruby, Swift, Dart.
|
|
17
|
+
- **Unified Interface**: One interactive wizard to rule them all.
|
|
18
|
+
- **Smart Context Awareness**: Running `projectcli` inside an existing project automatically offers to add libraries, CI/CD, or Dockerfiles tailored to that language.
|
|
19
|
+
- **Preflight Checks**: Warns you if you are missing required tools (e.g. `cargo`, `go`, `node`) before you start.
|
|
20
|
+
- **Remote Templates**: Clone any GitHub repository and automatically strip `.git` history for a fresh start.
|
|
21
|
+
- **CI/CD & Docker**: One-click generation of GitHub Actions workflows and Dockerfiles.
|
|
22
|
+
|
|
23
|
+
## ๐ Quick Start
|
|
24
|
+
|
|
25
|
+
Run instantly with `npx`:
|
|
10
26
|
|
|
11
27
|
```bash
|
|
12
28
|
npx @dawitworku/projectcli@latest
|
|
13
29
|
```
|
|
14
30
|
|
|
15
|
-
|
|
31
|
+
Or install globally:
|
|
16
32
|
|
|
17
33
|
```bash
|
|
18
|
-
|
|
34
|
+
npm install -g @dawitworku/projectcli
|
|
35
|
+
projectcli
|
|
19
36
|
```
|
|
20
37
|
|
|
21
|
-
##
|
|
38
|
+
## ๐ฎ Interactive Mode
|
|
39
|
+
|
|
40
|
+
Just run `projectcli` and follow the prompts:
|
|
41
|
+
|
|
42
|
+
1. Select **Language** (fuzzy search supported).
|
|
43
|
+
2. Select **Framework** (React, Vue, Next.js, Actix, Axum, Django, FastAPI, etc.).
|
|
44
|
+
3. Choose **Project Name**.
|
|
45
|
+
4. (Optional) Add **CI/CD** or **Docker**.
|
|
46
|
+
|
|
47
|
+
## ๐ Advanced Usage
|
|
48
|
+
|
|
49
|
+
### Context Awareness
|
|
50
|
+
|
|
51
|
+
Run it inside a project to detect the language and offer relevant tools:
|
|
22
52
|
|
|
23
53
|
```bash
|
|
24
|
-
|
|
25
|
-
|
|
54
|
+
cd my-rust-project
|
|
55
|
+
projectcli
|
|
56
|
+
# Output: "โ Detected active Rust project"
|
|
57
|
+
# Options: [Add GitHub Actions CI], [Add Dockerfile], [Add Dependencies]
|
|
26
58
|
```
|
|
27
59
|
|
|
28
|
-
|
|
60
|
+
### Remote Templates
|
|
29
61
|
|
|
30
|
-
|
|
62
|
+
Clone a starter kit from GitHub and make it your own instantly:
|
|
31
63
|
|
|
32
64
|
```bash
|
|
33
|
-
|
|
34
|
-
npm link
|
|
65
|
+
projectcli --template https://github.com/example/starter-repo --name my-app
|
|
35
66
|
```
|
|
36
67
|
|
|
37
|
-
|
|
68
|
+
### Automation / CI Use
|
|
69
|
+
|
|
70
|
+
Skip the interactive prompts for scripts or specialized workflows:
|
|
38
71
|
|
|
39
72
|
```bash
|
|
40
|
-
projectcli
|
|
73
|
+
projectcli --language Rust --framework Actix --name my-api --ci --docker --yes
|
|
41
74
|
```
|
|
42
75
|
|
|
43
|
-
|
|
76
|
+
### Configuration
|
|
44
77
|
|
|
45
|
-
|
|
78
|
+
Save your preferences (like default package manager):
|
|
46
79
|
|
|
47
80
|
```bash
|
|
48
|
-
projectcli
|
|
81
|
+
projectcli config
|
|
49
82
|
```
|
|
50
83
|
|
|
51
|
-
##
|
|
84
|
+
## ๐ฆ Supported Generators (Partial List)
|
|
52
85
|
|
|
53
|
-
|
|
54
|
-
|
|
86
|
+
| Language | Frameworks / Tools |
|
|
87
|
+
| ------------------------- | ------------------------------------------------------------- |
|
|
88
|
+
| **JavaScript/TypeScript** | React (Vite), Vue, Next.js, NestJS, Express, Astro, Svelte... |
|
|
89
|
+
| **Rust** | Binary, Library, Actix Web, Axum, Rocket, Taurus... |
|
|
90
|
+
| **Python** | Poetry, Setuptools, Django, Flask, FastAPI... |
|
|
91
|
+
| **Go** | Binary, Fiber, Gin, Chi, Echo... |
|
|
92
|
+
| **PHP** | Laravel, Symfony, Slim... |
|
|
93
|
+
| **Java/Kotlin** | Spring Boot, Gradle/Maven... |
|
|
94
|
+
| **...and more** | C#, Ruby, Swift, Dart |
|
|
55
95
|
|
|
56
|
-
##
|
|
96
|
+
## ๐ค Contributing
|
|
57
97
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
projectcli --dry-run --language "JavaScript" --framework "Vite (React)" --name demo --pm pnpm
|
|
66
|
-
```
|
|
98
|
+
We love contributions! Whether it's adding a new framework to the registry or fixing a bug.
|
|
99
|
+
|
|
100
|
+
1. Fork it.
|
|
101
|
+
2. Create your feature branch (`git checkout -b feature/new-framework`).
|
|
102
|
+
3. Commit your changes (`git commit -am 'Add support for X'`).
|
|
103
|
+
4. Push to the branch (`git push origin feature/new-framework`).
|
|
104
|
+
5. Create a new Pull Request.
|
|
67
105
|
|
|
68
|
-
|
|
106
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for more details.
|
|
69
107
|
|
|
70
|
-
|
|
71
|
-
- Choose framework
|
|
72
|
-
- Enter project name
|
|
73
|
-
- CLI runs the underlying generator commands (npm/cargo/django-admin/etc.)
|
|
108
|
+
## ๐ License
|
|
74
109
|
|
|
75
|
-
|
|
110
|
+
MIT ยฉ [Dawit Worku](https://github.com/dawitworku)
|
package/package.json
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dawitworku/projectcli",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "The ultimate interactive project generator (language -> framework -> create).",
|
|
5
|
+
"author": "Dawit Worku",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/dawitworku/projectcli"
|
|
9
|
+
},
|
|
10
|
+
"bugs": {
|
|
11
|
+
"url": "https://github.com/dawitworku/projectcli/issues"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://github.com/dawitworku/projectcli#readme",
|
|
5
14
|
"license": "MIT",
|
|
6
15
|
"type": "commonjs",
|
|
7
16
|
"bin": {
|
package/src/config.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const fs = require("node:fs");
|
|
2
|
+
const path = require("node:path");
|
|
3
|
+
const os = require("node:os");
|
|
4
|
+
|
|
5
|
+
const CONFIG_PATH = path.join(os.homedir(), ".projectcli.json");
|
|
6
|
+
|
|
7
|
+
function loadConfig() {
|
|
8
|
+
try {
|
|
9
|
+
if (fs.existsSync(CONFIG_PATH)) {
|
|
10
|
+
const content = fs.readFileSync(CONFIG_PATH, "utf8");
|
|
11
|
+
return JSON.parse(content);
|
|
12
|
+
}
|
|
13
|
+
} catch (err) {
|
|
14
|
+
// ignore
|
|
15
|
+
}
|
|
16
|
+
return {};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function saveConfig(data) {
|
|
20
|
+
try {
|
|
21
|
+
const current = loadConfig();
|
|
22
|
+
const updated = { ...current, ...data };
|
|
23
|
+
fs.writeFileSync(CONFIG_PATH, JSON.stringify(updated, null, 2));
|
|
24
|
+
return true;
|
|
25
|
+
} catch (err) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
module.exports = {
|
|
31
|
+
loadConfig,
|
|
32
|
+
saveConfig,
|
|
33
|
+
CONFIG_PATH,
|
|
34
|
+
};
|
package/src/index.js
CHANGED
|
@@ -28,8 +28,13 @@ try {
|
|
|
28
28
|
const { getLanguages, getFrameworks, getGenerator } = require("./registry");
|
|
29
29
|
const { runSteps } = require("./run");
|
|
30
30
|
const { runAdd } = require("./add");
|
|
31
|
+
const { checkBinaries } = require("./preflight");
|
|
32
|
+
const { gitClone, removeGitFolder } = require("./remote");
|
|
31
33
|
const { generateCI, generateDocker } = require("./cicd");
|
|
32
34
|
const { getDescription } = require("./descriptions");
|
|
35
|
+
const { detectLanguage, detectPackageManager } = require("./detect");
|
|
36
|
+
const { loadConfig } = require("./config");
|
|
37
|
+
const { runConfig } = require("./settings");
|
|
33
38
|
|
|
34
39
|
const RUST_KEYWORDS = new Set(
|
|
35
40
|
[
|
|
@@ -134,6 +139,7 @@ function parseArgs(argv) {
|
|
|
134
139
|
ci: false,
|
|
135
140
|
docker: false,
|
|
136
141
|
learning: false,
|
|
142
|
+
template: undefined,
|
|
137
143
|
};
|
|
138
144
|
|
|
139
145
|
const nextValue = (i) => {
|
|
@@ -151,7 +157,12 @@ function parseArgs(argv) {
|
|
|
151
157
|
else if (a === "--ci") out.ci = true;
|
|
152
158
|
else if (a === "--docker") out.docker = true;
|
|
153
159
|
else if (a === "--learning") out.learning = true;
|
|
154
|
-
else if (a.startsWith("--
|
|
160
|
+
else if (a.startsWith("--template="))
|
|
161
|
+
out.template = a.slice("--template=".length);
|
|
162
|
+
else if (a === "--template") {
|
|
163
|
+
out.template = nextValue(i);
|
|
164
|
+
i++;
|
|
165
|
+
} else if (a.startsWith("--language="))
|
|
155
166
|
out.language = a.slice("--language=".length);
|
|
156
167
|
else if (a === "--language") {
|
|
157
168
|
out.language = nextValue(i);
|
|
@@ -197,6 +208,7 @@ function printHelp() {
|
|
|
197
208
|
console.log(
|
|
198
209
|
" projectcli --language <lang> --framework <fw> --name <project>"
|
|
199
210
|
);
|
|
211
|
+
console.log(" projectcli config # configure defaults");
|
|
200
212
|
console.log("");
|
|
201
213
|
console.log("Flags:");
|
|
202
214
|
console.log(" --help, -h Show help");
|
|
@@ -213,9 +225,11 @@ function printHelp() {
|
|
|
213
225
|
console.log(" --ci Auto-add GitHub Actions CI");
|
|
214
226
|
console.log(" --docker Auto-add Dockerfile");
|
|
215
227
|
console.log(" --learning Enable learning mode (shows descriptions)");
|
|
228
|
+
console.log(" --template Clone from a Git repository URL");
|
|
216
229
|
}
|
|
217
230
|
|
|
218
231
|
const BACK = "__back__";
|
|
232
|
+
const COMMUNITY = "Community / Remote";
|
|
219
233
|
|
|
220
234
|
function withBack(choices) {
|
|
221
235
|
return [
|
|
@@ -310,6 +324,87 @@ async function main(options = {}) {
|
|
|
310
324
|
return;
|
|
311
325
|
}
|
|
312
326
|
|
|
327
|
+
if (cmd === "config") {
|
|
328
|
+
await runConfig({ prompt });
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Smart Context Detection
|
|
333
|
+
if (
|
|
334
|
+
cmd === "init" &&
|
|
335
|
+
rest.length === 0 &&
|
|
336
|
+
!args.language &&
|
|
337
|
+
!args.framework &&
|
|
338
|
+
!args.template &&
|
|
339
|
+
!args.name
|
|
340
|
+
) {
|
|
341
|
+
const detected = detectLanguage(process.cwd());
|
|
342
|
+
if (detected && detected !== "Unknown") {
|
|
343
|
+
console.clear();
|
|
344
|
+
console.log(
|
|
345
|
+
gradient.pastel.multiline(
|
|
346
|
+
figlet.textSync("ProjectCLI", { font: "Standard" })
|
|
347
|
+
)
|
|
348
|
+
);
|
|
349
|
+
console.log(chalk.bold.magenta(" The Swiss Army Knife for Developers"));
|
|
350
|
+
console.log("");
|
|
351
|
+
console.log(
|
|
352
|
+
chalk.green(`โ Detected active ${chalk.bold(detected)} project.`)
|
|
353
|
+
);
|
|
354
|
+
|
|
355
|
+
const { action } = await prompt([
|
|
356
|
+
{
|
|
357
|
+
type: "list",
|
|
358
|
+
name: "action",
|
|
359
|
+
message: "What would you like to do?",
|
|
360
|
+
choices: [
|
|
361
|
+
{ name: "Add Library / Dependency", value: "add" },
|
|
362
|
+
{ name: "Add GitHub Actions CI", value: "ci" },
|
|
363
|
+
{ name: "Add Dockerfile", value: "docker" },
|
|
364
|
+
new inquirer.Separator(),
|
|
365
|
+
{ name: "Start New Project Here", value: "new" },
|
|
366
|
+
{ name: "Exit", value: "exit" },
|
|
367
|
+
],
|
|
368
|
+
},
|
|
369
|
+
]);
|
|
370
|
+
|
|
371
|
+
if (action === "exit") process.exit(0);
|
|
372
|
+
|
|
373
|
+
if (action === "add") {
|
|
374
|
+
// reuse the add logic, passing empty args so it detects locally
|
|
375
|
+
await runAdd({ prompt, argv: [] });
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (action === "ci" || action === "docker") {
|
|
380
|
+
const pm = detectPackageManager(process.cwd());
|
|
381
|
+
let langArg = detected;
|
|
382
|
+
if (detected === "JavaScript/TypeScript") langArg = "JavaScript";
|
|
383
|
+
if (detected === "Java/Kotlin") langArg = "Java";
|
|
384
|
+
|
|
385
|
+
const steps =
|
|
386
|
+
action === "ci"
|
|
387
|
+
? generateCI(process.cwd(), langArg, pm)
|
|
388
|
+
: generateDocker(process.cwd(), langArg);
|
|
389
|
+
|
|
390
|
+
if (steps.length > 0) {
|
|
391
|
+
console.log("\nApplying changes...");
|
|
392
|
+
await runSteps(steps, { projectRoot: process.cwd() });
|
|
393
|
+
console.log(chalk.green("Done!"));
|
|
394
|
+
} else {
|
|
395
|
+
console.log(
|
|
396
|
+
chalk.yellow(
|
|
397
|
+
"No standard template available for this language yet."
|
|
398
|
+
)
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// If 'new', fall through to normal wizard
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
313
408
|
// Clear console for a fresh start
|
|
314
409
|
console.clear();
|
|
315
410
|
|
|
@@ -332,13 +427,26 @@ async function main(options = {}) {
|
|
|
332
427
|
throw new Error("No languages configured.");
|
|
333
428
|
}
|
|
334
429
|
|
|
430
|
+
const userConfig = loadConfig();
|
|
431
|
+
|
|
335
432
|
const allowedPms = ["npm", "pnpm", "yarn", "bun"];
|
|
336
|
-
|
|
433
|
+
let preselectedPm =
|
|
337
434
|
typeof args.pm === "string" && allowedPms.includes(args.pm)
|
|
338
435
|
? args.pm
|
|
339
436
|
: undefined;
|
|
340
437
|
|
|
438
|
+
if (!preselectedPm && userConfig.packageManager) {
|
|
439
|
+
if (allowedPms.includes(userConfig.packageManager)) {
|
|
440
|
+
preselectedPm = userConfig.packageManager;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
if (userConfig.learningMode && args.learning === false) {
|
|
445
|
+
args.learning = true;
|
|
446
|
+
}
|
|
447
|
+
|
|
341
448
|
const state = {
|
|
449
|
+
template: args.template || undefined,
|
|
342
450
|
language:
|
|
343
451
|
args.language && languages.includes(args.language)
|
|
344
452
|
? args.language
|
|
@@ -360,7 +468,10 @@ async function main(options = {}) {
|
|
|
360
468
|
state.language === "JavaScript" || state.language === "TypeScript";
|
|
361
469
|
|
|
362
470
|
let step = "language";
|
|
363
|
-
if (
|
|
471
|
+
if (state.template) {
|
|
472
|
+
if (!state.name) step = "name";
|
|
473
|
+
else step = "confirm";
|
|
474
|
+
} else if (!state.language) step = "language";
|
|
364
475
|
else if (!state.framework) step = "framework";
|
|
365
476
|
else if (needsPackageManager && !state.pm) step = "pm";
|
|
366
477
|
else if (!state.name) step = "name";
|
|
@@ -453,6 +564,38 @@ async function main(options = {}) {
|
|
|
453
564
|
}
|
|
454
565
|
|
|
455
566
|
state.framework = answer.framework;
|
|
567
|
+
|
|
568
|
+
// Preflight Checks
|
|
569
|
+
const gen = getGenerator(state.language, state.framework);
|
|
570
|
+
if (gen && gen.check && gen.check.length > 0) {
|
|
571
|
+
/* eslint-disable-next-line no-console */
|
|
572
|
+
console.log(chalk.dim("\n(checking requirements...)"));
|
|
573
|
+
const results = await checkBinaries(gen.check);
|
|
574
|
+
const missing = results.filter((r) => !r.ok);
|
|
575
|
+
|
|
576
|
+
if (missing.length > 0) {
|
|
577
|
+
console.log(chalk.red.bold("\nMissing required tools:"));
|
|
578
|
+
missing.forEach((m) => console.log(chalk.red(` - ${m.bin}`)));
|
|
579
|
+
console.log(
|
|
580
|
+
chalk.yellow("You may not be able to build or run this project.\n")
|
|
581
|
+
);
|
|
582
|
+
|
|
583
|
+
const { proceed } = await prompt([
|
|
584
|
+
{
|
|
585
|
+
type: "confirm",
|
|
586
|
+
name: "proceed",
|
|
587
|
+
message: "Continue anyway?",
|
|
588
|
+
default: false,
|
|
589
|
+
},
|
|
590
|
+
]);
|
|
591
|
+
|
|
592
|
+
if (!proceed) {
|
|
593
|
+
step = "framework";
|
|
594
|
+
continue;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
|
|
456
599
|
step = "pm";
|
|
457
600
|
continue;
|
|
458
601
|
}
|
|
@@ -528,6 +671,10 @@ async function main(options = {}) {
|
|
|
528
671
|
|
|
529
672
|
const v = String(projectName || "").trim();
|
|
530
673
|
if (v.toLowerCase() === "back") {
|
|
674
|
+
if (state.template) {
|
|
675
|
+
console.log("Operation cancelled.");
|
|
676
|
+
process.exit(0);
|
|
677
|
+
}
|
|
531
678
|
step =
|
|
532
679
|
state.language === "JavaScript" || state.language === "TypeScript"
|
|
533
680
|
? "pm"
|
|
@@ -541,13 +688,75 @@ async function main(options = {}) {
|
|
|
541
688
|
}
|
|
542
689
|
|
|
543
690
|
if (step === "confirm") {
|
|
691
|
+
const projectRoot = path.resolve(process.cwd(), state.name);
|
|
692
|
+
const targetExists = fs.existsSync(projectRoot);
|
|
693
|
+
|
|
694
|
+
if (state.template) {
|
|
695
|
+
if (targetExists && !args.dryRun) {
|
|
696
|
+
console.error(
|
|
697
|
+
`\nError: Target folder already exists: ${projectRoot}`
|
|
698
|
+
);
|
|
699
|
+
state.name = undefined;
|
|
700
|
+
step = "name";
|
|
701
|
+
continue;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
console.log("\nProject Configuration:");
|
|
705
|
+
console.log(` Template: ${state.template}`);
|
|
706
|
+
console.log(` Folder: ${state.name}`);
|
|
707
|
+
console.log("");
|
|
708
|
+
|
|
709
|
+
if (!args.yes) {
|
|
710
|
+
const { action } = await prompt([
|
|
711
|
+
{
|
|
712
|
+
type: "list",
|
|
713
|
+
name: "action",
|
|
714
|
+
message: "Clone this template?",
|
|
715
|
+
choices: [
|
|
716
|
+
{ name: "Clone template", value: "create" },
|
|
717
|
+
{ name: "Cancel", value: "cancel" },
|
|
718
|
+
],
|
|
719
|
+
},
|
|
720
|
+
]);
|
|
721
|
+
if (action === "cancel") {
|
|
722
|
+
console.log("Aborted.");
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
if (args.dryRun) {
|
|
728
|
+
console.log(
|
|
729
|
+
`[Dry Run] Would clone ${state.template} to ${projectRoot}`
|
|
730
|
+
);
|
|
731
|
+
return;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
console.log(chalk.dim("\nCloning repository..."));
|
|
735
|
+
try {
|
|
736
|
+
await gitClone(state.template, projectRoot);
|
|
737
|
+
// Remove .git to make it a fresh project
|
|
738
|
+
removeGitFolder(projectRoot);
|
|
739
|
+
|
|
740
|
+
console.log(
|
|
741
|
+
chalk.green(`\nSuccess! Created project at ${projectRoot}`)
|
|
742
|
+
);
|
|
743
|
+
console.log(
|
|
744
|
+
chalk.dim(
|
|
745
|
+
"You may need to run 'npm install' or similar inside the folder."
|
|
746
|
+
)
|
|
747
|
+
);
|
|
748
|
+
} catch (err) {
|
|
749
|
+
console.error(chalk.red("\nFailed to clone template:"), err.message);
|
|
750
|
+
process.exit(1);
|
|
751
|
+
}
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
|
|
544
755
|
const generator = getGenerator(state.language, state.framework);
|
|
545
756
|
if (!generator) {
|
|
546
757
|
throw new Error("Generator not found (registry mismatch).");
|
|
547
758
|
}
|
|
548
759
|
|
|
549
|
-
const projectRoot = path.resolve(process.cwd(), state.name);
|
|
550
|
-
const targetExists = fs.existsSync(projectRoot);
|
|
551
760
|
if (targetExists && !args.dryRun) {
|
|
552
761
|
console.error(`\nError: Target folder already exists: ${projectRoot}`);
|
|
553
762
|
state.name = undefined;
|
package/src/preflight.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const { spawn } = require("node:child_process");
|
|
2
|
+
|
|
3
|
+
function checkBinary(binary, args = ["--version"]) {
|
|
4
|
+
return new Promise((resolve) => {
|
|
5
|
+
const child = spawn(binary, args, {
|
|
6
|
+
stdio: "ignore",
|
|
7
|
+
shell: false,
|
|
8
|
+
});
|
|
9
|
+
child.on("error", () => resolve(false));
|
|
10
|
+
child.on("close", (code) => resolve(code === 0));
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function checkBinaries(binaries) {
|
|
15
|
+
if (!binaries || binaries.length === 0) return [];
|
|
16
|
+
const checks = binaries.map((bin) =>
|
|
17
|
+
checkBinary(bin).then((ok) => ({ bin, ok }))
|
|
18
|
+
);
|
|
19
|
+
return Promise.all(checks);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
module.exports = {
|
|
23
|
+
checkBinary,
|
|
24
|
+
checkBinaries,
|
|
25
|
+
};
|
package/src/registry.js
CHANGED
|
@@ -33,6 +33,7 @@ const REGISTRY = {
|
|
|
33
33
|
"Vite (Vanilla)": {
|
|
34
34
|
id: "js.vite.vanilla",
|
|
35
35
|
label: "Vite (Vanilla)",
|
|
36
|
+
check: ["npm"],
|
|
36
37
|
commands: (ctx) => {
|
|
37
38
|
const projectName = getProjectName(ctx);
|
|
38
39
|
const pm = getPackageManager(ctx);
|
|
@@ -409,6 +410,7 @@ const REGISTRY = {
|
|
|
409
410
|
Django: {
|
|
410
411
|
id: "py.django",
|
|
411
412
|
label: "Django",
|
|
413
|
+
check: ["django-admin"],
|
|
412
414
|
commands: (ctx) => {
|
|
413
415
|
const projectName = getProjectName(ctx);
|
|
414
416
|
return [
|
|
@@ -423,6 +425,7 @@ const REGISTRY = {
|
|
|
423
425
|
Flask: {
|
|
424
426
|
id: "py.flask.basic",
|
|
425
427
|
label: "Flask (basic)",
|
|
428
|
+
check: ["python3"],
|
|
426
429
|
commands: (_ctx) => [
|
|
427
430
|
{ type: "mkdir", path: "." },
|
|
428
431
|
{
|
|
@@ -502,6 +505,7 @@ const REGISTRY = {
|
|
|
502
505
|
"Cargo (bin)": {
|
|
503
506
|
id: "rs.cargo.bin",
|
|
504
507
|
label: "Cargo (bin)",
|
|
508
|
+
check: ["cargo"],
|
|
505
509
|
commands: (ctx) => {
|
|
506
510
|
const projectName = getProjectName(ctx);
|
|
507
511
|
return [
|
|
@@ -516,6 +520,7 @@ const REGISTRY = {
|
|
|
516
520
|
"Cargo (lib)": {
|
|
517
521
|
id: "rs.cargo.lib",
|
|
518
522
|
label: "Cargo (lib)",
|
|
523
|
+
check: ["cargo"],
|
|
519
524
|
commands: (ctx) => {
|
|
520
525
|
const projectName = getProjectName(ctx);
|
|
521
526
|
return [
|
|
@@ -558,6 +563,7 @@ const REGISTRY = {
|
|
|
558
563
|
"Go module (basic)": {
|
|
559
564
|
id: "go.module.basic",
|
|
560
565
|
label: "Go module (basic)",
|
|
566
|
+
check: ["go"],
|
|
561
567
|
commands: (ctx) => {
|
|
562
568
|
const projectName = getProjectName(ctx);
|
|
563
569
|
return [
|
package/src/remote.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const { spawn } = require("node:child_process");
|
|
2
|
+
|
|
3
|
+
function gitClone(url, targetDir) {
|
|
4
|
+
return new Promise((resolve, reject) => {
|
|
5
|
+
// git clone --depth 1 url targetDir
|
|
6
|
+
const child = spawn("git", ["clone", "--depth", "1", url, targetDir], {
|
|
7
|
+
stdio: "inherit",
|
|
8
|
+
shell: false,
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
child.on("error", (err) => reject(err));
|
|
12
|
+
child.on("close", (code) => {
|
|
13
|
+
if (code === 0) resolve();
|
|
14
|
+
else reject(new Error(`Git clone failed with code ${code}`));
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Removes the .git folder from the target directory so it becomes a fresh project.
|
|
21
|
+
*/
|
|
22
|
+
function removeGitFolder(targetDir) {
|
|
23
|
+
const fs = require("node:fs");
|
|
24
|
+
const path = require("node:path");
|
|
25
|
+
const gitPath = path.join(targetDir, ".git");
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
if (fs.existsSync(gitPath)) {
|
|
29
|
+
fs.rmSync(gitPath, { recursive: true, force: true });
|
|
30
|
+
}
|
|
31
|
+
} catch (err) {
|
|
32
|
+
// ignore
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
module.exports = {
|
|
37
|
+
gitClone,
|
|
38
|
+
removeGitFolder,
|
|
39
|
+
};
|
package/src/settings.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
const chalk = require("chalk");
|
|
2
|
+
const { loadConfig, saveConfig, CONFIG_PATH } = require("./config");
|
|
3
|
+
|
|
4
|
+
async function runConfig({ prompt }) {
|
|
5
|
+
const config = loadConfig();
|
|
6
|
+
|
|
7
|
+
console.log(chalk.bold.cyan("\nConfiguration Settings"));
|
|
8
|
+
console.log(chalk.dim(`File: ${CONFIG_PATH}\n`));
|
|
9
|
+
|
|
10
|
+
const answers = await prompt([
|
|
11
|
+
{
|
|
12
|
+
type: "list",
|
|
13
|
+
name: "packageManager",
|
|
14
|
+
message: "Default Package Manager (for JS/TS):",
|
|
15
|
+
choices: [
|
|
16
|
+
{ name: "None (Always ask)", value: null },
|
|
17
|
+
new inquirerSeparator(),
|
|
18
|
+
{ name: "npm", value: "npm" },
|
|
19
|
+
{ name: "pnpm", value: "pnpm" },
|
|
20
|
+
{ name: "yarn", value: "yarn" },
|
|
21
|
+
{ name: "bun", value: "bun" },
|
|
22
|
+
],
|
|
23
|
+
default: config.packageManager || null,
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
type: "confirm",
|
|
27
|
+
name: "learningMode",
|
|
28
|
+
message: "Enable Learning Mode by default?",
|
|
29
|
+
default: config.learningMode || false,
|
|
30
|
+
},
|
|
31
|
+
]);
|
|
32
|
+
|
|
33
|
+
saveConfig(answers);
|
|
34
|
+
console.log(chalk.green("\nโ Settings saved!"));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Helper for pure inquirer usage if needed,
|
|
38
|
+
// though index.js passes the prompt instance.
|
|
39
|
+
function inquirerSeparator() {
|
|
40
|
+
// We can't easily import inquirer.Separator here without adding dependency
|
|
41
|
+
// or passing it in. Let's just use a string for now or skip it.
|
|
42
|
+
return { name: "โโโโโโโโโโโโโโ", disabled: true };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
module.exports = { runConfig };
|