@dawitworku/projectcli 0.1.0
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/LICENSE +21 -0
- package/README.md +75 -0
- package/bin/projectcli.js +9 -0
- package/package.json +32 -0
- package/src/add.js +263 -0
- package/src/detect.js +44 -0
- package/src/index.js +513 -0
- package/src/libraries.js +72 -0
- package/src/pm.js +44 -0
- package/src/registry.js +515 -0
- package/src/run.js +77 -0
package/src/index.js
ADDED
|
@@ -0,0 +1,513 @@
|
|
|
1
|
+
const path = require("node:path");
|
|
2
|
+
const fs = require("node:fs");
|
|
3
|
+
const os = require("node:os");
|
|
4
|
+
const inquirerImport = require("inquirer");
|
|
5
|
+
const inquirer = inquirerImport.default ?? inquirerImport;
|
|
6
|
+
const prompt =
|
|
7
|
+
typeof inquirer?.prompt === "function"
|
|
8
|
+
? inquirer.prompt.bind(inquirer)
|
|
9
|
+
: typeof inquirer?.createPromptModule === "function"
|
|
10
|
+
? inquirer.createPromptModule()
|
|
11
|
+
: null;
|
|
12
|
+
|
|
13
|
+
let hasAutocomplete = false;
|
|
14
|
+
try {
|
|
15
|
+
if (typeof inquirer?.registerPrompt === "function") {
|
|
16
|
+
const autocomplete = require("inquirer-autocomplete-prompt");
|
|
17
|
+
inquirer.registerPrompt("autocomplete", autocomplete);
|
|
18
|
+
hasAutocomplete = true;
|
|
19
|
+
}
|
|
20
|
+
} catch {
|
|
21
|
+
hasAutocomplete = false;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const { getLanguages, getFrameworks, getGenerator } = require("./registry");
|
|
25
|
+
const { runSteps } = require("./run");
|
|
26
|
+
const { runAdd } = require("./add");
|
|
27
|
+
|
|
28
|
+
function isSafeProjectName(name) {
|
|
29
|
+
// Avoid path traversal / empty names; keep permissive.
|
|
30
|
+
if (!name) return false;
|
|
31
|
+
if (name.includes("..")) return false;
|
|
32
|
+
if (name.includes(path.sep)) return false;
|
|
33
|
+
if (name.includes("/")) return false;
|
|
34
|
+
if (name.includes("\\")) return false;
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function readPackageVersion() {
|
|
39
|
+
try {
|
|
40
|
+
const pkgPath = path.resolve(__dirname, "..", "package.json");
|
|
41
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
42
|
+
return pkg.version || "0.0.0";
|
|
43
|
+
} catch {
|
|
44
|
+
return "0.0.0";
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function parseArgs(argv) {
|
|
49
|
+
const out = {
|
|
50
|
+
help: false,
|
|
51
|
+
version: false,
|
|
52
|
+
list: false,
|
|
53
|
+
yes: false,
|
|
54
|
+
dryRun: false,
|
|
55
|
+
language: undefined,
|
|
56
|
+
framework: undefined,
|
|
57
|
+
name: undefined,
|
|
58
|
+
pm: undefined,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const nextValue = (i) => {
|
|
62
|
+
if (i + 1 >= argv.length) return undefined;
|
|
63
|
+
return argv[i + 1];
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
for (let i = 0; i < argv.length; i++) {
|
|
67
|
+
const a = argv[i];
|
|
68
|
+
if (a === "--help" || a === "-h") out.help = true;
|
|
69
|
+
else if (a === "--version" || a === "-v") out.version = true;
|
|
70
|
+
else if (a === "--list") out.list = true;
|
|
71
|
+
else if (a === "--yes" || a === "-y") out.yes = true;
|
|
72
|
+
else if (a === "--dry-run") out.dryRun = true;
|
|
73
|
+
else if (a.startsWith("--language="))
|
|
74
|
+
out.language = a.slice("--language=".length);
|
|
75
|
+
else if (a === "--language") {
|
|
76
|
+
out.language = nextValue(i);
|
|
77
|
+
i++;
|
|
78
|
+
} else if (a.startsWith("--framework="))
|
|
79
|
+
out.framework = a.slice("--framework=".length);
|
|
80
|
+
else if (a === "--framework") {
|
|
81
|
+
out.framework = nextValue(i);
|
|
82
|
+
i++;
|
|
83
|
+
} else if (a.startsWith("--name=")) out.name = a.slice("--name=".length);
|
|
84
|
+
else if (a === "--name") {
|
|
85
|
+
out.name = nextValue(i);
|
|
86
|
+
i++;
|
|
87
|
+
} else if (a.startsWith("--pm=")) out.pm = a.slice("--pm=".length);
|
|
88
|
+
else if (a === "--pm" || a === "--package-manager") {
|
|
89
|
+
out.pm = nextValue(i);
|
|
90
|
+
i++;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return out;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function splitCommand(argv) {
|
|
98
|
+
if (!argv || argv.length === 0) return { cmd: "init", rest: [] };
|
|
99
|
+
const first = argv[0];
|
|
100
|
+
if (typeof first === "string" && !first.startsWith("-")) {
|
|
101
|
+
if (first === "init" || first === "add") {
|
|
102
|
+
return { cmd: first, rest: argv.slice(1) };
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return { cmd: "init", rest: argv };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function printHelp() {
|
|
109
|
+
console.log("projectcli - interactive project generator");
|
|
110
|
+
console.log("");
|
|
111
|
+
console.log("Usage:");
|
|
112
|
+
console.log(" projectcli # init a new project");
|
|
113
|
+
console.log(" projectcli init # init a new project");
|
|
114
|
+
console.log(" projectcli add # add libraries to current project");
|
|
115
|
+
console.log(" projectcli --list # list all frameworks");
|
|
116
|
+
console.log(
|
|
117
|
+
" projectcli --language <lang> --framework <fw> --name <project>"
|
|
118
|
+
);
|
|
119
|
+
console.log("");
|
|
120
|
+
console.log("Flags:");
|
|
121
|
+
console.log(" --help, -h Show help");
|
|
122
|
+
console.log(" --version, -v Show version");
|
|
123
|
+
console.log(" --list List available languages/frameworks");
|
|
124
|
+
console.log(" --yes, -y Skip confirmation");
|
|
125
|
+
console.log(" --dry-run Print planned actions, do nothing");
|
|
126
|
+
console.log(" --language Preselect language");
|
|
127
|
+
console.log(" --framework Preselect framework");
|
|
128
|
+
console.log(" --name Project folder name");
|
|
129
|
+
console.log(
|
|
130
|
+
" --pm Package manager for JS/TS (npm|pnpm|yarn|bun)"
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const BACK = "__back__";
|
|
135
|
+
|
|
136
|
+
function withBack(choices) {
|
|
137
|
+
return [
|
|
138
|
+
{ name: "← Back", value: BACK },
|
|
139
|
+
new inquirer.Separator(),
|
|
140
|
+
...choices,
|
|
141
|
+
];
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function printStepsPreview(steps) {
|
|
145
|
+
console.log("\nPlanned actions:");
|
|
146
|
+
for (const step of steps) {
|
|
147
|
+
const type = step.type || "command";
|
|
148
|
+
if (type === "command") {
|
|
149
|
+
const where = step.cwdFromProjectRoot ? "(in project)" : "(here)";
|
|
150
|
+
console.log(`- ${step.program} ${step.args.join(" ")} ${where}`);
|
|
151
|
+
} else if (type === "mkdir") {
|
|
152
|
+
console.log(`- mkdir -p ${step.path}`);
|
|
153
|
+
} else if (type === "writeFile") {
|
|
154
|
+
console.log(`- write ${step.path}`);
|
|
155
|
+
} else {
|
|
156
|
+
console.log(`- ${type}`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
console.log("");
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function printList() {
|
|
163
|
+
for (const lang of getLanguages()) {
|
|
164
|
+
console.log(`${lang}:`);
|
|
165
|
+
for (const fw of getFrameworks(lang)) {
|
|
166
|
+
const gen = getGenerator(lang, fw);
|
|
167
|
+
const note = gen?.notes ? ` - ${gen.notes}` : "";
|
|
168
|
+
console.log(` - ${fw}${note}`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async function main(options = {}) {
|
|
174
|
+
if (!prompt) {
|
|
175
|
+
throw new Error(
|
|
176
|
+
"Unable to initialize prompts (inquirer import shape not supported)."
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const argv = options.argv || [];
|
|
181
|
+
const { cmd, rest } = splitCommand(argv);
|
|
182
|
+
const args = parseArgs(rest);
|
|
183
|
+
if (args.help) {
|
|
184
|
+
printHelp();
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
if (args.version) {
|
|
188
|
+
console.log(readPackageVersion());
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
if (args.list) {
|
|
192
|
+
printList();
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (cmd === "add") {
|
|
197
|
+
await runAdd({ prompt, argv: rest });
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
console.log("\nprojectcli");
|
|
202
|
+
console.log("Create a project in seconds.");
|
|
203
|
+
console.log(`Host: ${os.platform()} ${os.arch()}`);
|
|
204
|
+
console.log("Tip: use ↑/↓, Enter, and type-to-search when available.");
|
|
205
|
+
console.log("Tip: Ctrl+C anytime to quit.\n");
|
|
206
|
+
|
|
207
|
+
const languages = getLanguages();
|
|
208
|
+
if (languages.length === 0) {
|
|
209
|
+
throw new Error("No languages configured.");
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const allowedPms = ["npm", "pnpm", "yarn", "bun"];
|
|
213
|
+
const preselectedPm =
|
|
214
|
+
typeof args.pm === "string" && allowedPms.includes(args.pm)
|
|
215
|
+
? args.pm
|
|
216
|
+
: undefined;
|
|
217
|
+
|
|
218
|
+
const state = {
|
|
219
|
+
language:
|
|
220
|
+
args.language && languages.includes(args.language)
|
|
221
|
+
? args.language
|
|
222
|
+
: undefined,
|
|
223
|
+
framework: undefined,
|
|
224
|
+
pm: preselectedPm,
|
|
225
|
+
name: args.name && isSafeProjectName(args.name) ? args.name : undefined,
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
if (state.language) {
|
|
229
|
+
const frameworksForLanguage = getFrameworks(state.language);
|
|
230
|
+
state.framework =
|
|
231
|
+
args.framework && frameworksForLanguage.includes(args.framework)
|
|
232
|
+
? args.framework
|
|
233
|
+
: undefined;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const needsPackageManager =
|
|
237
|
+
state.language === "JavaScript" || state.language === "TypeScript";
|
|
238
|
+
|
|
239
|
+
let step = "language";
|
|
240
|
+
if (!state.language) step = "language";
|
|
241
|
+
else if (!state.framework) step = "framework";
|
|
242
|
+
else if (needsPackageManager && !state.pm) step = "pm";
|
|
243
|
+
else if (!state.name) step = "name";
|
|
244
|
+
else step = "confirm";
|
|
245
|
+
|
|
246
|
+
while (true) {
|
|
247
|
+
if (step === "language") {
|
|
248
|
+
const languageChoices = languages.map((lang) => {
|
|
249
|
+
const count = getFrameworks(lang).length;
|
|
250
|
+
return { name: `${lang} (${count})`, value: lang, short: lang };
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
const { language } = await prompt([
|
|
254
|
+
{
|
|
255
|
+
type: "list",
|
|
256
|
+
name: "language",
|
|
257
|
+
message: "Language:",
|
|
258
|
+
choices: languageChoices,
|
|
259
|
+
pageSize: 12,
|
|
260
|
+
},
|
|
261
|
+
]);
|
|
262
|
+
|
|
263
|
+
state.language = language;
|
|
264
|
+
state.framework = undefined;
|
|
265
|
+
if (!preselectedPm) state.pm = undefined;
|
|
266
|
+
step = "framework";
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (step === "framework") {
|
|
271
|
+
const frameworks = getFrameworks(state.language);
|
|
272
|
+
if (frameworks.length === 0) {
|
|
273
|
+
throw new Error(`No frameworks configured for ${state.language}.`);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const frameworkChoices = frameworks.map((fw) => {
|
|
277
|
+
const gen = getGenerator(state.language, fw);
|
|
278
|
+
const note = gen?.notes ? ` — ${gen.notes}` : "";
|
|
279
|
+
return { name: `${fw}${note}`, value: fw, short: fw };
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
const frameworkQuestion =
|
|
283
|
+
hasAutocomplete && frameworkChoices.length > 12
|
|
284
|
+
? {
|
|
285
|
+
type: "autocomplete",
|
|
286
|
+
name: "framework",
|
|
287
|
+
message: "Framework (type to search):",
|
|
288
|
+
pageSize: 12,
|
|
289
|
+
source: async (_answersSoFar, input) => {
|
|
290
|
+
const q = String(input || "")
|
|
291
|
+
.toLowerCase()
|
|
292
|
+
.trim();
|
|
293
|
+
if (!q) return withBack(frameworkChoices);
|
|
294
|
+
return withBack(
|
|
295
|
+
frameworkChoices.filter((c) =>
|
|
296
|
+
String(c.name).toLowerCase().includes(q)
|
|
297
|
+
)
|
|
298
|
+
);
|
|
299
|
+
},
|
|
300
|
+
}
|
|
301
|
+
: {
|
|
302
|
+
type: "list",
|
|
303
|
+
name: "framework",
|
|
304
|
+
message: "Framework:",
|
|
305
|
+
choices: withBack(frameworkChoices),
|
|
306
|
+
pageSize: 12,
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
const answer = await prompt([frameworkQuestion]);
|
|
310
|
+
if (answer.framework === BACK) {
|
|
311
|
+
step = "language";
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
state.framework = answer.framework;
|
|
316
|
+
step = "pm";
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (step === "pm") {
|
|
321
|
+
const needsPackageManager =
|
|
322
|
+
state.language === "JavaScript" || state.language === "TypeScript";
|
|
323
|
+
|
|
324
|
+
if (!needsPackageManager) {
|
|
325
|
+
state.pm = undefined;
|
|
326
|
+
step = "name";
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (state.pm) {
|
|
331
|
+
step = "name";
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const { pm } = await prompt([
|
|
336
|
+
{
|
|
337
|
+
type: "list",
|
|
338
|
+
name: "pm",
|
|
339
|
+
message: "Package manager:",
|
|
340
|
+
choices: withBack([
|
|
341
|
+
{ name: "npm (default)", value: "npm" },
|
|
342
|
+
{ name: "pnpm", value: "pnpm" },
|
|
343
|
+
{ name: "yarn", value: "yarn" },
|
|
344
|
+
{ name: "bun", value: "bun" },
|
|
345
|
+
]),
|
|
346
|
+
pageSize: 10,
|
|
347
|
+
},
|
|
348
|
+
]);
|
|
349
|
+
|
|
350
|
+
if (pm === BACK) {
|
|
351
|
+
step = "framework";
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
state.pm = pm;
|
|
356
|
+
step = "name";
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (step === "name") {
|
|
361
|
+
if (state.name) {
|
|
362
|
+
step = "confirm";
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const { projectName } = await prompt([
|
|
367
|
+
{
|
|
368
|
+
type: "input",
|
|
369
|
+
name: "projectName",
|
|
370
|
+
message: "Project folder name (or type 'back'):",
|
|
371
|
+
validate: (input) => {
|
|
372
|
+
const v = String(input || "").trim();
|
|
373
|
+
if (v.toLowerCase() === "back") return true;
|
|
374
|
+
if (!isSafeProjectName(v)) {
|
|
375
|
+
return "Use a simple folder name (no slashes).";
|
|
376
|
+
}
|
|
377
|
+
const target = path.resolve(process.cwd(), v);
|
|
378
|
+
if (fs.existsSync(target)) {
|
|
379
|
+
return "That folder already exists. Pick a different name.";
|
|
380
|
+
}
|
|
381
|
+
return true;
|
|
382
|
+
},
|
|
383
|
+
},
|
|
384
|
+
]);
|
|
385
|
+
|
|
386
|
+
const v = String(projectName || "").trim();
|
|
387
|
+
if (v.toLowerCase() === "back") {
|
|
388
|
+
step =
|
|
389
|
+
state.language === "JavaScript" || state.language === "TypeScript"
|
|
390
|
+
? "pm"
|
|
391
|
+
: "framework";
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
state.name = v;
|
|
396
|
+
step = "confirm";
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
if (step === "confirm") {
|
|
401
|
+
const generator = getGenerator(state.language, state.framework);
|
|
402
|
+
if (!generator) {
|
|
403
|
+
throw new Error("Generator not found (registry mismatch).");
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const projectRoot = path.resolve(process.cwd(), state.name);
|
|
407
|
+
const targetExists = fs.existsSync(projectRoot);
|
|
408
|
+
if (targetExists && !args.dryRun) {
|
|
409
|
+
console.error(`\nError: Target folder already exists: ${projectRoot}`);
|
|
410
|
+
state.name = undefined;
|
|
411
|
+
step = "name";
|
|
412
|
+
continue;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
const summaryLines = [
|
|
416
|
+
`Project: ${state.name}`,
|
|
417
|
+
`Language: ${state.language}`,
|
|
418
|
+
`Framework: ${state.framework}`,
|
|
419
|
+
];
|
|
420
|
+
if (state.pm) summaryLines.push(`Package manager: ${state.pm}`);
|
|
421
|
+
console.log("\n" + summaryLines.join("\n") + "\n");
|
|
422
|
+
|
|
423
|
+
if (targetExists && args.dryRun) {
|
|
424
|
+
console.log(`Note: target folder already exists: ${projectRoot}`);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const steps = generator.commands({
|
|
428
|
+
projectName: state.name,
|
|
429
|
+
packageManager: state.pm,
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
if (args.dryRun) {
|
|
433
|
+
printStepsPreview(steps);
|
|
434
|
+
console.log("Dry run: nothing executed.");
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
if (!args.yes) {
|
|
439
|
+
const { action } = await prompt([
|
|
440
|
+
{
|
|
441
|
+
type: "list",
|
|
442
|
+
name: "action",
|
|
443
|
+
message: "Continue?",
|
|
444
|
+
choices: [
|
|
445
|
+
{ name: "Create project", value: "create" },
|
|
446
|
+
{ name: "← Back", value: "back" },
|
|
447
|
+
{ name: "Cancel", value: "cancel" },
|
|
448
|
+
],
|
|
449
|
+
pageSize: 6,
|
|
450
|
+
},
|
|
451
|
+
]);
|
|
452
|
+
|
|
453
|
+
if (action === "cancel") return;
|
|
454
|
+
if (action === "back") {
|
|
455
|
+
step = "name";
|
|
456
|
+
continue;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const needsCwd = steps.some((s) => s.cwdFromProjectRoot);
|
|
461
|
+
if (needsCwd) {
|
|
462
|
+
fs.mkdirSync(projectRoot, { recursive: true });
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
console.log(`\nCreating: ${state.name}`);
|
|
466
|
+
if (generator.notes) console.log(`Note: ${generator.notes}`);
|
|
467
|
+
|
|
468
|
+
try {
|
|
469
|
+
await runSteps(steps, { projectRoot });
|
|
470
|
+
} catch (err) {
|
|
471
|
+
const message = err && err.message ? err.message : String(err);
|
|
472
|
+
console.error(`\nError: ${message}`);
|
|
473
|
+
|
|
474
|
+
if (args.yes) {
|
|
475
|
+
throw err;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
const { next } = await prompt([
|
|
479
|
+
{
|
|
480
|
+
type: "list",
|
|
481
|
+
name: "next",
|
|
482
|
+
message: "What next?",
|
|
483
|
+
choices: [
|
|
484
|
+
{ name: "Try again", value: "retry" },
|
|
485
|
+
{ name: "← Back", value: "back" },
|
|
486
|
+
{ name: "Cancel", value: "cancel" },
|
|
487
|
+
],
|
|
488
|
+
pageSize: 6,
|
|
489
|
+
},
|
|
490
|
+
]);
|
|
491
|
+
|
|
492
|
+
if (next === "cancel") return;
|
|
493
|
+
if (next === "back") {
|
|
494
|
+
step = "name";
|
|
495
|
+
continue;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// retry
|
|
499
|
+
step = "confirm";
|
|
500
|
+
continue;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
console.log(`\nDone. Created project in: ${projectRoot}`);
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
throw new Error(`Unknown wizard step: ${step}`);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
module.exports = {
|
|
512
|
+
main,
|
|
513
|
+
};
|
package/src/libraries.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
const JS_TS = {
|
|
2
|
+
"UI / Components": [
|
|
3
|
+
{
|
|
4
|
+
label: "Tailwind CSS",
|
|
5
|
+
packagesDev: ["tailwindcss", "postcss", "autoprefixer"],
|
|
6
|
+
post: "tailwind-init",
|
|
7
|
+
},
|
|
8
|
+
{ label: "shadcn/ui (Next/Vite)", packagesDev: [], post: "shadcn-init" },
|
|
9
|
+
{
|
|
10
|
+
label: "Material UI (MUI)",
|
|
11
|
+
packages: ["@mui/material", "@emotion/react", "@emotion/styled"],
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
label: "Chakra UI",
|
|
15
|
+
packages: [
|
|
16
|
+
"@chakra-ui/react",
|
|
17
|
+
"@emotion/react",
|
|
18
|
+
"@emotion/styled",
|
|
19
|
+
"framer-motion",
|
|
20
|
+
],
|
|
21
|
+
},
|
|
22
|
+
{ label: "Ant Design", packages: ["antd"] },
|
|
23
|
+
{ label: "Mantine", packages: ["@mantine/core", "@mantine/hooks"] },
|
|
24
|
+
{ label: "Radix UI (primitives)", packages: ["@radix-ui/react-dialog"] },
|
|
25
|
+
{ label: "DaisyUI (Tailwind plugin)", packagesDev: ["daisyui"] },
|
|
26
|
+
],
|
|
27
|
+
"UX / Animation": [
|
|
28
|
+
{ label: "Framer Motion", packages: ["framer-motion"] },
|
|
29
|
+
{ label: "Lottie", packages: ["lottie-react"] },
|
|
30
|
+
],
|
|
31
|
+
"Forms / Validation": [
|
|
32
|
+
{ label: "React Hook Form", packages: ["react-hook-form"] },
|
|
33
|
+
{ label: "Zod", packages: ["zod"] },
|
|
34
|
+
{ label: "Yup", packages: ["yup"] },
|
|
35
|
+
],
|
|
36
|
+
"Data / State": [
|
|
37
|
+
{ label: "TanStack Query", packages: ["@tanstack/react-query"] },
|
|
38
|
+
{ label: "Zustand", packages: ["zustand"] },
|
|
39
|
+
{ label: "Redux Toolkit", packages: ["@reduxjs/toolkit", "react-redux"] },
|
|
40
|
+
],
|
|
41
|
+
Testing: [
|
|
42
|
+
{ label: "Vitest", packagesDev: ["vitest"] },
|
|
43
|
+
{
|
|
44
|
+
label: "Playwright",
|
|
45
|
+
packagesDev: ["@playwright/test"],
|
|
46
|
+
post: "playwright-install",
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const PY = {
|
|
52
|
+
"TUI / UI": [
|
|
53
|
+
{ label: "Rich", packages: ["rich"] },
|
|
54
|
+
{ label: "Typer (CLI)", packages: ["typer"] },
|
|
55
|
+
{ label: "Textual", packages: ["textual"] },
|
|
56
|
+
],
|
|
57
|
+
Web: [
|
|
58
|
+
{ label: "FastAPI", packages: ["fastapi", "uvicorn"] },
|
|
59
|
+
{ label: "Flask", packages: ["flask"] },
|
|
60
|
+
{ label: "Django", packages: ["django"] },
|
|
61
|
+
],
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
function getCatalog(language) {
|
|
65
|
+
if (language === "JavaScript/TypeScript") return JS_TS;
|
|
66
|
+
if (language === "Python") return PY;
|
|
67
|
+
return {};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
module.exports = {
|
|
71
|
+
getCatalog,
|
|
72
|
+
};
|
package/src/pm.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
function pmInstallCommand(pm) {
|
|
2
|
+
if (pm === "pnpm") return { program: "pnpm", args: ["install"] };
|
|
3
|
+
if (pm === "yarn") return { program: "yarn", args: ["install"] };
|
|
4
|
+
if (pm === "bun") return { program: "bun", args: ["install"] };
|
|
5
|
+
return { program: "npm", args: ["install"] };
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function pmAddCommand(pm, packages, { dev = false } = {}) {
|
|
9
|
+
const pkgs = Array.isArray(packages) ? packages.filter(Boolean) : [];
|
|
10
|
+
if (pkgs.length === 0) {
|
|
11
|
+
throw new Error("No packages to install.");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (pm === "pnpm") {
|
|
15
|
+
return { program: "pnpm", args: ["add", ...(dev ? ["-D"] : []), ...pkgs] };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (pm === "yarn") {
|
|
19
|
+
return { program: "yarn", args: ["add", ...(dev ? ["-D"] : []), ...pkgs] };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (pm === "bun") {
|
|
23
|
+
return { program: "bun", args: ["add", ...(dev ? ["-d"] : []), ...pkgs] };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return { program: "npm", args: ["install", ...(dev ? ["-D"] : []), ...pkgs] };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function pmExecCommand(pm, pkg, pkgArgs) {
|
|
30
|
+
const args = Array.isArray(pkgArgs) ? pkgArgs : [];
|
|
31
|
+
|
|
32
|
+
if (pm === "pnpm") return { program: "pnpm", args: ["dlx", pkg, ...args] };
|
|
33
|
+
if (pm === "yarn") return { program: "yarn", args: ["dlx", pkg, ...args] };
|
|
34
|
+
if (pm === "bun") return { program: "bunx", args: [pkg, ...args] };
|
|
35
|
+
|
|
36
|
+
// npm default
|
|
37
|
+
return { program: "npx", args: ["--yes", pkg, ...args] };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
module.exports = {
|
|
41
|
+
pmInstallCommand,
|
|
42
|
+
pmAddCommand,
|
|
43
|
+
pmExecCommand,
|
|
44
|
+
};
|