@dawitworku/projectcli 0.2.2 → 3.0.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/CHANGELOG.md +29 -0
- package/README.md +98 -0
- package/ROADMAP.md +23 -0
- package/docs/demo.cast +445 -0
- package/docs/demo.svg +254637 -0
- package/package.json +13 -3
- package/scripts/release-notes.js +34 -0
- package/src/add.js +164 -1
- package/src/config.js +66 -0
- package/src/core/doctor/index.js +504 -0
- package/src/index.js +184 -37
- package/src/plugin.js +140 -0
- package/src/plugins/contributions.js +101 -0
- package/src/plugins/manager.js +38 -0
- package/src/preflight.js +26 -0
- package/src/preset.js +59 -0
- package/src/presets/index.js +124 -0
- package/src/registry/index.js +91 -0
- package/src/registry.js +3 -909
- package/src/registry_legacy.js +911 -0
- package/src/settings.js +11 -0
- package/src/upgrade.js +228 -0
package/src/index.js
CHANGED
|
@@ -28,15 +28,20 @@ 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");
|
|
31
|
+
const { checkBinaries, getInstallHint } = require("./preflight");
|
|
32
32
|
const { gitClone, removeGitFolder } = require("./remote");
|
|
33
33
|
const { generateCI, generateDocker } = require("./cicd");
|
|
34
34
|
const { generateDevContainer } = require("./devcontainer");
|
|
35
35
|
const { generateLicense, licenseTypes } = require("./license");
|
|
36
36
|
const { getDescription } = require("./descriptions");
|
|
37
37
|
const { detectLanguage, detectPackageManager } = require("./detect");
|
|
38
|
-
const { loadConfig } = require("./config");
|
|
38
|
+
const { loadConfig, loadProjectConfig } = require("./config");
|
|
39
39
|
const { runConfig } = require("./settings");
|
|
40
|
+
const { runDoctor } = require("./core/doctor");
|
|
41
|
+
const { runPreset } = require("./preset");
|
|
42
|
+
const { getPreset } = require("./presets");
|
|
43
|
+
const { runUpgrade } = require("./upgrade");
|
|
44
|
+
const { runPlugin } = require("./plugin");
|
|
40
45
|
|
|
41
46
|
const RUST_KEYWORDS = new Set(
|
|
42
47
|
[
|
|
@@ -197,7 +202,15 @@ function splitCommand(argv) {
|
|
|
197
202
|
if (!argv || argv.length === 0) return { cmd: "init", rest: [] };
|
|
198
203
|
const first = argv[0];
|
|
199
204
|
if (typeof first === "string" && !first.startsWith("-")) {
|
|
200
|
-
if (
|
|
205
|
+
if (
|
|
206
|
+
first === "init" ||
|
|
207
|
+
first === "add" ||
|
|
208
|
+
first === "config" ||
|
|
209
|
+
first === "doctor" ||
|
|
210
|
+
first === "upgrade" ||
|
|
211
|
+
first === "preset" ||
|
|
212
|
+
first === "plugin"
|
|
213
|
+
) {
|
|
201
214
|
return { cmd: first, rest: argv.slice(1) };
|
|
202
215
|
}
|
|
203
216
|
}
|
|
@@ -211,11 +224,22 @@ function printHelp() {
|
|
|
211
224
|
console.log(" projectcli # init a new project");
|
|
212
225
|
console.log(" projectcli init # init a new project");
|
|
213
226
|
console.log(" projectcli add # add libraries to current project");
|
|
227
|
+
console.log(" projectcli add ci # add GitHub Actions CI");
|
|
228
|
+
console.log(" projectcli add docker # add Dockerfile");
|
|
229
|
+
console.log(" projectcli add devcontainer # add VS Code devcontainer");
|
|
230
|
+
console.log(" projectcli add license # add LICENSE");
|
|
231
|
+
console.log(" projectcli add lint # add linter defaults");
|
|
232
|
+
console.log(" projectcli add test # add test runner defaults");
|
|
233
|
+
console.log(" projectcli doctor # check a repo and optionally fix");
|
|
234
|
+
console.log(" projectcli preset # manage presets");
|
|
235
|
+
console.log(" projectcli upgrade # upgrade configs safely");
|
|
236
|
+
console.log(" projectcli plugin # manage plugins");
|
|
214
237
|
console.log(" projectcli --list # list all frameworks");
|
|
215
238
|
console.log(
|
|
216
239
|
" projectcli --language <lang> --framework <fw> --name <project>"
|
|
217
240
|
);
|
|
218
241
|
console.log(" projectcli config # configure defaults");
|
|
242
|
+
console.log(" # config files: .projectclirc or projectcli.config.json");
|
|
219
243
|
console.log("");
|
|
220
244
|
console.log("Flags:");
|
|
221
245
|
console.log(" --help, -h Show help");
|
|
@@ -236,6 +260,23 @@ function printHelp() {
|
|
|
236
260
|
console.log(" --no-license Never add LICENSE");
|
|
237
261
|
console.log(" --learning Enable learning mode (shows descriptions)");
|
|
238
262
|
console.log(" --template Clone from a Git repository URL");
|
|
263
|
+
console.log("");
|
|
264
|
+
console.log("Doctor flags:");
|
|
265
|
+
console.log(" --fix Apply safe fixes");
|
|
266
|
+
console.log(" --json JSON output (CI-friendly)");
|
|
267
|
+
console.log(" --ci-only Only check CI");
|
|
268
|
+
|
|
269
|
+
console.log("");
|
|
270
|
+
console.log("Upgrade flags:");
|
|
271
|
+
console.log(" --preview Show what would change");
|
|
272
|
+
console.log(" --only ci Upgrade only CI templates");
|
|
273
|
+
console.log(" --only docker Upgrade Docker templates");
|
|
274
|
+
console.log(" --only devcontainer Upgrade devcontainer templates");
|
|
275
|
+
|
|
276
|
+
console.log("");
|
|
277
|
+
console.log("Plugin commands:");
|
|
278
|
+
console.log(" projectcli plugin list");
|
|
279
|
+
console.log(" projectcli plugin install <id>");
|
|
239
280
|
}
|
|
240
281
|
|
|
241
282
|
const BACK = "__back__";
|
|
@@ -287,17 +328,31 @@ function filterExistingWriteFiles(steps, projectRoot) {
|
|
|
287
328
|
return { kept, skipped };
|
|
288
329
|
}
|
|
289
330
|
|
|
290
|
-
function printList() {
|
|
291
|
-
for (const lang of getLanguages()) {
|
|
331
|
+
function printList(effectiveConfig) {
|
|
332
|
+
for (const lang of getLanguages(effectiveConfig)) {
|
|
292
333
|
console.log(`${lang}:`);
|
|
293
|
-
for (const fw of getFrameworks(lang)) {
|
|
294
|
-
const gen = getGenerator(lang, fw);
|
|
334
|
+
for (const fw of getFrameworks(lang, effectiveConfig)) {
|
|
335
|
+
const gen = getGenerator(lang, fw, effectiveConfig);
|
|
295
336
|
const note = gen?.notes ? ` - ${gen.notes}` : "";
|
|
296
|
-
|
|
337
|
+
const stability = gen?.stability ? ` [${gen.stability}]` : "";
|
|
338
|
+
console.log(` - ${fw}${stability}${note}`);
|
|
297
339
|
}
|
|
298
340
|
}
|
|
299
341
|
}
|
|
300
342
|
|
|
343
|
+
function formatStabilityBadge(stability) {
|
|
344
|
+
if (stability === "core") return chalk.green("✅ Core");
|
|
345
|
+
if (stability === "experimental") return chalk.yellow("⚠ Experimental");
|
|
346
|
+
if (stability === "community") return chalk.cyan("👥 Community");
|
|
347
|
+
return null;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function printMissingTool(bin) {
|
|
351
|
+
console.log(chalk.red(`✖ ${bin} not found`));
|
|
352
|
+
const hint = getInstallHint(bin);
|
|
353
|
+
if (hint) console.log(chalk.dim(` → ${hint}`));
|
|
354
|
+
}
|
|
355
|
+
|
|
301
356
|
function showBanner() {
|
|
302
357
|
console.log("");
|
|
303
358
|
console.log(
|
|
@@ -340,13 +395,20 @@ async function main(options = {}) {
|
|
|
340
395
|
console.log(readPackageVersion());
|
|
341
396
|
return;
|
|
342
397
|
}
|
|
398
|
+
|
|
399
|
+
// Config precedence: CLI args > project config > global config (~/.projectcli.json)
|
|
400
|
+
const userConfig = loadConfig();
|
|
401
|
+
const projectConfigInfo = loadProjectConfig(process.cwd());
|
|
402
|
+
const projectConfig = projectConfigInfo.data || {};
|
|
403
|
+
const effectiveConfig = { ...userConfig, ...projectConfig };
|
|
404
|
+
|
|
343
405
|
if (args.list) {
|
|
344
|
-
printList();
|
|
406
|
+
printList(effectiveConfig);
|
|
345
407
|
return;
|
|
346
408
|
}
|
|
347
409
|
|
|
348
410
|
if (cmd === "add") {
|
|
349
|
-
await runAdd({ prompt, argv: rest });
|
|
411
|
+
await runAdd({ prompt, argv: rest, effectiveConfig });
|
|
350
412
|
return;
|
|
351
413
|
}
|
|
352
414
|
|
|
@@ -355,6 +417,26 @@ async function main(options = {}) {
|
|
|
355
417
|
return;
|
|
356
418
|
}
|
|
357
419
|
|
|
420
|
+
if (cmd === "doctor") {
|
|
421
|
+
await runDoctor({ prompt, argv: rest, effectiveConfig });
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
if (cmd === "preset") {
|
|
426
|
+
await runPreset({ prompt, argv: rest, effectiveConfig });
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
if (cmd === "upgrade") {
|
|
431
|
+
await runUpgrade({ prompt, argv: rest });
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
if (cmd === "plugin") {
|
|
436
|
+
await runPlugin({ prompt, argv: rest, effectiveConfig });
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
|
|
358
440
|
// Smart Context Detection
|
|
359
441
|
if (
|
|
360
442
|
cmd === "init" &&
|
|
@@ -452,7 +534,7 @@ async function main(options = {}) {
|
|
|
452
534
|
type: "input",
|
|
453
535
|
name: "author",
|
|
454
536
|
message: "Author Name:",
|
|
455
|
-
default:
|
|
537
|
+
default: effectiveConfig.author || "The Authors",
|
|
456
538
|
},
|
|
457
539
|
]);
|
|
458
540
|
const steps = generateLicense(process.cwd(), type, author);
|
|
@@ -495,40 +577,80 @@ async function main(options = {}) {
|
|
|
495
577
|
chalk.dim(" (Type to search)")
|
|
496
578
|
);
|
|
497
579
|
|
|
498
|
-
const languages = getLanguages();
|
|
580
|
+
const languages = getLanguages(effectiveConfig);
|
|
499
581
|
if (languages.length === 0) {
|
|
500
582
|
throw new Error("No languages configured.");
|
|
501
583
|
}
|
|
502
584
|
|
|
503
|
-
const userConfig = loadConfig();
|
|
504
|
-
|
|
505
585
|
const allowedPms = ["npm", "pnpm", "yarn", "bun"];
|
|
506
586
|
let preselectedPm =
|
|
507
587
|
typeof args.pm === "string" && allowedPms.includes(args.pm)
|
|
508
588
|
? args.pm
|
|
509
589
|
: undefined;
|
|
510
590
|
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
591
|
+
const configuredPm =
|
|
592
|
+
typeof effectiveConfig.packageManager === "string"
|
|
593
|
+
? effectiveConfig.packageManager
|
|
594
|
+
: undefined;
|
|
595
|
+
|
|
596
|
+
if (!preselectedPm && configuredPm) {
|
|
597
|
+
if (allowedPms.includes(configuredPm)) {
|
|
598
|
+
preselectedPm = configuredPm;
|
|
514
599
|
}
|
|
515
600
|
}
|
|
516
601
|
|
|
517
|
-
|
|
602
|
+
const presetId =
|
|
603
|
+
typeof effectiveConfig.preset === "string" && effectiveConfig.preset.trim()
|
|
604
|
+
? effectiveConfig.preset.trim()
|
|
605
|
+
: "startup";
|
|
606
|
+
const preset = getPreset(presetId, effectiveConfig);
|
|
607
|
+
const presetPm =
|
|
608
|
+
typeof preset?.defaults?.packageManager === "string"
|
|
609
|
+
? preset.defaults.packageManager
|
|
610
|
+
: null;
|
|
611
|
+
|
|
612
|
+
if (!preselectedPm && presetPm && allowedPms.includes(presetPm)) {
|
|
613
|
+
preselectedPm = presetPm;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
const learningEnabled =
|
|
617
|
+
typeof effectiveConfig.learningMode === "boolean"
|
|
618
|
+
? effectiveConfig.learningMode
|
|
619
|
+
: typeof effectiveConfig.learning === "boolean"
|
|
620
|
+
? effectiveConfig.learning
|
|
621
|
+
: false;
|
|
622
|
+
|
|
623
|
+
if (learningEnabled && args.learning === false) {
|
|
518
624
|
args.learning = true;
|
|
519
625
|
}
|
|
520
626
|
|
|
521
627
|
const defaultLicenseType =
|
|
522
|
-
typeof
|
|
523
|
-
licenseTypes.includes(
|
|
524
|
-
?
|
|
628
|
+
typeof effectiveConfig.license === "string" &&
|
|
629
|
+
licenseTypes.includes(effectiveConfig.license)
|
|
630
|
+
? effectiveConfig.license
|
|
631
|
+
: typeof effectiveConfig.defaultLicense === "string" &&
|
|
632
|
+
licenseTypes.includes(effectiveConfig.defaultLicense)
|
|
633
|
+
? effectiveConfig.defaultLicense
|
|
525
634
|
: null;
|
|
526
635
|
|
|
527
636
|
const defaultAuthor =
|
|
528
|
-
typeof
|
|
529
|
-
?
|
|
637
|
+
typeof effectiveConfig.author === "string" && effectiveConfig.author.trim()
|
|
638
|
+
? effectiveConfig.author.trim()
|
|
530
639
|
: "The Authors";
|
|
531
640
|
|
|
641
|
+
const defaultCi =
|
|
642
|
+
typeof effectiveConfig.ci === "boolean"
|
|
643
|
+
? effectiveConfig.ci
|
|
644
|
+
: preset?.defaults?.extras?.ci === true;
|
|
645
|
+
const defaultDocker =
|
|
646
|
+
typeof effectiveConfig.docker === "boolean"
|
|
647
|
+
? effectiveConfig.docker
|
|
648
|
+
: preset?.defaults?.extras?.docker === true;
|
|
649
|
+
const defaultDevContainer =
|
|
650
|
+
typeof effectiveConfig.devcontainer === "boolean"
|
|
651
|
+
? effectiveConfig.devcontainer
|
|
652
|
+
: preset?.defaults?.extras?.devcontainer === true;
|
|
653
|
+
|
|
532
654
|
const state = {
|
|
533
655
|
template: args.template || undefined,
|
|
534
656
|
language:
|
|
@@ -541,7 +663,10 @@ async function main(options = {}) {
|
|
|
541
663
|
};
|
|
542
664
|
|
|
543
665
|
if (state.language) {
|
|
544
|
-
const frameworksForLanguage = getFrameworks(
|
|
666
|
+
const frameworksForLanguage = getFrameworks(
|
|
667
|
+
state.language,
|
|
668
|
+
effectiveConfig
|
|
669
|
+
);
|
|
545
670
|
state.framework =
|
|
546
671
|
args.framework && frameworksForLanguage.includes(args.framework)
|
|
547
672
|
? args.framework
|
|
@@ -564,7 +689,7 @@ async function main(options = {}) {
|
|
|
564
689
|
while (true) {
|
|
565
690
|
if (step === "language") {
|
|
566
691
|
const languageChoices = languages.map((lang) => {
|
|
567
|
-
const count = getFrameworks(lang).length;
|
|
692
|
+
const count = getFrameworks(lang, effectiveConfig).length;
|
|
568
693
|
return { name: `${lang} (${count})`, value: lang, short: lang };
|
|
569
694
|
});
|
|
570
695
|
|
|
@@ -603,15 +728,19 @@ async function main(options = {}) {
|
|
|
603
728
|
}
|
|
604
729
|
|
|
605
730
|
if (step === "framework") {
|
|
606
|
-
const frameworks = getFrameworks(state.language);
|
|
731
|
+
const frameworks = getFrameworks(state.language, effectiveConfig);
|
|
607
732
|
if (frameworks.length === 0) {
|
|
608
733
|
throw new Error(`No frameworks configured for ${state.language}.`);
|
|
609
734
|
}
|
|
610
735
|
|
|
611
736
|
const frameworkChoices = frameworks.map((fw) => {
|
|
612
|
-
const gen = getGenerator(state.language, fw);
|
|
737
|
+
const gen = getGenerator(state.language, fw, effectiveConfig);
|
|
613
738
|
const note = gen?.notes ? ` — ${gen.notes}` : "";
|
|
614
|
-
|
|
739
|
+
const badge = gen?.stability
|
|
740
|
+
? formatStabilityBadge(gen.stability)
|
|
741
|
+
: null;
|
|
742
|
+
const badgeText = badge ? ` ${badge}` : "";
|
|
743
|
+
return { name: `${fw}${badgeText}${note}`, value: fw, short: fw };
|
|
615
744
|
});
|
|
616
745
|
|
|
617
746
|
const frameworkQuestion = hasAutocomplete
|
|
@@ -650,7 +779,11 @@ async function main(options = {}) {
|
|
|
650
779
|
state.framework = answer.framework;
|
|
651
780
|
|
|
652
781
|
// Preflight Checks
|
|
653
|
-
const gen = getGenerator(
|
|
782
|
+
const gen = getGenerator(
|
|
783
|
+
state.language,
|
|
784
|
+
state.framework,
|
|
785
|
+
effectiveConfig
|
|
786
|
+
);
|
|
654
787
|
if (gen && gen.check && gen.check.length > 0) {
|
|
655
788
|
/* eslint-disable-next-line no-console */
|
|
656
789
|
console.log(chalk.dim("\n(checking requirements...)"));
|
|
@@ -659,7 +792,7 @@ async function main(options = {}) {
|
|
|
659
792
|
|
|
660
793
|
if (missing.length > 0) {
|
|
661
794
|
console.log(chalk.red.bold("\nMissing required tools:"));
|
|
662
|
-
missing.forEach((m) =>
|
|
795
|
+
missing.forEach((m) => printMissingTool(m.bin));
|
|
663
796
|
console.log(
|
|
664
797
|
chalk.yellow("You may not be able to build or run this project.\n")
|
|
665
798
|
);
|
|
@@ -830,9 +963,10 @@ async function main(options = {}) {
|
|
|
830
963
|
langArg = "JavaScript";
|
|
831
964
|
if (detectedTemplate === "Java/Kotlin") langArg = "Java";
|
|
832
965
|
|
|
833
|
-
let wantCi = Boolean(args.ci);
|
|
834
|
-
let wantDocker = Boolean(args.docker);
|
|
835
|
-
let wantDevContainer =
|
|
966
|
+
let wantCi = Boolean(args.ci) || defaultCi;
|
|
967
|
+
let wantDocker = Boolean(args.docker) || defaultDocker;
|
|
968
|
+
let wantDevContainer =
|
|
969
|
+
Boolean(args.devcontainer) || defaultDevContainer;
|
|
836
970
|
let wantLicense =
|
|
837
971
|
typeof args.license === "boolean"
|
|
838
972
|
? args.license
|
|
@@ -928,7 +1062,14 @@ async function main(options = {}) {
|
|
|
928
1062
|
)
|
|
929
1063
|
);
|
|
930
1064
|
} catch (err) {
|
|
931
|
-
|
|
1065
|
+
const message = err && err.message ? err.message : String(err);
|
|
1066
|
+
console.error(chalk.red("\nFailed to clone template:"), message);
|
|
1067
|
+
if (
|
|
1068
|
+
/\bENOENT\b/i.test(message) ||
|
|
1069
|
+
/command not found/i.test(message)
|
|
1070
|
+
) {
|
|
1071
|
+
printMissingTool("git");
|
|
1072
|
+
}
|
|
932
1073
|
process.exit(1);
|
|
933
1074
|
}
|
|
934
1075
|
return;
|
|
@@ -1020,6 +1161,11 @@ async function main(options = {}) {
|
|
|
1020
1161
|
const message = err && err.message ? err.message : String(err);
|
|
1021
1162
|
console.error(`\nError: ${message}`);
|
|
1022
1163
|
|
|
1164
|
+
const cmdNotFound = /^Command not found:\s*(.+)$/.exec(message);
|
|
1165
|
+
if (cmdNotFound && cmdNotFound[1]) {
|
|
1166
|
+
printMissingTool(cmdNotFound[1].trim());
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1023
1169
|
const looksLikeNameIssue =
|
|
1024
1170
|
/cannot be used as a package name|Rust keyword|keyword|Cargo\.toml/i.test(
|
|
1025
1171
|
message
|
|
@@ -1105,9 +1251,10 @@ async function main(options = {}) {
|
|
|
1105
1251
|
|
|
1106
1252
|
// CI/CD & Docker & DevContainer
|
|
1107
1253
|
if (!args.dryRun) {
|
|
1108
|
-
let wantCi = args.ci;
|
|
1109
|
-
let wantDocker = args.docker;
|
|
1110
|
-
let wantDevContainer =
|
|
1254
|
+
let wantCi = Boolean(args.ci) || defaultCi;
|
|
1255
|
+
let wantDocker = Boolean(args.docker) || defaultDocker;
|
|
1256
|
+
let wantDevContainer =
|
|
1257
|
+
Boolean(args.devcontainer) || defaultDevContainer;
|
|
1111
1258
|
let wantLicense =
|
|
1112
1259
|
typeof args.license === "boolean"
|
|
1113
1260
|
? args.license
|
package/src/plugin.js
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
const chalk = require("chalk");
|
|
2
|
+
|
|
3
|
+
const { loadConfig, saveConfig, CONFIG_PATH } = require("./config");
|
|
4
|
+
const { getEnabledPlugins, tryLoadPlugin } = require("./plugins/manager");
|
|
5
|
+
|
|
6
|
+
function parsePluginArgs(argv) {
|
|
7
|
+
const out = { yes: false, dryRun: false };
|
|
8
|
+
const args = Array.isArray(argv) ? argv : [];
|
|
9
|
+
for (const a of args) {
|
|
10
|
+
if (a === "--yes" || a === "-y") out.yes = true;
|
|
11
|
+
else if (a === "--dry-run") out.dryRun = true;
|
|
12
|
+
}
|
|
13
|
+
return out;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function firstPositional(argv) {
|
|
17
|
+
const args = Array.isArray(argv) ? argv : [];
|
|
18
|
+
for (const a of args) {
|
|
19
|
+
if (typeof a !== "string") continue;
|
|
20
|
+
if (a.startsWith("-")) continue;
|
|
21
|
+
return a;
|
|
22
|
+
}
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function secondPositional(argv) {
|
|
27
|
+
const args = Array.isArray(argv) ? argv : [];
|
|
28
|
+
let seenFirst = false;
|
|
29
|
+
for (const a of args) {
|
|
30
|
+
if (typeof a !== "string") continue;
|
|
31
|
+
if (a.startsWith("-")) continue;
|
|
32
|
+
if (!seenFirst) {
|
|
33
|
+
seenFirst = true;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
return a;
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function printPluginList(effectiveConfig) {
|
|
42
|
+
const enabled = getEnabledPlugins(effectiveConfig);
|
|
43
|
+
|
|
44
|
+
console.log(chalk.bold("\nPlugins:"));
|
|
45
|
+
if (enabled.length === 0) {
|
|
46
|
+
console.log(chalk.dim("(none enabled)"));
|
|
47
|
+
console.log(chalk.dim(`Config: ${CONFIG_PATH}`));
|
|
48
|
+
console.log("");
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
for (const id of enabled) {
|
|
53
|
+
const loaded = tryLoadPlugin(id);
|
|
54
|
+
if (loaded.ok) {
|
|
55
|
+
console.log(`${chalk.green("✓")} ${id}`);
|
|
56
|
+
} else {
|
|
57
|
+
console.log(`${chalk.red("✗")} ${id}`);
|
|
58
|
+
console.log(chalk.dim(` → ${loaded.error}`));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
console.log(chalk.dim(`\nConfig: ${CONFIG_PATH}`));
|
|
62
|
+
console.log("");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function runPlugin({ prompt, argv, effectiveConfig }) {
|
|
66
|
+
const flags = parsePluginArgs(argv);
|
|
67
|
+
const args = Array.isArray(argv) ? argv : [];
|
|
68
|
+
|
|
69
|
+
const sub = firstPositional(args);
|
|
70
|
+
|
|
71
|
+
if (!sub || sub === "list") {
|
|
72
|
+
printPluginList(effectiveConfig);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (sub === "install") {
|
|
77
|
+
const pluginId = secondPositional(args);
|
|
78
|
+
if (!pluginId) {
|
|
79
|
+
throw new Error(
|
|
80
|
+
"Missing plugin id. Example: projectcli plugin install my-company"
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const cfg = loadConfig();
|
|
85
|
+
const current = Array.isArray(cfg.plugins) ? cfg.plugins : [];
|
|
86
|
+
const enabled = new Set(
|
|
87
|
+
current.map((p) => String(p || "").trim()).filter(Boolean)
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
if (enabled.has(pluginId)) {
|
|
91
|
+
console.log(chalk.dim(`\nAlready enabled: ${pluginId}`));
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const loaded = tryLoadPlugin(pluginId);
|
|
96
|
+
if (!loaded.ok) {
|
|
97
|
+
console.log(
|
|
98
|
+
chalk.yellow("\nNote: this command does not install packages.")
|
|
99
|
+
);
|
|
100
|
+
console.log(
|
|
101
|
+
chalk.yellow(
|
|
102
|
+
`It only enables a plugin that is already resolvable by Node: ${pluginId}`
|
|
103
|
+
)
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (flags.dryRun) {
|
|
108
|
+
console.log(chalk.bold("\nPlanned actions:"));
|
|
109
|
+
console.log(chalk.gray("- ") + chalk.yellow(`enable plugin ${pluginId}`));
|
|
110
|
+
console.log("\nDry run: nothing executed.");
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (!flags.yes) {
|
|
115
|
+
const { ok } = await prompt([
|
|
116
|
+
{
|
|
117
|
+
type: "confirm",
|
|
118
|
+
name: "ok",
|
|
119
|
+
message: `Enable plugin ${pluginId} in ${CONFIG_PATH}?`,
|
|
120
|
+
default: true,
|
|
121
|
+
},
|
|
122
|
+
]);
|
|
123
|
+
if (!ok) return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
enabled.add(pluginId);
|
|
127
|
+
saveConfig({ plugins: Array.from(enabled) });
|
|
128
|
+
console.log(chalk.green(`\n✓ Enabled plugin: ${pluginId}`));
|
|
129
|
+
console.log(chalk.dim(`Config: ${CONFIG_PATH}`));
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
throw new Error(
|
|
134
|
+
`Unknown plugin subcommand: ${sub}. Try: projectcli plugin list | projectcli plugin install <id>`
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
module.exports = {
|
|
139
|
+
runPlugin,
|
|
140
|
+
};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
const { getEnabledPlugins, tryLoadPlugin } = require("./manager");
|
|
2
|
+
|
|
3
|
+
function loadEnabledPlugins(effectiveConfig) {
|
|
4
|
+
const enabled = getEnabledPlugins(effectiveConfig);
|
|
5
|
+
const plugins = [];
|
|
6
|
+
const errors = [];
|
|
7
|
+
|
|
8
|
+
for (const id of enabled) {
|
|
9
|
+
const res = tryLoadPlugin(id);
|
|
10
|
+
if (res.ok) {
|
|
11
|
+
plugins.push({ id, module: res.module });
|
|
12
|
+
} else {
|
|
13
|
+
errors.push({ id, error: res.error || "Failed to load plugin" });
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return { plugins, errors };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function getPluginPresets(effectiveConfig) {
|
|
21
|
+
const { plugins } = loadEnabledPlugins(effectiveConfig);
|
|
22
|
+
const out = [];
|
|
23
|
+
|
|
24
|
+
for (const p of plugins) {
|
|
25
|
+
const mod = p.module || {};
|
|
26
|
+
const presets = mod.presets;
|
|
27
|
+
if (!presets) continue;
|
|
28
|
+
|
|
29
|
+
if (Array.isArray(presets)) {
|
|
30
|
+
for (const preset of presets) {
|
|
31
|
+
if (preset && typeof preset.id === "string") {
|
|
32
|
+
out.push({ ...preset, pluginId: p.id });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (typeof presets === "object") {
|
|
39
|
+
for (const key of Object.keys(presets)) {
|
|
40
|
+
const preset = presets[key];
|
|
41
|
+
if (!preset) continue;
|
|
42
|
+
const id = typeof preset.id === "string" ? preset.id : key;
|
|
43
|
+
if (!id) continue;
|
|
44
|
+
out.push({ ...preset, id, pluginId: p.id });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return out;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function getPluginRegistryAdditions(effectiveConfig) {
|
|
53
|
+
const { plugins } = loadEnabledPlugins(effectiveConfig);
|
|
54
|
+
const out = [];
|
|
55
|
+
|
|
56
|
+
for (const p of plugins) {
|
|
57
|
+
const mod = p.module || {};
|
|
58
|
+
const reg = mod.registry;
|
|
59
|
+
if (!reg) continue;
|
|
60
|
+
|
|
61
|
+
// Supported shapes:
|
|
62
|
+
// 1) registry: { "Language": { "Framework": generator, ... }, ... }
|
|
63
|
+
// 2) registry: { languages: { ... } }
|
|
64
|
+
const languages =
|
|
65
|
+
reg.languages && typeof reg.languages === "object" ? reg.languages : reg;
|
|
66
|
+
if (!languages || typeof languages !== "object") continue;
|
|
67
|
+
|
|
68
|
+
out.push({ pluginId: p.id, languages });
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return out;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function getPluginDoctorChecks(effectiveConfig) {
|
|
75
|
+
const { plugins } = loadEnabledPlugins(effectiveConfig);
|
|
76
|
+
const out = [];
|
|
77
|
+
|
|
78
|
+
for (const p of plugins) {
|
|
79
|
+
const mod = p.module || {};
|
|
80
|
+
|
|
81
|
+
const checks = Array.isArray(mod.doctorChecks)
|
|
82
|
+
? mod.doctorChecks
|
|
83
|
+
: Array.isArray(mod.doctor?.checks)
|
|
84
|
+
? mod.doctor.checks
|
|
85
|
+
: [];
|
|
86
|
+
|
|
87
|
+
for (const check of checks) {
|
|
88
|
+
if (!check || typeof check.id !== "string") continue;
|
|
89
|
+
out.push({ ...check, pluginId: p.id });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return out;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
module.exports = {
|
|
97
|
+
loadEnabledPlugins,
|
|
98
|
+
getPluginPresets,
|
|
99
|
+
getPluginRegistryAdditions,
|
|
100
|
+
getPluginDoctorChecks,
|
|
101
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
function uniqStrings(items) {
|
|
2
|
+
const out = [];
|
|
3
|
+
const seen = new Set();
|
|
4
|
+
for (const v of items || []) {
|
|
5
|
+
if (typeof v !== "string") continue;
|
|
6
|
+
const s = v.trim();
|
|
7
|
+
if (!s) continue;
|
|
8
|
+
if (seen.has(s)) continue;
|
|
9
|
+
seen.add(s);
|
|
10
|
+
out.push(s);
|
|
11
|
+
}
|
|
12
|
+
return out;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function getEnabledPlugins(effectiveConfig) {
|
|
16
|
+
const fromConfig = Array.isArray(effectiveConfig?.plugins)
|
|
17
|
+
? effectiveConfig.plugins
|
|
18
|
+
: [];
|
|
19
|
+
return uniqStrings(fromConfig);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function tryLoadPlugin(pluginId) {
|
|
23
|
+
try {
|
|
24
|
+
// Plugins are expected to be resolvable via Node resolution.
|
|
25
|
+
// This command only enables/disables plugins in config; it does not install packages.
|
|
26
|
+
// eslint-disable-next-line global-require, import/no-dynamic-require
|
|
27
|
+
const mod = require(pluginId);
|
|
28
|
+
return { ok: true, module: mod, error: null };
|
|
29
|
+
} catch (err) {
|
|
30
|
+
const message = err && err.message ? err.message : String(err);
|
|
31
|
+
return { ok: false, module: null, error: message };
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
module.exports = {
|
|
36
|
+
getEnabledPlugins,
|
|
37
|
+
tryLoadPlugin,
|
|
38
|
+
};
|