@dawitworku/projectcli 0.2.0 โ 0.2.2
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 +81 -38
- package/package.json +12 -3
- package/src/config.js +34 -0
- package/src/devcontainer.js +82 -0
- package/src/index.js +441 -8
- package/src/license.js +80 -0
- package/src/preflight.js +25 -0
- package/src/registry.js +6 -0
- package/src/remote.js +39 -0
- package/src/settings.js +63 -0
package/README.md
CHANGED
|
@@ -1,75 +1,118 @@
|
|
|
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
|
+
- **Dev Containers**: Generate `.devcontainer/devcontainer.json` for VS Code / Codespaces.
|
|
23
|
+
- **License Generator**: Add a standard `LICENSE` file (configurable default).
|
|
24
|
+
|
|
25
|
+
## ๐ Quick Start
|
|
26
|
+
|
|
27
|
+
Run instantly with `npx`:
|
|
10
28
|
|
|
11
29
|
```bash
|
|
12
30
|
npx @dawitworku/projectcli@latest
|
|
13
31
|
```
|
|
14
32
|
|
|
15
|
-
|
|
33
|
+
Or install globally:
|
|
16
34
|
|
|
17
35
|
```bash
|
|
18
|
-
|
|
36
|
+
npm install -g @dawitworku/projectcli
|
|
37
|
+
projectcli
|
|
19
38
|
```
|
|
20
39
|
|
|
21
|
-
##
|
|
40
|
+
## ๐ฎ Interactive Mode
|
|
41
|
+
|
|
42
|
+
Just run `projectcli` and follow the prompts:
|
|
43
|
+
|
|
44
|
+
1. Select **Language** (fuzzy search supported).
|
|
45
|
+
2. Select **Framework** (React, Vue, Next.js, Actix, Axum, Django, FastAPI, etc.).
|
|
46
|
+
3. Choose **Project Name**.
|
|
47
|
+
4. (Optional) Add **CI/CD** or **Docker**.
|
|
48
|
+
|
|
49
|
+
## ๐ Advanced Usage
|
|
50
|
+
|
|
51
|
+
### Context Awareness
|
|
52
|
+
|
|
53
|
+
Run it inside a project to detect the language and offer relevant tools:
|
|
22
54
|
|
|
23
55
|
```bash
|
|
24
|
-
|
|
25
|
-
|
|
56
|
+
cd my-rust-project
|
|
57
|
+
projectcli
|
|
58
|
+
# Output: "โ Detected active Rust project"
|
|
59
|
+
# Options: [Add GitHub Actions CI], [Add Dockerfile], [Add Dependencies]
|
|
26
60
|
```
|
|
27
61
|
|
|
28
|
-
|
|
62
|
+
### Remote Templates
|
|
29
63
|
|
|
30
|
-
|
|
64
|
+
Clone a starter kit from GitHub and make it your own instantly:
|
|
31
65
|
|
|
32
66
|
```bash
|
|
33
|
-
|
|
34
|
-
npm link
|
|
67
|
+
projectcli --template https://github.com/example/starter-repo --name my-app
|
|
35
68
|
```
|
|
36
69
|
|
|
37
|
-
|
|
70
|
+
### Automation / CI Use
|
|
71
|
+
|
|
72
|
+
Skip the interactive prompts for scripts or specialized workflows:
|
|
38
73
|
|
|
39
74
|
```bash
|
|
40
|
-
projectcli
|
|
75
|
+
projectcli --language Rust --framework Actix --name my-api --ci --docker --yes
|
|
41
76
|
```
|
|
42
77
|
|
|
43
|
-
|
|
78
|
+
### Configuration
|
|
44
79
|
|
|
45
|
-
|
|
80
|
+
Save your preferences (like default package manager):
|
|
46
81
|
|
|
47
82
|
```bash
|
|
48
|
-
projectcli
|
|
83
|
+
projectcli config
|
|
49
84
|
```
|
|
50
85
|
|
|
51
|
-
|
|
86
|
+
You can set defaults like:
|
|
52
87
|
|
|
53
|
-
-
|
|
54
|
-
-
|
|
88
|
+
- JS/TS package manager
|
|
89
|
+
- Author name (for LICENSE)
|
|
90
|
+
- Default license type (MIT/Apache2/ISC)
|
|
55
91
|
|
|
56
|
-
##
|
|
92
|
+
## ๐ฆ Supported Generators (Partial List)
|
|
57
93
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
94
|
+
| Language | Frameworks / Tools |
|
|
95
|
+
| ------------------------- | ------------------------------------------------------------- |
|
|
96
|
+
| **JavaScript/TypeScript** | React (Vite), Vue, Next.js, NestJS, Express, Astro, Svelte... |
|
|
97
|
+
| **Rust** | Binary, Library, Actix Web, Axum, Rocket, Taurus... |
|
|
98
|
+
| **Python** | Poetry, Setuptools, Django, Flask, FastAPI... |
|
|
99
|
+
| **Go** | Binary, Fiber, Gin, Chi, Echo... |
|
|
100
|
+
| **PHP** | Laravel, Symfony, Slim... |
|
|
101
|
+
| **Java/Kotlin** | Spring Boot, Gradle/Maven... |
|
|
102
|
+
| **...and more** | C#, Ruby, Swift, Dart |
|
|
103
|
+
|
|
104
|
+
## ๐ค Contributing
|
|
105
|
+
|
|
106
|
+
We love contributions! Whether it's adding a new framework to the registry or fixing a bug.
|
|
107
|
+
|
|
108
|
+
1. Fork it.
|
|
109
|
+
2. Create your feature branch (`git checkout -b feature/new-framework`).
|
|
110
|
+
3. Commit your changes (`git commit -am 'Add support for X'`).
|
|
111
|
+
4. Push to the branch (`git push origin feature/new-framework`).
|
|
112
|
+
5. Create a new Pull Request.
|
|
67
113
|
|
|
68
|
-
|
|
114
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for more details.
|
|
69
115
|
|
|
70
|
-
|
|
71
|
-
- Choose framework
|
|
72
|
-
- Enter project name
|
|
73
|
-
- CLI runs the underlying generator commands (npm/cargo/django-admin/etc.)
|
|
116
|
+
## ๐ License
|
|
74
117
|
|
|
75
|
-
|
|
118
|
+
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.2",
|
|
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": {
|
|
@@ -23,7 +32,7 @@
|
|
|
23
32
|
],
|
|
24
33
|
"scripts": {
|
|
25
34
|
"start": "node bin/projectcli.js",
|
|
26
|
-
"lint": "node -c bin/projectcli.js && node -c src/index.js && node -c src/registry.js && node -c src/run.js"
|
|
35
|
+
"lint": "node -c bin/projectcli.js && node -c src/index.js && node -c src/registry.js && node -c src/run.js && node -c src/settings.js && node -c src/config.js && node -c src/devcontainer.js && node -c src/license.js"
|
|
27
36
|
},
|
|
28
37
|
"dependencies": {
|
|
29
38
|
"boxen": "^5.1.2",
|
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
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
const DEVCONTAINER_NODE = `{
|
|
2
|
+
"name": "Node.js",
|
|
3
|
+
"image": "mcr.microsoft.com/devcontainers/javascript-node:20",
|
|
4
|
+
"features": {
|
|
5
|
+
"ghcr.io/devcontainers/features/node:1": {}
|
|
6
|
+
},
|
|
7
|
+
"forwardPorts": [3000],
|
|
8
|
+
"postCreateCommand": "npm install"
|
|
9
|
+
}`;
|
|
10
|
+
|
|
11
|
+
const DEVCONTAINER_PYTHON = `{
|
|
12
|
+
"name": "Python 3",
|
|
13
|
+
"image": "mcr.microsoft.com/devcontainers/python:3.11",
|
|
14
|
+
"features": {
|
|
15
|
+
"ghcr.io/devcontainers/features/python:1": {}
|
|
16
|
+
},
|
|
17
|
+
"postCreateCommand": "pip install -r requirements.txt"
|
|
18
|
+
}`;
|
|
19
|
+
|
|
20
|
+
const DEVCONTAINER_RUST = `{
|
|
21
|
+
"name": "Rust",
|
|
22
|
+
"image": "mcr.microsoft.com/devcontainers/rust:1",
|
|
23
|
+
"features": {
|
|
24
|
+
"ghcr.io/devcontainers/features/rust:1": {}
|
|
25
|
+
},
|
|
26
|
+
"customizations": {
|
|
27
|
+
"vscode": {
|
|
28
|
+
"extensions": ["rust-lang.rust-analyzer", "tamasfe.even-better-toml"]
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"postCreateCommand": "cargo build"
|
|
32
|
+
}`;
|
|
33
|
+
|
|
34
|
+
const DEVCONTAINER_GO = `{
|
|
35
|
+
"name": "Go",
|
|
36
|
+
"image": "mcr.microsoft.com/devcontainers/go:1.21",
|
|
37
|
+
"features": {
|
|
38
|
+
"ghcr.io/devcontainers/features/go:1": {}
|
|
39
|
+
},
|
|
40
|
+
"customizations": {
|
|
41
|
+
"vscode": {
|
|
42
|
+
"extensions": ["golang.Go"]
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"postCreateCommand": "go mod download"
|
|
46
|
+
}`;
|
|
47
|
+
|
|
48
|
+
function getDevContainer(language) {
|
|
49
|
+
if (language === "JavaScript" || language === "TypeScript")
|
|
50
|
+
return DEVCONTAINER_NODE;
|
|
51
|
+
if (language === "Python") return DEVCONTAINER_PYTHON;
|
|
52
|
+
if (language === "Rust") return DEVCONTAINER_RUST;
|
|
53
|
+
if (language === "Go") return DEVCONTAINER_GO;
|
|
54
|
+
|
|
55
|
+
// Generic fallback
|
|
56
|
+
return `{
|
|
57
|
+
"name": "Default",
|
|
58
|
+
"image": "mcr.microsoft.com/devcontainers/base:ubuntu",
|
|
59
|
+
"features": {
|
|
60
|
+
"ghcr.io/devcontainers/features/common-utils:2": {}
|
|
61
|
+
}
|
|
62
|
+
}`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function generateDevContainer(projectRoot, language) {
|
|
66
|
+
const content = getDevContainer(language);
|
|
67
|
+
return [
|
|
68
|
+
{
|
|
69
|
+
type: "mkdir",
|
|
70
|
+
path: ".devcontainer",
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
type: "writeFile",
|
|
74
|
+
path: ".devcontainer/devcontainer.json",
|
|
75
|
+
content: content,
|
|
76
|
+
},
|
|
77
|
+
];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
module.exports = {
|
|
81
|
+
generateDevContainer,
|
|
82
|
+
};
|
package/src/index.js
CHANGED
|
@@ -28,8 +28,15 @@ 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");
|
|
34
|
+
const { generateDevContainer } = require("./devcontainer");
|
|
35
|
+
const { generateLicense, licenseTypes } = require("./license");
|
|
32
36
|
const { getDescription } = require("./descriptions");
|
|
37
|
+
const { detectLanguage, detectPackageManager } = require("./detect");
|
|
38
|
+
const { loadConfig } = require("./config");
|
|
39
|
+
const { runConfig } = require("./settings");
|
|
33
40
|
|
|
34
41
|
const RUST_KEYWORDS = new Set(
|
|
35
42
|
[
|
|
@@ -133,7 +140,10 @@ function parseArgs(argv) {
|
|
|
133
140
|
pm: undefined,
|
|
134
141
|
ci: false,
|
|
135
142
|
docker: false,
|
|
143
|
+
devcontainer: false,
|
|
144
|
+
license: undefined,
|
|
136
145
|
learning: false,
|
|
146
|
+
template: undefined,
|
|
137
147
|
};
|
|
138
148
|
|
|
139
149
|
const nextValue = (i) => {
|
|
@@ -150,8 +160,16 @@ function parseArgs(argv) {
|
|
|
150
160
|
else if (a === "--dry-run") out.dryRun = true;
|
|
151
161
|
else if (a === "--ci") out.ci = true;
|
|
152
162
|
else if (a === "--docker") out.docker = true;
|
|
163
|
+
else if (a === "--devcontainer") out.devcontainer = true;
|
|
164
|
+
else if (a === "--license") out.license = true;
|
|
165
|
+
else if (a === "--no-license") out.license = false;
|
|
153
166
|
else if (a === "--learning") out.learning = true;
|
|
154
|
-
else if (a.startsWith("--
|
|
167
|
+
else if (a.startsWith("--template="))
|
|
168
|
+
out.template = a.slice("--template=".length);
|
|
169
|
+
else if (a === "--template") {
|
|
170
|
+
out.template = nextValue(i);
|
|
171
|
+
i++;
|
|
172
|
+
} else if (a.startsWith("--language="))
|
|
155
173
|
out.language = a.slice("--language=".length);
|
|
156
174
|
else if (a === "--language") {
|
|
157
175
|
out.language = nextValue(i);
|
|
@@ -197,6 +215,7 @@ function printHelp() {
|
|
|
197
215
|
console.log(
|
|
198
216
|
" projectcli --language <lang> --framework <fw> --name <project>"
|
|
199
217
|
);
|
|
218
|
+
console.log(" projectcli config # configure defaults");
|
|
200
219
|
console.log("");
|
|
201
220
|
console.log("Flags:");
|
|
202
221
|
console.log(" --help, -h Show help");
|
|
@@ -212,10 +231,15 @@ function printHelp() {
|
|
|
212
231
|
);
|
|
213
232
|
console.log(" --ci Auto-add GitHub Actions CI");
|
|
214
233
|
console.log(" --docker Auto-add Dockerfile");
|
|
234
|
+
console.log(" --devcontainer Auto-add VS Code Dev Container");
|
|
235
|
+
console.log(" --license Force-add LICENSE (uses config defaults)");
|
|
236
|
+
console.log(" --no-license Never add LICENSE");
|
|
215
237
|
console.log(" --learning Enable learning mode (shows descriptions)");
|
|
238
|
+
console.log(" --template Clone from a Git repository URL");
|
|
216
239
|
}
|
|
217
240
|
|
|
218
241
|
const BACK = "__back__";
|
|
242
|
+
const COMMUNITY = "Community / Remote";
|
|
219
243
|
|
|
220
244
|
function withBack(choices) {
|
|
221
245
|
return [
|
|
@@ -247,6 +271,22 @@ function printStepsPreview(steps) {
|
|
|
247
271
|
console.log("");
|
|
248
272
|
}
|
|
249
273
|
|
|
274
|
+
function filterExistingWriteFiles(steps, projectRoot) {
|
|
275
|
+
const kept = [];
|
|
276
|
+
const skipped = [];
|
|
277
|
+
for (const step of steps || []) {
|
|
278
|
+
if (step && step.type === "writeFile" && typeof step.path === "string") {
|
|
279
|
+
const target = path.resolve(projectRoot, step.path);
|
|
280
|
+
if (fs.existsSync(target)) {
|
|
281
|
+
skipped.push(step.path);
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
kept.push(step);
|
|
286
|
+
}
|
|
287
|
+
return { kept, skipped };
|
|
288
|
+
}
|
|
289
|
+
|
|
250
290
|
function printList() {
|
|
251
291
|
for (const lang of getLanguages()) {
|
|
252
292
|
console.log(`${lang}:`);
|
|
@@ -310,6 +350,134 @@ async function main(options = {}) {
|
|
|
310
350
|
return;
|
|
311
351
|
}
|
|
312
352
|
|
|
353
|
+
if (cmd === "config") {
|
|
354
|
+
await runConfig({ prompt });
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Smart Context Detection
|
|
359
|
+
if (
|
|
360
|
+
cmd === "init" &&
|
|
361
|
+
rest.length === 0 &&
|
|
362
|
+
!args.language &&
|
|
363
|
+
!args.framework &&
|
|
364
|
+
!args.template &&
|
|
365
|
+
!args.name
|
|
366
|
+
) {
|
|
367
|
+
const detected = detectLanguage(process.cwd());
|
|
368
|
+
if (detected && detected !== "Unknown") {
|
|
369
|
+
console.clear();
|
|
370
|
+
console.log(
|
|
371
|
+
gradient.pastel.multiline(
|
|
372
|
+
figlet.textSync("ProjectCLI", { font: "Standard" })
|
|
373
|
+
)
|
|
374
|
+
);
|
|
375
|
+
console.log(chalk.bold.magenta(" The Swiss Army Knife for Developers"));
|
|
376
|
+
console.log("");
|
|
377
|
+
console.log(
|
|
378
|
+
chalk.green(`โ Detected active ${chalk.bold(detected)} project.`)
|
|
379
|
+
);
|
|
380
|
+
|
|
381
|
+
const { action } = await prompt([
|
|
382
|
+
{
|
|
383
|
+
type: "list",
|
|
384
|
+
name: "action",
|
|
385
|
+
message: "What would you like to do?",
|
|
386
|
+
choices: [
|
|
387
|
+
{ name: "Add Library / Dependency", value: "add" },
|
|
388
|
+
{ name: "Add GitHub Actions CI", value: "ci" },
|
|
389
|
+
{ name: "Add Dockerfile", value: "docker" },
|
|
390
|
+
{ name: "Add Dev Container (VS Code)", value: "devcontainer" },
|
|
391
|
+
{ name: "Add License", value: "license" },
|
|
392
|
+
new inquirer.Separator(),
|
|
393
|
+
{ name: "Start New Project Here", value: "new" },
|
|
394
|
+
{ name: "Exit", value: "exit" },
|
|
395
|
+
],
|
|
396
|
+
},
|
|
397
|
+
]);
|
|
398
|
+
|
|
399
|
+
if (action === "exit") process.exit(0);
|
|
400
|
+
|
|
401
|
+
if (action === "add") {
|
|
402
|
+
// reuse the add logic, passing empty args so it detects locally
|
|
403
|
+
await runAdd({ prompt, argv: [] });
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (action === "ci" || action === "docker" || action === "devcontainer") {
|
|
408
|
+
const pm = detectPackageManager(process.cwd());
|
|
409
|
+
let langArg = detected;
|
|
410
|
+
if (detected === "JavaScript/TypeScript") langArg = "JavaScript";
|
|
411
|
+
if (detected === "Java/Kotlin") langArg = "Java";
|
|
412
|
+
|
|
413
|
+
let steps = [];
|
|
414
|
+
if (action === "ci") steps = generateCI(process.cwd(), langArg, pm);
|
|
415
|
+
else if (action === "docker")
|
|
416
|
+
steps = generateDocker(process.cwd(), langArg);
|
|
417
|
+
else steps = generateDevContainer(process.cwd(), langArg);
|
|
418
|
+
|
|
419
|
+
const { kept, skipped } = filterExistingWriteFiles(
|
|
420
|
+
steps,
|
|
421
|
+
process.cwd()
|
|
422
|
+
);
|
|
423
|
+
|
|
424
|
+
if (kept.length > 0) {
|
|
425
|
+
console.log("\nApplying changes...");
|
|
426
|
+
await runSteps(kept, { projectRoot: process.cwd() });
|
|
427
|
+
if (skipped.length > 0) {
|
|
428
|
+
console.log(
|
|
429
|
+
chalk.dim(`Skipped existing files: ${skipped.join(", ")}`)
|
|
430
|
+
);
|
|
431
|
+
}
|
|
432
|
+
console.log(chalk.green("Done!"));
|
|
433
|
+
} else {
|
|
434
|
+
console.log(
|
|
435
|
+
chalk.yellow(
|
|
436
|
+
"No standard template available for this language yet."
|
|
437
|
+
)
|
|
438
|
+
);
|
|
439
|
+
}
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
if (action === "license") {
|
|
444
|
+
const { type, author } = await prompt([
|
|
445
|
+
{
|
|
446
|
+
type: "list",
|
|
447
|
+
name: "type",
|
|
448
|
+
message: "Choose a license:",
|
|
449
|
+
choices: licenseTypes,
|
|
450
|
+
},
|
|
451
|
+
{
|
|
452
|
+
type: "input",
|
|
453
|
+
name: "author",
|
|
454
|
+
message: "Author Name:",
|
|
455
|
+
default: userConfig.author || "The Authors",
|
|
456
|
+
},
|
|
457
|
+
]);
|
|
458
|
+
const steps = generateLicense(process.cwd(), type, author);
|
|
459
|
+
const { kept, skipped } = filterExistingWriteFiles(
|
|
460
|
+
steps,
|
|
461
|
+
process.cwd()
|
|
462
|
+
);
|
|
463
|
+
if (kept.length === 0) {
|
|
464
|
+
console.log(chalk.dim("Nothing to do (LICENSE already exists)."));
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
await runSteps(kept, { projectRoot: process.cwd() });
|
|
468
|
+
if (skipped.length > 0) {
|
|
469
|
+
console.log(
|
|
470
|
+
chalk.dim(`Skipped existing files: ${skipped.join(", ")}`)
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
console.log(chalk.green("Done!"));
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// If 'new', fall through to normal wizard
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
313
481
|
// Clear console for a fresh start
|
|
314
482
|
console.clear();
|
|
315
483
|
|
|
@@ -332,13 +500,37 @@ async function main(options = {}) {
|
|
|
332
500
|
throw new Error("No languages configured.");
|
|
333
501
|
}
|
|
334
502
|
|
|
503
|
+
const userConfig = loadConfig();
|
|
504
|
+
|
|
335
505
|
const allowedPms = ["npm", "pnpm", "yarn", "bun"];
|
|
336
|
-
|
|
506
|
+
let preselectedPm =
|
|
337
507
|
typeof args.pm === "string" && allowedPms.includes(args.pm)
|
|
338
508
|
? args.pm
|
|
339
509
|
: undefined;
|
|
340
510
|
|
|
511
|
+
if (!preselectedPm && userConfig.packageManager) {
|
|
512
|
+
if (allowedPms.includes(userConfig.packageManager)) {
|
|
513
|
+
preselectedPm = userConfig.packageManager;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
if (userConfig.learningMode && args.learning === false) {
|
|
518
|
+
args.learning = true;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
const defaultLicenseType =
|
|
522
|
+
typeof userConfig.defaultLicense === "string" &&
|
|
523
|
+
licenseTypes.includes(userConfig.defaultLicense)
|
|
524
|
+
? userConfig.defaultLicense
|
|
525
|
+
: null;
|
|
526
|
+
|
|
527
|
+
const defaultAuthor =
|
|
528
|
+
typeof userConfig.author === "string" && userConfig.author.trim()
|
|
529
|
+
? userConfig.author.trim()
|
|
530
|
+
: "The Authors";
|
|
531
|
+
|
|
341
532
|
const state = {
|
|
533
|
+
template: args.template || undefined,
|
|
342
534
|
language:
|
|
343
535
|
args.language && languages.includes(args.language)
|
|
344
536
|
? args.language
|
|
@@ -360,7 +552,10 @@ async function main(options = {}) {
|
|
|
360
552
|
state.language === "JavaScript" || state.language === "TypeScript";
|
|
361
553
|
|
|
362
554
|
let step = "language";
|
|
363
|
-
if (
|
|
555
|
+
if (state.template) {
|
|
556
|
+
if (!state.name) step = "name";
|
|
557
|
+
else step = "confirm";
|
|
558
|
+
} else if (!state.language) step = "language";
|
|
364
559
|
else if (!state.framework) step = "framework";
|
|
365
560
|
else if (needsPackageManager && !state.pm) step = "pm";
|
|
366
561
|
else if (!state.name) step = "name";
|
|
@@ -453,6 +648,38 @@ async function main(options = {}) {
|
|
|
453
648
|
}
|
|
454
649
|
|
|
455
650
|
state.framework = answer.framework;
|
|
651
|
+
|
|
652
|
+
// Preflight Checks
|
|
653
|
+
const gen = getGenerator(state.language, state.framework);
|
|
654
|
+
if (gen && gen.check && gen.check.length > 0) {
|
|
655
|
+
/* eslint-disable-next-line no-console */
|
|
656
|
+
console.log(chalk.dim("\n(checking requirements...)"));
|
|
657
|
+
const results = await checkBinaries(gen.check);
|
|
658
|
+
const missing = results.filter((r) => !r.ok);
|
|
659
|
+
|
|
660
|
+
if (missing.length > 0) {
|
|
661
|
+
console.log(chalk.red.bold("\nMissing required tools:"));
|
|
662
|
+
missing.forEach((m) => console.log(chalk.red(` - ${m.bin}`)));
|
|
663
|
+
console.log(
|
|
664
|
+
chalk.yellow("You may not be able to build or run this project.\n")
|
|
665
|
+
);
|
|
666
|
+
|
|
667
|
+
const { proceed } = await prompt([
|
|
668
|
+
{
|
|
669
|
+
type: "confirm",
|
|
670
|
+
name: "proceed",
|
|
671
|
+
message: "Continue anyway?",
|
|
672
|
+
default: false,
|
|
673
|
+
},
|
|
674
|
+
]);
|
|
675
|
+
|
|
676
|
+
if (!proceed) {
|
|
677
|
+
step = "framework";
|
|
678
|
+
continue;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
|
|
456
683
|
step = "pm";
|
|
457
684
|
continue;
|
|
458
685
|
}
|
|
@@ -528,6 +755,10 @@ async function main(options = {}) {
|
|
|
528
755
|
|
|
529
756
|
const v = String(projectName || "").trim();
|
|
530
757
|
if (v.toLowerCase() === "back") {
|
|
758
|
+
if (state.template) {
|
|
759
|
+
console.log("Operation cancelled.");
|
|
760
|
+
process.exit(0);
|
|
761
|
+
}
|
|
531
762
|
step =
|
|
532
763
|
state.language === "JavaScript" || state.language === "TypeScript"
|
|
533
764
|
? "pm"
|
|
@@ -541,13 +772,173 @@ async function main(options = {}) {
|
|
|
541
772
|
}
|
|
542
773
|
|
|
543
774
|
if (step === "confirm") {
|
|
775
|
+
const projectRoot = path.resolve(process.cwd(), state.name);
|
|
776
|
+
const targetExists = fs.existsSync(projectRoot);
|
|
777
|
+
|
|
778
|
+
if (state.template) {
|
|
779
|
+
if (targetExists && !args.dryRun) {
|
|
780
|
+
console.error(
|
|
781
|
+
`\nError: Target folder already exists: ${projectRoot}`
|
|
782
|
+
);
|
|
783
|
+
state.name = undefined;
|
|
784
|
+
step = "name";
|
|
785
|
+
continue;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
console.log("\nProject Configuration:");
|
|
789
|
+
console.log(` Template: ${state.template}`);
|
|
790
|
+
console.log(` Folder: ${state.name}`);
|
|
791
|
+
console.log("");
|
|
792
|
+
|
|
793
|
+
if (!args.yes) {
|
|
794
|
+
const { action } = await prompt([
|
|
795
|
+
{
|
|
796
|
+
type: "list",
|
|
797
|
+
name: "action",
|
|
798
|
+
message: "Clone this template?",
|
|
799
|
+
choices: [
|
|
800
|
+
{ name: "Clone template", value: "create" },
|
|
801
|
+
{ name: "Cancel", value: "cancel" },
|
|
802
|
+
],
|
|
803
|
+
},
|
|
804
|
+
]);
|
|
805
|
+
if (action === "cancel") {
|
|
806
|
+
console.log("Aborted.");
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
if (args.dryRun) {
|
|
812
|
+
console.log(
|
|
813
|
+
`[Dry Run] Would clone ${state.template} to ${projectRoot}`
|
|
814
|
+
);
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
console.log(chalk.dim("\nCloning repository..."));
|
|
819
|
+
try {
|
|
820
|
+
await gitClone(state.template, projectRoot);
|
|
821
|
+
// Remove .git to make it a fresh project
|
|
822
|
+
removeGitFolder(projectRoot);
|
|
823
|
+
|
|
824
|
+
// Optional extras after cloning
|
|
825
|
+
const detectedTemplate = detectLanguage(projectRoot);
|
|
826
|
+
const pmTemplate = detectPackageManager(projectRoot);
|
|
827
|
+
|
|
828
|
+
let langArg = detectedTemplate;
|
|
829
|
+
if (detectedTemplate === "JavaScript/TypeScript")
|
|
830
|
+
langArg = "JavaScript";
|
|
831
|
+
if (detectedTemplate === "Java/Kotlin") langArg = "Java";
|
|
832
|
+
|
|
833
|
+
let wantCi = Boolean(args.ci);
|
|
834
|
+
let wantDocker = Boolean(args.docker);
|
|
835
|
+
let wantDevContainer = Boolean(args.devcontainer);
|
|
836
|
+
let wantLicense =
|
|
837
|
+
typeof args.license === "boolean"
|
|
838
|
+
? args.license
|
|
839
|
+
: defaultLicenseType !== null;
|
|
840
|
+
|
|
841
|
+
if (!args.yes) {
|
|
842
|
+
const licenseLabel =
|
|
843
|
+
defaultLicenseType !== null
|
|
844
|
+
? `LICENSE (${defaultLicenseType})`
|
|
845
|
+
: "LICENSE (skip)";
|
|
846
|
+
|
|
847
|
+
const { extras } = await prompt([
|
|
848
|
+
{
|
|
849
|
+
type: "checkbox",
|
|
850
|
+
name: "extras",
|
|
851
|
+
message: "Extras to apply after clone:",
|
|
852
|
+
choices: [
|
|
853
|
+
{ name: "GitHub Actions CI", value: "ci", checked: wantCi },
|
|
854
|
+
{
|
|
855
|
+
name: "Dockerfile",
|
|
856
|
+
value: "docker",
|
|
857
|
+
checked: wantDocker,
|
|
858
|
+
},
|
|
859
|
+
{
|
|
860
|
+
name: "Dev Container (VS Code)",
|
|
861
|
+
value: "devcontainer",
|
|
862
|
+
checked: wantDevContainer,
|
|
863
|
+
},
|
|
864
|
+
{
|
|
865
|
+
name: licenseLabel,
|
|
866
|
+
value: "license",
|
|
867
|
+
checked: wantLicense,
|
|
868
|
+
disabled:
|
|
869
|
+
defaultLicenseType === null
|
|
870
|
+
? "Configure a default license in 'projectcli config'"
|
|
871
|
+
: false,
|
|
872
|
+
},
|
|
873
|
+
],
|
|
874
|
+
},
|
|
875
|
+
]);
|
|
876
|
+
if (extras) {
|
|
877
|
+
wantCi = extras.includes("ci");
|
|
878
|
+
wantDocker = extras.includes("docker");
|
|
879
|
+
wantDevContainer = extras.includes("devcontainer");
|
|
880
|
+
wantLicense = extras.includes("license");
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
const extraSteps = [];
|
|
885
|
+
if (wantCi)
|
|
886
|
+
extraSteps.push(...generateCI(projectRoot, langArg, pmTemplate));
|
|
887
|
+
if (wantDocker)
|
|
888
|
+
extraSteps.push(...generateDocker(projectRoot, langArg));
|
|
889
|
+
if (wantDevContainer) {
|
|
890
|
+
extraSteps.push(...generateDevContainer(projectRoot, langArg));
|
|
891
|
+
}
|
|
892
|
+
if (wantLicense && defaultLicenseType !== null) {
|
|
893
|
+
extraSteps.push(
|
|
894
|
+
...generateLicense(projectRoot, defaultLicenseType, defaultAuthor)
|
|
895
|
+
);
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
if (wantLicense && defaultLicenseType === null) {
|
|
899
|
+
console.log(
|
|
900
|
+
chalk.dim(
|
|
901
|
+
"Skipping LICENSE (no default license configured; run 'projectcli config')."
|
|
902
|
+
)
|
|
903
|
+
);
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
if (extraSteps.length > 0) {
|
|
907
|
+
const { kept, skipped } = filterExistingWriteFiles(
|
|
908
|
+
extraSteps,
|
|
909
|
+
projectRoot
|
|
910
|
+
);
|
|
911
|
+
if (kept.length > 0) {
|
|
912
|
+
console.log(chalk.dim("\nApplying extras..."));
|
|
913
|
+
await runSteps(kept, { projectRoot });
|
|
914
|
+
}
|
|
915
|
+
if (skipped.length > 0) {
|
|
916
|
+
console.log(
|
|
917
|
+
chalk.dim(`Skipped existing files: ${skipped.join(", ")}`)
|
|
918
|
+
);
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
console.log(
|
|
923
|
+
chalk.green(`\nSuccess! Created project at ${projectRoot}`)
|
|
924
|
+
);
|
|
925
|
+
console.log(
|
|
926
|
+
chalk.dim(
|
|
927
|
+
"You may need to run 'npm install' or similar inside the folder."
|
|
928
|
+
)
|
|
929
|
+
);
|
|
930
|
+
} catch (err) {
|
|
931
|
+
console.error(chalk.red("\nFailed to clone template:"), err.message);
|
|
932
|
+
process.exit(1);
|
|
933
|
+
}
|
|
934
|
+
return;
|
|
935
|
+
}
|
|
936
|
+
|
|
544
937
|
const generator = getGenerator(state.language, state.framework);
|
|
545
938
|
if (!generator) {
|
|
546
939
|
throw new Error("Generator not found (registry mismatch).");
|
|
547
940
|
}
|
|
548
941
|
|
|
549
|
-
const projectRoot = path.resolve(process.cwd(), state.name);
|
|
550
|
-
const targetExists = fs.existsSync(projectRoot);
|
|
551
942
|
if (targetExists && !args.dryRun) {
|
|
552
943
|
console.error(`\nError: Target folder already exists: ${projectRoot}`);
|
|
553
944
|
state.name = undefined;
|
|
@@ -712,10 +1103,15 @@ async function main(options = {}) {
|
|
|
712
1103
|
}
|
|
713
1104
|
}
|
|
714
1105
|
|
|
715
|
-
// CI/CD & Docker
|
|
1106
|
+
// CI/CD & Docker & DevContainer
|
|
716
1107
|
if (!args.dryRun) {
|
|
717
1108
|
let wantCi = args.ci;
|
|
718
1109
|
let wantDocker = args.docker;
|
|
1110
|
+
let wantDevContainer = Boolean(args.devcontainer);
|
|
1111
|
+
let wantLicense =
|
|
1112
|
+
typeof args.license === "boolean"
|
|
1113
|
+
? args.license
|
|
1114
|
+
: defaultLicenseType !== null;
|
|
719
1115
|
|
|
720
1116
|
if (!args.yes) {
|
|
721
1117
|
const { extras } = await prompt([
|
|
@@ -726,12 +1122,31 @@ async function main(options = {}) {
|
|
|
726
1122
|
choices: [
|
|
727
1123
|
{ name: "GitHub Actions CI", value: "ci", checked: wantCi },
|
|
728
1124
|
{ name: "Dockerfile", value: "docker", checked: wantDocker },
|
|
1125
|
+
{
|
|
1126
|
+
name: "Dev Container (VS Code)",
|
|
1127
|
+
value: "devcontainer",
|
|
1128
|
+
checked: wantDevContainer,
|
|
1129
|
+
},
|
|
1130
|
+
{
|
|
1131
|
+
name:
|
|
1132
|
+
defaultLicenseType !== null
|
|
1133
|
+
? `LICENSE (${defaultLicenseType})`
|
|
1134
|
+
: "LICENSE (skip)",
|
|
1135
|
+
value: "license",
|
|
1136
|
+
checked: wantLicense,
|
|
1137
|
+
disabled:
|
|
1138
|
+
defaultLicenseType === null
|
|
1139
|
+
? "Configure a default license in 'projectcli config'"
|
|
1140
|
+
: false,
|
|
1141
|
+
},
|
|
729
1142
|
],
|
|
730
1143
|
},
|
|
731
1144
|
]);
|
|
732
1145
|
if (extras) {
|
|
733
1146
|
wantCi = extras.includes("ci");
|
|
734
1147
|
wantDocker = extras.includes("docker");
|
|
1148
|
+
wantDevContainer = extras.includes("devcontainer");
|
|
1149
|
+
wantLicense = extras.includes("license");
|
|
735
1150
|
}
|
|
736
1151
|
}
|
|
737
1152
|
|
|
@@ -742,10 +1157,28 @@ async function main(options = {}) {
|
|
|
742
1157
|
if (wantDocker) {
|
|
743
1158
|
extraSteps.push(...generateDocker(projectRoot, state.language));
|
|
744
1159
|
}
|
|
1160
|
+
if (wantDevContainer) {
|
|
1161
|
+
extraSteps.push(...generateDevContainer(projectRoot, state.language));
|
|
1162
|
+
}
|
|
1163
|
+
if (wantLicense && defaultLicenseType !== null) {
|
|
1164
|
+
extraSteps.push(
|
|
1165
|
+
...generateLicense(projectRoot, defaultLicenseType, defaultAuthor)
|
|
1166
|
+
);
|
|
1167
|
+
}
|
|
745
1168
|
|
|
746
|
-
|
|
1169
|
+
const { kept, skipped } = filterExistingWriteFiles(
|
|
1170
|
+
extraSteps,
|
|
1171
|
+
projectRoot
|
|
1172
|
+
);
|
|
1173
|
+
|
|
1174
|
+
if (kept.length > 0) {
|
|
747
1175
|
console.log("Adding extras...");
|
|
748
|
-
await runSteps(
|
|
1176
|
+
await runSteps(kept, { projectRoot });
|
|
1177
|
+
}
|
|
1178
|
+
if (skipped.length > 0) {
|
|
1179
|
+
console.log(
|
|
1180
|
+
chalk.dim(`Skipped existing files: ${skipped.join(", ")}`)
|
|
1181
|
+
);
|
|
749
1182
|
}
|
|
750
1183
|
}
|
|
751
1184
|
|
package/src/license.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
const licenses = {
|
|
2
|
+
MIT: (year, author) => `MIT License
|
|
3
|
+
|
|
4
|
+
Copyright (c) ${year} ${author}
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
|
23
|
+
`,
|
|
24
|
+
Apache2: (year, author) => ` Apache License
|
|
25
|
+
Version 2.0, January 2004
|
|
26
|
+
http://www.apache.org/licenses/
|
|
27
|
+
|
|
28
|
+
Copyright ${year} ${author}
|
|
29
|
+
|
|
30
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
31
|
+
you may not use this file except in compliance with the License.
|
|
32
|
+
You may obtain a copy of the License at
|
|
33
|
+
|
|
34
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
35
|
+
|
|
36
|
+
Unless required by applicable law or agreed to in writing, software
|
|
37
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
38
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
39
|
+
See the License for the specific language governing permissions and
|
|
40
|
+
limitations under the License.
|
|
41
|
+
`,
|
|
42
|
+
ISC: (year, author) => `ISC License
|
|
43
|
+
|
|
44
|
+
Copyright (c) ${year}, ${author}
|
|
45
|
+
|
|
46
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
47
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
48
|
+
copyright notice and this permission notice appear in all copies.
|
|
49
|
+
|
|
50
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
51
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
52
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
53
|
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
54
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
55
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
56
|
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
57
|
+
`,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
function getLicense(type, author) {
|
|
61
|
+
const year = new Date().getFullYear();
|
|
62
|
+
const template = licenses[type] || licenses.MIT;
|
|
63
|
+
return template(year, author || "The Authors");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function generateLicense(projectRoot, type, author) {
|
|
67
|
+
return [
|
|
68
|
+
{
|
|
69
|
+
type: "writeFile",
|
|
70
|
+
path: "LICENSE",
|
|
71
|
+
content: getLicense(type, author),
|
|
72
|
+
},
|
|
73
|
+
];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
module.exports = {
|
|
77
|
+
getLicense,
|
|
78
|
+
generateLicense,
|
|
79
|
+
licenseTypes: ["MIT", "Apache2", "ISC"],
|
|
80
|
+
};
|
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,63 @@
|
|
|
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
|
+
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: "input",
|
|
27
|
+
name: "author",
|
|
28
|
+
message: "Default Author Name (for LICENSE):",
|
|
29
|
+
default: config.author || "The Authors",
|
|
30
|
+
filter: (v) => String(v || "").trim(),
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
type: "list",
|
|
34
|
+
name: "defaultLicense",
|
|
35
|
+
message: "Default License Type:",
|
|
36
|
+
choices: [
|
|
37
|
+
{ name: "None", value: null },
|
|
38
|
+
inquirerSeparator(),
|
|
39
|
+
{ name: "MIT", value: "MIT" },
|
|
40
|
+
{ name: "Apache-2.0", value: "Apache2" },
|
|
41
|
+
{ name: "ISC", value: "ISC" },
|
|
42
|
+
],
|
|
43
|
+
default: config.defaultLicense || "MIT",
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
type: "confirm",
|
|
47
|
+
name: "learningMode",
|
|
48
|
+
message: "Enable Learning Mode by default?",
|
|
49
|
+
default: config.learningMode || false,
|
|
50
|
+
},
|
|
51
|
+
]);
|
|
52
|
+
|
|
53
|
+
saveConfig(answers);
|
|
54
|
+
console.log(chalk.green("\nโ Settings saved!"));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Helper for pure inquirer usage if needed,
|
|
58
|
+
// though index.js passes the prompt instance.
|
|
59
|
+
function inquirerSeparator() {
|
|
60
|
+
return { name: "โโโโโโโโโโโโโโ", disabled: true };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
module.exports = { runConfig };
|