@dawitworku/projectcli 0.1.0 → 0.1.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 +2 -2
- package/package.json +1 -1
- package/src/add.js +156 -171
- package/src/detect.js +19 -1
- package/src/index.js +171 -48
- package/src/libraries.js +79 -1
- package/src/pm.js +32 -0
- package/src/registry.js +399 -1
package/README.md
CHANGED
|
@@ -9,13 +9,13 @@ Run it as a single command, pick language → framework, and it scaffolds the pr
|
|
|
9
9
|
After you publish this package to npm:
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
npx projectcli@latest
|
|
12
|
+
npx @dawitworku/projectcli@latest
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
You can also run non-interactively:
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
|
-
npx projectcli@latest --language "TypeScript" --framework "NestJS" --name my-api --pm npm
|
|
18
|
+
npx @dawitworku/projectcli@latest --language "TypeScript" --framework "NestJS" --name my-api --pm npm
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
## Run (dev)
|
package/package.json
CHANGED
package/src/add.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
const fs = require("node:fs");
|
|
2
2
|
const path = require("node:path");
|
|
3
|
+
const chalk = require("chalk");
|
|
4
|
+
const Fuse = require("fuse.js");
|
|
5
|
+
const inquirerImport = require("inquirer");
|
|
6
|
+
const inquirer = inquirerImport.default ?? inquirerImport;
|
|
3
7
|
|
|
4
8
|
const { detectLanguage, detectPackageManager } = require("./detect");
|
|
5
9
|
const { getCatalog } = require("./libraries");
|
|
@@ -21,18 +25,22 @@ function parseAddArgs(argv) {
|
|
|
21
25
|
}
|
|
22
26
|
|
|
23
27
|
function printStepsPreview(steps) {
|
|
24
|
-
console.log("\nPlanned actions:");
|
|
28
|
+
console.log(chalk.bold.cyan("\nPlanned actions:"));
|
|
25
29
|
for (const step of steps) {
|
|
26
30
|
const type = step.type || "command";
|
|
27
31
|
if (type === "command") {
|
|
28
32
|
const where = step.cwdFromProjectRoot ? "(in project)" : "(here)";
|
|
29
|
-
console.log(
|
|
33
|
+
console.log(
|
|
34
|
+
chalk.gray("- ") +
|
|
35
|
+
chalk.green(`${step.program} ${step.args.join(" ")}`) +
|
|
36
|
+
chalk.dim(` ${where}`)
|
|
37
|
+
);
|
|
30
38
|
} else if (type === "mkdir") {
|
|
31
|
-
console.log(
|
|
39
|
+
console.log(chalk.gray("- ") + chalk.yellow(`mkdir -p ${step.path}`));
|
|
32
40
|
} else if (type === "writeFile") {
|
|
33
|
-
console.log(
|
|
41
|
+
console.log(chalk.gray("- ") + chalk.yellow(`write ${step.path}`));
|
|
34
42
|
} else {
|
|
35
|
-
console.log(`- ${type}`);
|
|
43
|
+
console.log(chalk.gray(`- ${type}`));
|
|
36
44
|
}
|
|
37
45
|
}
|
|
38
46
|
console.log("");
|
|
@@ -42,7 +50,7 @@ async function runAdd({ prompt, argv }) {
|
|
|
42
50
|
const flags = parseAddArgs(argv);
|
|
43
51
|
const cwd = process.cwd();
|
|
44
52
|
const language = detectLanguage(cwd);
|
|
45
|
-
const pm = detectPackageManager(cwd);
|
|
53
|
+
const pm = detectPackageManager(cwd); // Now returns pip, poetry, cargo, go, etc.
|
|
46
54
|
|
|
47
55
|
if (language === "Unknown") {
|
|
48
56
|
throw new Error(
|
|
@@ -50,10 +58,8 @@ async function runAdd({ prompt, argv }) {
|
|
|
50
58
|
);
|
|
51
59
|
}
|
|
52
60
|
|
|
53
|
-
console.log(`\nDetected project: ${language}`);
|
|
54
|
-
|
|
55
|
-
console.log(`Detected package manager: ${pm}`);
|
|
56
|
-
}
|
|
61
|
+
console.log(chalk.bold(`\nDetected project: ${chalk.blue(language)}`));
|
|
62
|
+
console.log(chalk.dim(`Detected package manager: ${pm}`));
|
|
57
63
|
|
|
58
64
|
const catalog = getCatalog(language);
|
|
59
65
|
const categories = Object.keys(catalog);
|
|
@@ -61,201 +67,180 @@ async function runAdd({ prompt, argv }) {
|
|
|
61
67
|
throw new Error(`No library catalog configured for: ${language}`);
|
|
62
68
|
}
|
|
63
69
|
|
|
70
|
+
// Flatten catalog for fuzzy search
|
|
71
|
+
const allItems = [];
|
|
72
|
+
for (const cat of categories) {
|
|
73
|
+
for (const item of catalog[cat]) {
|
|
74
|
+
allItems.push({
|
|
75
|
+
...item,
|
|
76
|
+
category: cat,
|
|
77
|
+
displayName: `${cat}: ${item.label}`,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const fuse = new Fuse(allItems, {
|
|
83
|
+
keys: ["label", "category"],
|
|
84
|
+
threshold: 0.4,
|
|
85
|
+
});
|
|
86
|
+
|
|
64
87
|
const BACK = "__back__";
|
|
65
88
|
const EXIT = "__exit__";
|
|
66
89
|
|
|
67
|
-
let
|
|
68
|
-
let selectedLabels = null;
|
|
90
|
+
let selectedItems = [];
|
|
69
91
|
|
|
70
92
|
while (true) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
})),
|
|
77
|
-
new (require("inquirer").Separator)(),
|
|
78
|
-
{ name: "Exit", value: EXIT },
|
|
79
|
-
];
|
|
93
|
+
// If we have selected items, show them
|
|
94
|
+
if (selectedItems.length > 0) {
|
|
95
|
+
console.log(chalk.green(`\nSelected:`));
|
|
96
|
+
selectedItems.forEach((i) => console.log(` - ${i.displayName}`));
|
|
97
|
+
}
|
|
80
98
|
|
|
81
|
-
|
|
99
|
+
const choices = [
|
|
100
|
+
{ name: "Done (Install selected)", value: "DONE" },
|
|
101
|
+
{ name: "Search libraries (Fuzzy)", value: "SEARCH" },
|
|
102
|
+
{ name: "Browse by Category", value: "BROWSE" },
|
|
103
|
+
new inquirer.Separator(),
|
|
104
|
+
{ name: "Exit", value: EXIT },
|
|
105
|
+
];
|
|
106
|
+
|
|
107
|
+
const { action } = await prompt([
|
|
108
|
+
{
|
|
109
|
+
type: "list",
|
|
110
|
+
name: "action",
|
|
111
|
+
message: "What do you want to do?",
|
|
112
|
+
choices,
|
|
113
|
+
pageSize: 10,
|
|
114
|
+
},
|
|
115
|
+
]);
|
|
116
|
+
|
|
117
|
+
if (action === EXIT) return;
|
|
118
|
+
|
|
119
|
+
if (action === "SEARCH") {
|
|
120
|
+
// Fuzzy search
|
|
121
|
+
const { item } = await prompt([
|
|
82
122
|
{
|
|
83
|
-
type: "
|
|
84
|
-
name: "
|
|
85
|
-
message: "
|
|
86
|
-
|
|
87
|
-
|
|
123
|
+
type: "autocomplete",
|
|
124
|
+
name: "item",
|
|
125
|
+
message: "Search library:",
|
|
126
|
+
source: (answersSoFar, input) => {
|
|
127
|
+
if (!input)
|
|
128
|
+
return Promise.resolve(
|
|
129
|
+
allItems.map((i) => ({ name: i.displayName, value: i }))
|
|
130
|
+
);
|
|
131
|
+
return Promise.resolve(
|
|
132
|
+
fuse
|
|
133
|
+
.search(input)
|
|
134
|
+
.map((r) => ({ name: r.item.displayName, value: r.item }))
|
|
135
|
+
);
|
|
136
|
+
},
|
|
88
137
|
},
|
|
89
138
|
]);
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
139
|
+
if (item) {
|
|
140
|
+
if (!selectedItems.find((i) => i.label === item.label)) {
|
|
141
|
+
selectedItems.push(item);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
93
144
|
continue;
|
|
94
145
|
}
|
|
95
146
|
|
|
96
|
-
if (
|
|
97
|
-
const
|
|
98
|
-
const { picks } = await prompt([
|
|
147
|
+
if (action === "BROWSE") {
|
|
148
|
+
const { category } = await prompt([
|
|
99
149
|
{
|
|
100
|
-
type: "
|
|
101
|
-
name: "
|
|
102
|
-
message: "
|
|
103
|
-
choices:
|
|
104
|
-
validate: (v) =>
|
|
105
|
-
v && v.length ? true : "Pick at least one library.",
|
|
106
|
-
pageSize: 14,
|
|
150
|
+
type: "list",
|
|
151
|
+
name: "category",
|
|
152
|
+
message: "Category:",
|
|
153
|
+
choices: [...categories, { name: "Back", value: BACK }],
|
|
107
154
|
},
|
|
108
155
|
]);
|
|
156
|
+
if (category === BACK) continue;
|
|
109
157
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
const { next } = await prompt([
|
|
158
|
+
const items = catalog[category];
|
|
159
|
+
const { picked } = await prompt([
|
|
113
160
|
{
|
|
114
|
-
type: "
|
|
115
|
-
name: "
|
|
116
|
-
message: "
|
|
117
|
-
choices:
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
pageSize: 8,
|
|
161
|
+
type: "checkbox",
|
|
162
|
+
name: "picked",
|
|
163
|
+
message: "Select libraries:",
|
|
164
|
+
choices: items.map((i) => ({
|
|
165
|
+
name: i.label,
|
|
166
|
+
value: i,
|
|
167
|
+
checked: !!selectedItems.find((s) => s.label === i.label),
|
|
168
|
+
})),
|
|
123
169
|
},
|
|
124
170
|
]);
|
|
125
171
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
selectedCategory = null;
|
|
130
|
-
continue;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// continue
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
const items = catalog[selectedCategory] || [];
|
|
137
|
-
const chosen = items.filter((i) => selectedLabels.includes(i.label));
|
|
138
|
-
|
|
139
|
-
const packages = uniq(chosen.flatMap((i) => i.packages || []));
|
|
140
|
-
const packagesDev = uniq(chosen.flatMap((i) => i.packagesDev || []));
|
|
141
|
-
const posts = uniq(chosen.map((i) => i.post).filter(Boolean));
|
|
142
|
-
|
|
143
|
-
const steps = [];
|
|
144
|
-
|
|
145
|
-
if (language === "JavaScript/TypeScript") {
|
|
146
|
-
if (!fs.existsSync(path.join(cwd, "package.json"))) {
|
|
147
|
-
throw new Error(
|
|
148
|
-
"No package.json found; run this inside a JS/TS project."
|
|
149
|
-
);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
if (packages.length) {
|
|
153
|
-
const cmd = pmAddCommand(pm, packages, { dev: false });
|
|
154
|
-
steps.push({ type: "command", ...cmd, cwdFromProjectRoot: true });
|
|
155
|
-
}
|
|
156
|
-
if (packagesDev.length) {
|
|
157
|
-
const cmd = pmAddCommand(pm, packagesDev, { dev: true });
|
|
158
|
-
steps.push({ type: "command", ...cmd, cwdFromProjectRoot: true });
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// Optional post-steps
|
|
162
|
-
for (const post of posts) {
|
|
163
|
-
if (post === "tailwind-init") {
|
|
164
|
-
const cmd = pmExecCommand(pm, "tailwindcss", ["init", "-p"]);
|
|
165
|
-
steps.push({ type: "command", ...cmd, cwdFromProjectRoot: true });
|
|
166
|
-
}
|
|
167
|
-
if (post === "playwright-install") {
|
|
168
|
-
const cmd = pmExecCommand(pm, "playwright", ["install"]);
|
|
169
|
-
steps.push({ type: "command", ...cmd, cwdFromProjectRoot: true });
|
|
170
|
-
}
|
|
171
|
-
if (post === "shadcn-init") {
|
|
172
|
-
// shadcn/ui uses a CLI; keep it interactive.
|
|
173
|
-
const cmd = pmExecCommand(pm, "shadcn@latest", ["init"]);
|
|
174
|
-
steps.push({ type: "command", ...cmd, cwdFromProjectRoot: true });
|
|
172
|
+
for (const p of picked) {
|
|
173
|
+
if (!selectedItems.find((i) => i.label === p.label)) {
|
|
174
|
+
selectedItems.push(p);
|
|
175
175
|
}
|
|
176
176
|
}
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
177
179
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
console.log("
|
|
180
|
+
if (action === "DONE") {
|
|
181
|
+
if (selectedItems.length === 0) {
|
|
182
|
+
console.log(chalk.yellow("No libraries selected. Exiting."));
|
|
181
183
|
return;
|
|
182
184
|
}
|
|
183
|
-
|
|
184
|
-
if (!flags.yes) {
|
|
185
|
-
const { ok } = await prompt([
|
|
186
|
-
{
|
|
187
|
-
type: "confirm",
|
|
188
|
-
name: "ok",
|
|
189
|
-
message: "Install selected libraries now?",
|
|
190
|
-
default: true,
|
|
191
|
-
},
|
|
192
|
-
]);
|
|
193
|
-
if (!ok) {
|
|
194
|
-
selectedLabels = null;
|
|
195
|
-
continue;
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
await runSteps(steps, { projectRoot: cwd });
|
|
200
|
-
console.log("\nDone. Libraries added.");
|
|
201
|
-
return;
|
|
185
|
+
break;
|
|
202
186
|
}
|
|
187
|
+
}
|
|
203
188
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
type: "list",
|
|
209
|
-
name: "mode",
|
|
210
|
-
message: "How to add Python packages?",
|
|
211
|
-
choices: [
|
|
212
|
-
{
|
|
213
|
-
name: "Append to requirements.txt (no install)",
|
|
214
|
-
value: "requirements",
|
|
215
|
-
},
|
|
216
|
-
{
|
|
217
|
-
name: "pip install now (and append requirements.txt)",
|
|
218
|
-
value: "pip",
|
|
219
|
-
},
|
|
220
|
-
],
|
|
221
|
-
default: "requirements",
|
|
222
|
-
},
|
|
223
|
-
]);
|
|
189
|
+
// Install phase
|
|
190
|
+
const packages = uniq(selectedItems.flatMap((i) => i.packages || []));
|
|
191
|
+
const packagesDev = uniq(selectedItems.flatMap((i) => i.packagesDev || []));
|
|
192
|
+
const posts = uniq(selectedItems.map((i) => i.post).filter(Boolean));
|
|
224
193
|
|
|
225
|
-
|
|
226
|
-
const toAppend = all.map((p) => `${p}\n`).join("");
|
|
194
|
+
const steps = [];
|
|
227
195
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
console.log(`- append requirements.txt: ${all.join(", ")}`);
|
|
234
|
-
console.log("\nDry run: nothing executed.");
|
|
235
|
-
return;
|
|
236
|
-
}
|
|
196
|
+
// Generic handling based on 'pm'
|
|
197
|
+
if (packages.length) {
|
|
198
|
+
const cmd = pmAddCommand(pm, packages, { dev: false });
|
|
199
|
+
steps.push({ type: "command", ...cmd, cwdFromProjectRoot: true });
|
|
200
|
+
}
|
|
237
201
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
type: "command",
|
|
243
|
-
program: "python",
|
|
244
|
-
args: ["-m", "pip", "install", ...all],
|
|
245
|
-
cwdFromProjectRoot: true,
|
|
246
|
-
},
|
|
247
|
-
],
|
|
248
|
-
{ projectRoot: cwd }
|
|
249
|
-
);
|
|
250
|
-
}
|
|
202
|
+
if (packagesDev.length) {
|
|
203
|
+
const cmd = pmAddCommand(pm, packagesDev, { dev: true });
|
|
204
|
+
steps.push({ type: "command", ...cmd, cwdFromProjectRoot: true });
|
|
205
|
+
}
|
|
251
206
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
207
|
+
// Post install steps
|
|
208
|
+
for (const post of posts) {
|
|
209
|
+
if (post === "tailwind-init") {
|
|
210
|
+
const cmd = pmExecCommand(pm, "tailwindcss", ["init", "-p"]);
|
|
211
|
+
steps.push({ type: "command", ...cmd, cwdFromProjectRoot: true });
|
|
212
|
+
}
|
|
213
|
+
if (post === "playwright-install") {
|
|
214
|
+
const cmd = pmExecCommand(pm, "playwright", ["install"]);
|
|
215
|
+
steps.push({ type: "command", ...cmd, cwdFromProjectRoot: true });
|
|
255
216
|
}
|
|
217
|
+
if (post === "shadcn-init") {
|
|
218
|
+
const cmd = pmExecCommand(pm, "shadcn@latest", ["init"]);
|
|
219
|
+
steps.push({ type: "command", ...cmd, cwdFromProjectRoot: true });
|
|
220
|
+
}
|
|
221
|
+
}
|
|
256
222
|
|
|
257
|
-
|
|
223
|
+
if (flags.dryRun) {
|
|
224
|
+
printStepsPreview(steps);
|
|
225
|
+
console.log("Dry run: nothing executed.");
|
|
226
|
+
return;
|
|
258
227
|
}
|
|
228
|
+
|
|
229
|
+
if (!flags.yes) {
|
|
230
|
+
printStepsPreview(steps);
|
|
231
|
+
const { ok } = await prompt([
|
|
232
|
+
{
|
|
233
|
+
type: "confirm",
|
|
234
|
+
name: "ok",
|
|
235
|
+
message: "Proceed with installation?",
|
|
236
|
+
default: true,
|
|
237
|
+
},
|
|
238
|
+
]);
|
|
239
|
+
if (!ok) return;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
await runSteps(steps, { projectRoot: cwd });
|
|
243
|
+
console.log(chalk.green("\nDone. Libraries added."));
|
|
259
244
|
}
|
|
260
245
|
|
|
261
246
|
module.exports = {
|
package/src/detect.js
CHANGED
|
@@ -6,11 +6,29 @@ function exists(cwd, rel) {
|
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
function detectPackageManager(cwd) {
|
|
9
|
+
// JS/TS
|
|
9
10
|
if (exists(cwd, "pnpm-lock.yaml")) return "pnpm";
|
|
10
11
|
if (exists(cwd, "yarn.lock")) return "yarn";
|
|
11
12
|
if (exists(cwd, "bun.lockb") || exists(cwd, "bun.lock")) return "bun";
|
|
12
13
|
if (exists(cwd, "package-lock.json")) return "npm";
|
|
13
|
-
|
|
14
|
+
|
|
15
|
+
// Python
|
|
16
|
+
if (exists(cwd, "poetry.lock")) return "poetry";
|
|
17
|
+
if (exists(cwd, "requirements.txt")) return "pip";
|
|
18
|
+
if (exists(cwd, "pyproject.toml")) return "pip"; // Default to pip/standard if no specific lock file
|
|
19
|
+
|
|
20
|
+
// Rust
|
|
21
|
+
if (exists(cwd, "Cargo.toml")) return "cargo";
|
|
22
|
+
|
|
23
|
+
// Go
|
|
24
|
+
if (exists(cwd, "go.mod")) return "go";
|
|
25
|
+
|
|
26
|
+
// PHP
|
|
27
|
+
if (exists(cwd, "composer.json")) return "composer";
|
|
28
|
+
|
|
29
|
+
// fallback for JS
|
|
30
|
+
if (exists(cwd, "package.json")) return "npm";
|
|
31
|
+
|
|
14
32
|
return "npm";
|
|
15
33
|
}
|
|
16
34
|
|