@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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dawitworku/projectcli",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Interactive project generator (language -> framework -> create).",
5
5
  "license": "MIT",
6
6
  "type": "commonjs",
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(`- ${step.program} ${step.args.join(" ")} ${where}`);
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(`- mkdir -p ${step.path}`);
39
+ console.log(chalk.gray("- ") + chalk.yellow(`mkdir -p ${step.path}`));
32
40
  } else if (type === "writeFile") {
33
- console.log(`- write ${step.path}`);
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
- if (language === "JavaScript/TypeScript") {
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 selectedCategory = null;
68
- let selectedLabels = null;
90
+ let selectedItems = [];
69
91
 
70
92
  while (true) {
71
- if (!selectedCategory) {
72
- const categoryChoices = [
73
- ...categories.map((c) => ({
74
- name: `${c} (${catalog[c].length})`,
75
- value: c,
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
- const { category } = await prompt([
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: "list",
84
- name: "category",
85
- message: "Category:",
86
- choices: categoryChoices,
87
- pageSize: 14,
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
- if (category === EXIT) return;
92
- selectedCategory = category;
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 (!selectedLabels) {
97
- const items = catalog[selectedCategory] || [];
98
- const { picks } = await prompt([
147
+ if (action === "BROWSE") {
148
+ const { category } = await prompt([
99
149
  {
100
- type: "checkbox",
101
- name: "picks",
102
- message: "Select libraries (space to toggle):",
103
- choices: items.map((i) => ({ name: i.label, value: i.label })),
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
- selectedLabels = picks;
111
-
112
- const { next } = await prompt([
158
+ const items = catalog[category];
159
+ const { picked } = await prompt([
113
160
  {
114
- type: "list",
115
- name: "next",
116
- message: "Next:",
117
- choices: [
118
- { name: "Continue", value: "continue" },
119
- { name: "← Back", value: BACK },
120
- { name: "Cancel", value: EXIT },
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
- if (next === EXIT) return;
127
- if (next === BACK) {
128
- selectedLabels = null;
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
- if (flags.dryRun) {
179
- printStepsPreview(steps);
180
- console.log("Dry run: nothing executed.");
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
- if (language === "Python") {
205
- const all = uniq([...packages, ...packagesDev]);
206
- const { mode } = await prompt([
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
- const reqPath = path.join(cwd, "requirements.txt");
226
- const toAppend = all.map((p) => `${p}\n`).join("");
194
+ const steps = [];
227
195
 
228
- if (flags.dryRun) {
229
- console.log("\nPlanned actions:");
230
- if (mode === "pip") {
231
- console.log(`- python -m pip install ${all.join(" ")} (here)`);
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
- if (mode === "pip") {
239
- await runSteps(
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
- fs.appendFileSync(reqPath, toAppend, "utf8");
253
- console.log("\nDone. Updated requirements.txt");
254
- return;
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
- throw new Error(`Add mode not implemented yet for: ${language}`);
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
- // fallback
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