@dawitworku/projectcli 0.1.4 โ 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/cicd.js +169 -0
- package/src/config.js +34 -0
- package/src/descriptions.js +45 -0
- package/src/index.js +340 -5
- package/src/libraries.js +49 -0
- package/src/pm.js +27 -0
- package/src/preflight.js +25 -0
- package/src/registry.js +18 -22
- 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.1
|
|
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/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
|
+
};
|
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,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,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");
|
|
33
|
+
const { generateCI, generateDocker } = require("./cicd");
|
|
34
|
+
const { getDescription } = require("./descriptions");
|
|
35
|
+
const { detectLanguage, detectPackageManager } = require("./detect");
|
|
36
|
+
const { loadConfig } = require("./config");
|
|
37
|
+
const { runConfig } = require("./settings");
|
|
31
38
|
|
|
32
39
|
const RUST_KEYWORDS = new Set(
|
|
33
40
|
[
|
|
@@ -129,6 +136,10 @@ function parseArgs(argv) {
|
|
|
129
136
|
framework: undefined,
|
|
130
137
|
name: undefined,
|
|
131
138
|
pm: undefined,
|
|
139
|
+
ci: false,
|
|
140
|
+
docker: false,
|
|
141
|
+
learning: false,
|
|
142
|
+
template: undefined,
|
|
132
143
|
};
|
|
133
144
|
|
|
134
145
|
const nextValue = (i) => {
|
|
@@ -143,7 +154,15 @@ function parseArgs(argv) {
|
|
|
143
154
|
else if (a === "--list") out.list = true;
|
|
144
155
|
else if (a === "--yes" || a === "-y") out.yes = true;
|
|
145
156
|
else if (a === "--dry-run") out.dryRun = true;
|
|
146
|
-
else if (a
|
|
157
|
+
else if (a === "--ci") out.ci = true;
|
|
158
|
+
else if (a === "--docker") out.docker = true;
|
|
159
|
+
else if (a === "--learning") out.learning = true;
|
|
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="))
|
|
147
166
|
out.language = a.slice("--language=".length);
|
|
148
167
|
else if (a === "--language") {
|
|
149
168
|
out.language = nextValue(i);
|
|
@@ -189,6 +208,7 @@ function printHelp() {
|
|
|
189
208
|
console.log(
|
|
190
209
|
" projectcli --language <lang> --framework <fw> --name <project>"
|
|
191
210
|
);
|
|
211
|
+
console.log(" projectcli config # configure defaults");
|
|
192
212
|
console.log("");
|
|
193
213
|
console.log("Flags:");
|
|
194
214
|
console.log(" --help, -h Show help");
|
|
@@ -202,9 +222,14 @@ function printHelp() {
|
|
|
202
222
|
console.log(
|
|
203
223
|
" --pm Package manager for JS/TS (npm|pnpm|yarn|bun)"
|
|
204
224
|
);
|
|
225
|
+
console.log(" --ci Auto-add GitHub Actions CI");
|
|
226
|
+
console.log(" --docker Auto-add Dockerfile");
|
|
227
|
+
console.log(" --learning Enable learning mode (shows descriptions)");
|
|
228
|
+
console.log(" --template Clone from a Git repository URL");
|
|
205
229
|
}
|
|
206
230
|
|
|
207
231
|
const BACK = "__back__";
|
|
232
|
+
const COMMUNITY = "Community / Remote";
|
|
208
233
|
|
|
209
234
|
function withBack(choices) {
|
|
210
235
|
return [
|
|
@@ -247,6 +272,18 @@ function printList() {
|
|
|
247
272
|
}
|
|
248
273
|
}
|
|
249
274
|
|
|
275
|
+
function showBanner() {
|
|
276
|
+
console.log("");
|
|
277
|
+
console.log(
|
|
278
|
+
gradient.pastel.multiline(
|
|
279
|
+
figlet.textSync("ProjectCLI", { font: "Standard" })
|
|
280
|
+
)
|
|
281
|
+
);
|
|
282
|
+
console.log(chalk.bold.magenta(" The Swiss Army Knife for Developers"));
|
|
283
|
+
console.log(chalk.dim(" v" + readPackageVersion()));
|
|
284
|
+
console.log("");
|
|
285
|
+
}
|
|
286
|
+
|
|
250
287
|
async function main(options = {}) {
|
|
251
288
|
if (!prompt) {
|
|
252
289
|
throw new Error(
|
|
@@ -257,7 +294,19 @@ async function main(options = {}) {
|
|
|
257
294
|
const argv = options.argv || [];
|
|
258
295
|
const { cmd, rest } = splitCommand(argv);
|
|
259
296
|
const args = parseArgs(rest);
|
|
297
|
+
|
|
298
|
+
if (
|
|
299
|
+
!args.list &&
|
|
300
|
+
!args.version &&
|
|
301
|
+
!args.help &&
|
|
302
|
+
rest.length === 0 &&
|
|
303
|
+
cmd === "init"
|
|
304
|
+
) {
|
|
305
|
+
showBanner();
|
|
306
|
+
}
|
|
307
|
+
|
|
260
308
|
if (args.help) {
|
|
309
|
+
showBanner();
|
|
261
310
|
printHelp();
|
|
262
311
|
return;
|
|
263
312
|
}
|
|
@@ -275,6 +324,87 @@ async function main(options = {}) {
|
|
|
275
324
|
return;
|
|
276
325
|
}
|
|
277
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
|
+
|
|
278
408
|
// Clear console for a fresh start
|
|
279
409
|
console.clear();
|
|
280
410
|
|
|
@@ -297,13 +427,26 @@ async function main(options = {}) {
|
|
|
297
427
|
throw new Error("No languages configured.");
|
|
298
428
|
}
|
|
299
429
|
|
|
430
|
+
const userConfig = loadConfig();
|
|
431
|
+
|
|
300
432
|
const allowedPms = ["npm", "pnpm", "yarn", "bun"];
|
|
301
|
-
|
|
433
|
+
let preselectedPm =
|
|
302
434
|
typeof args.pm === "string" && allowedPms.includes(args.pm)
|
|
303
435
|
? args.pm
|
|
304
436
|
: undefined;
|
|
305
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
|
+
|
|
306
448
|
const state = {
|
|
449
|
+
template: args.template || undefined,
|
|
307
450
|
language:
|
|
308
451
|
args.language && languages.includes(args.language)
|
|
309
452
|
? args.language
|
|
@@ -325,7 +468,10 @@ async function main(options = {}) {
|
|
|
325
468
|
state.language === "JavaScript" || state.language === "TypeScript";
|
|
326
469
|
|
|
327
470
|
let step = "language";
|
|
328
|
-
if (
|
|
471
|
+
if (state.template) {
|
|
472
|
+
if (!state.name) step = "name";
|
|
473
|
+
else step = "confirm";
|
|
474
|
+
} else if (!state.language) step = "language";
|
|
329
475
|
else if (!state.framework) step = "framework";
|
|
330
476
|
else if (needsPackageManager && !state.pm) step = "pm";
|
|
331
477
|
else if (!state.name) step = "name";
|
|
@@ -418,6 +564,38 @@ async function main(options = {}) {
|
|
|
418
564
|
}
|
|
419
565
|
|
|
420
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
|
+
|
|
421
599
|
step = "pm";
|
|
422
600
|
continue;
|
|
423
601
|
}
|
|
@@ -493,6 +671,10 @@ async function main(options = {}) {
|
|
|
493
671
|
|
|
494
672
|
const v = String(projectName || "").trim();
|
|
495
673
|
if (v.toLowerCase() === "back") {
|
|
674
|
+
if (state.template) {
|
|
675
|
+
console.log("Operation cancelled.");
|
|
676
|
+
process.exit(0);
|
|
677
|
+
}
|
|
496
678
|
step =
|
|
497
679
|
state.language === "JavaScript" || state.language === "TypeScript"
|
|
498
680
|
? "pm"
|
|
@@ -506,13 +688,75 @@ async function main(options = {}) {
|
|
|
506
688
|
}
|
|
507
689
|
|
|
508
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
|
+
|
|
509
755
|
const generator = getGenerator(state.language, state.framework);
|
|
510
756
|
if (!generator) {
|
|
511
757
|
throw new Error("Generator not found (registry mismatch).");
|
|
512
758
|
}
|
|
513
759
|
|
|
514
|
-
const projectRoot = path.resolve(process.cwd(), state.name);
|
|
515
|
-
const targetExists = fs.existsSync(projectRoot);
|
|
516
760
|
if (targetExists && !args.dryRun) {
|
|
517
761
|
console.error(`\nError: Target folder already exists: ${projectRoot}`);
|
|
518
762
|
state.name = undefined;
|
|
@@ -526,6 +770,20 @@ async function main(options = {}) {
|
|
|
526
770
|
`Framework: ${state.framework}`,
|
|
527
771
|
];
|
|
528
772
|
if (state.pm) summaryLines.push(`Package manager: ${state.pm}`);
|
|
773
|
+
|
|
774
|
+
if (args.learning) {
|
|
775
|
+
const desc = getDescription(generator.id);
|
|
776
|
+
console.log(
|
|
777
|
+
chalk.cyan(
|
|
778
|
+
boxen(desc, {
|
|
779
|
+
padding: 1,
|
|
780
|
+
title: `About ${state.framework}`,
|
|
781
|
+
borderStyle: "round",
|
|
782
|
+
})
|
|
783
|
+
)
|
|
784
|
+
);
|
|
785
|
+
}
|
|
786
|
+
|
|
529
787
|
console.log("\n" + summaryLines.join("\n") + "\n");
|
|
530
788
|
|
|
531
789
|
if (targetExists && args.dryRun) {
|
|
@@ -623,6 +881,83 @@ async function main(options = {}) {
|
|
|
623
881
|
continue;
|
|
624
882
|
}
|
|
625
883
|
|
|
884
|
+
// Git init
|
|
885
|
+
if (!args.dryRun) {
|
|
886
|
+
let doGit = args.yes;
|
|
887
|
+
if (!doGit) {
|
|
888
|
+
const { git } = await prompt([
|
|
889
|
+
{
|
|
890
|
+
type: "confirm",
|
|
891
|
+
name: "git",
|
|
892
|
+
message: "Initialize a new git repository?",
|
|
893
|
+
default: true,
|
|
894
|
+
},
|
|
895
|
+
]);
|
|
896
|
+
doGit = git;
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
if (doGit) {
|
|
900
|
+
try {
|
|
901
|
+
await runSteps(
|
|
902
|
+
[
|
|
903
|
+
{ program: "git", args: ["init"], cwdFromProjectRoot: true },
|
|
904
|
+
{
|
|
905
|
+
program: "git",
|
|
906
|
+
args: ["add", "."],
|
|
907
|
+
cwdFromProjectRoot: true,
|
|
908
|
+
},
|
|
909
|
+
{
|
|
910
|
+
program: "git",
|
|
911
|
+
args: ["commit", "-m", "Initial commit"],
|
|
912
|
+
cwdFromProjectRoot: true,
|
|
913
|
+
},
|
|
914
|
+
],
|
|
915
|
+
{ projectRoot }
|
|
916
|
+
);
|
|
917
|
+
console.log("Initialized git repository.");
|
|
918
|
+
} catch (e) {
|
|
919
|
+
console.warn("Git init failed (is git installed?):", e.message);
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
// CI/CD & Docker
|
|
925
|
+
if (!args.dryRun) {
|
|
926
|
+
let wantCi = args.ci;
|
|
927
|
+
let wantDocker = args.docker;
|
|
928
|
+
|
|
929
|
+
if (!args.yes) {
|
|
930
|
+
const { extras } = await prompt([
|
|
931
|
+
{
|
|
932
|
+
type: "checkbox",
|
|
933
|
+
name: "extras",
|
|
934
|
+
message: "Extras:",
|
|
935
|
+
choices: [
|
|
936
|
+
{ name: "GitHub Actions CI", value: "ci", checked: wantCi },
|
|
937
|
+
{ name: "Dockerfile", value: "docker", checked: wantDocker },
|
|
938
|
+
],
|
|
939
|
+
},
|
|
940
|
+
]);
|
|
941
|
+
if (extras) {
|
|
942
|
+
wantCi = extras.includes("ci");
|
|
943
|
+
wantDocker = extras.includes("docker");
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
const extraSteps = [];
|
|
948
|
+
if (wantCi) {
|
|
949
|
+
extraSteps.push(...generateCI(projectRoot, state.language, state.pm));
|
|
950
|
+
}
|
|
951
|
+
if (wantDocker) {
|
|
952
|
+
extraSteps.push(...generateDocker(projectRoot, state.language));
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
if (extraSteps.length > 0) {
|
|
956
|
+
console.log("Adding extras...");
|
|
957
|
+
await runSteps(extraSteps, { projectRoot });
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
|
|
626
961
|
console.log(`\nDone. Created project in: ${projectRoot}`);
|
|
627
962
|
return;
|
|
628
963
|
}
|
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
|
|
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,23 +425,24 @@ 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
|
{
|
|
429
432
|
type: "writeFile",
|
|
430
433
|
path: "app.py",
|
|
431
434
|
content:
|
|
432
|
-
"from flask import Flask
|
|
435
|
+
"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
436
|
},
|
|
434
437
|
{
|
|
435
438
|
type: "writeFile",
|
|
436
439
|
path: "requirements.txt",
|
|
437
|
-
content: "flask
|
|
440
|
+
content: "flask\\n",
|
|
438
441
|
},
|
|
439
442
|
{
|
|
440
443
|
type: "writeFile",
|
|
441
444
|
path: ".gitignore",
|
|
442
|
-
content: ".venv
|
|
445
|
+
content: ".venv\\n__pycache__\\n*.pyc\\n.DS_Store\\n",
|
|
443
446
|
},
|
|
444
447
|
],
|
|
445
448
|
notes: "Writes app.py + requirements.txt (no pip install).",
|
|
@@ -453,23 +456,23 @@ const REGISTRY = {
|
|
|
453
456
|
type: "writeFile",
|
|
454
457
|
path: "main.py",
|
|
455
458
|
content:
|
|
456
|
-
"from fastapi import FastAPI
|
|
459
|
+
"from fastapi import FastAPI\\n\\napp = FastAPI()\\n\\n@app.get('/')\\ndef root():\\n return {'status': 'ok'}\\n",
|
|
457
460
|
},
|
|
458
461
|
{
|
|
459
462
|
type: "writeFile",
|
|
460
463
|
path: "requirements.txt",
|
|
461
|
-
content: "fastapi
|
|
464
|
+
content: "fastapi\\nuvicorn\\n",
|
|
462
465
|
},
|
|
463
466
|
{
|
|
464
467
|
type: "writeFile",
|
|
465
468
|
path: "README.md",
|
|
466
469
|
content:
|
|
467
|
-
"# FastAPI app
|
|
470
|
+
"# 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
471
|
},
|
|
469
472
|
{
|
|
470
473
|
type: "writeFile",
|
|
471
474
|
path: ".gitignore",
|
|
472
|
-
content: ".venv
|
|
475
|
+
content: ".venv\\n__pycache__\\n*.pyc\\n.DS_Store\\n",
|
|
473
476
|
},
|
|
474
477
|
],
|
|
475
478
|
notes: "Writes files only (no pip install).",
|
|
@@ -480,18 +483,17 @@ const REGISTRY = {
|
|
|
480
483
|
commands: (ctx) => {
|
|
481
484
|
const projectName = getProjectName(ctx);
|
|
482
485
|
return [
|
|
483
|
-
// Pyramid cookiecutter is common, but basic scaffold:
|
|
484
486
|
{ type: "mkdir", path: "." },
|
|
485
487
|
{
|
|
486
488
|
type: "writeFile",
|
|
487
489
|
path: "requirements.txt",
|
|
488
|
-
content: "pyramid
|
|
490
|
+
content: "pyramid\\nwaitress\\n",
|
|
489
491
|
},
|
|
490
492
|
{
|
|
491
493
|
type: "writeFile",
|
|
492
494
|
path: "app.py",
|
|
493
495
|
content:
|
|
494
|
-
"from wsgiref.simple_server import make_server
|
|
496
|
+
"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
497
|
},
|
|
496
498
|
];
|
|
497
499
|
},
|
|
@@ -503,6 +505,7 @@ const REGISTRY = {
|
|
|
503
505
|
"Cargo (bin)": {
|
|
504
506
|
id: "rs.cargo.bin",
|
|
505
507
|
label: "Cargo (bin)",
|
|
508
|
+
check: ["cargo"],
|
|
506
509
|
commands: (ctx) => {
|
|
507
510
|
const projectName = getProjectName(ctx);
|
|
508
511
|
return [
|
|
@@ -517,6 +520,7 @@ const REGISTRY = {
|
|
|
517
520
|
"Cargo (lib)": {
|
|
518
521
|
id: "rs.cargo.lib",
|
|
519
522
|
label: "Cargo (lib)",
|
|
523
|
+
check: ["cargo"],
|
|
520
524
|
commands: (ctx) => {
|
|
521
525
|
const projectName = getProjectName(ctx);
|
|
522
526
|
return [
|
|
@@ -534,8 +538,6 @@ const REGISTRY = {
|
|
|
534
538
|
commands: (ctx) => {
|
|
535
539
|
const projectName = getProjectName(ctx);
|
|
536
540
|
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
541
|
return [pmExec(pm, "create-tauri-app@latest", [projectName])];
|
|
540
542
|
},
|
|
541
543
|
notes: "Cross-platform desktop app",
|
|
@@ -551,8 +553,6 @@ const REGISTRY = {
|
|
|
551
553
|
args: ["init", "--name", projectName],
|
|
552
554
|
cwdFromProjectRoot: true,
|
|
553
555
|
},
|
|
554
|
-
// In reality, one would use cargo-leptos or a template.
|
|
555
|
-
// Just a stub for now.
|
|
556
556
|
];
|
|
557
557
|
},
|
|
558
558
|
notes: "Init basic Rust project (Leptos needs template usually)",
|
|
@@ -563,6 +563,7 @@ const REGISTRY = {
|
|
|
563
563
|
"Go module (basic)": {
|
|
564
564
|
id: "go.module.basic",
|
|
565
565
|
label: "Go module (basic)",
|
|
566
|
+
check: ["go"],
|
|
566
567
|
commands: (ctx) => {
|
|
567
568
|
const projectName = getProjectName(ctx);
|
|
568
569
|
return [
|
|
@@ -591,7 +592,7 @@ const REGISTRY = {
|
|
|
591
592
|
type: "writeFile",
|
|
592
593
|
path: "main.go",
|
|
593
594
|
content:
|
|
594
|
-
'package main
|
|
595
|
+
'package main\\n\\nimport "fmt"\\n\\nfunc main() {\\n fmt.Println("hello")\\n}\\n',
|
|
595
596
|
},
|
|
596
597
|
];
|
|
597
598
|
},
|
|
@@ -783,8 +784,6 @@ int main() {
|
|
|
783
784
|
id: "java.springboot.maven",
|
|
784
785
|
label: "Spring Boot (Maven)",
|
|
785
786
|
commands: (ctx) => {
|
|
786
|
-
// Simple archetype or just hints. Spring Init is complex.
|
|
787
|
-
// Let's use maven quickstart for now as basic.
|
|
788
787
|
const projectName = getProjectName(ctx);
|
|
789
788
|
return [
|
|
790
789
|
{
|
|
@@ -819,11 +818,8 @@ int main() {
|
|
|
819
818
|
"--project-name",
|
|
820
819
|
projectName,
|
|
821
820
|
],
|
|
822
|
-
cwd: projectName,
|
|
823
|
-
|
|
824
|
-
// runSteps implementation check:
|
|
825
|
-
// It uses `cwdFromProjectRoot` or defaults to current process.cwd().
|
|
826
|
-
// Better to just run gradle init inside the folder.
|
|
821
|
+
cwd: projectName,
|
|
822
|
+
cwdFromProjectRoot: true,
|
|
827
823
|
},
|
|
828
824
|
];
|
|
829
825
|
},
|
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 };
|