create-nexu 1.1.0 → 1.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/dist/index.js +468 -118
- package/package.json +5 -3
package/dist/index.js
CHANGED
|
@@ -90,18 +90,6 @@ function execInherit(command, cwd) {
|
|
|
90
90
|
throw new Error(`Command failed: ${command}`);
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
|
-
function copyWithMerge(src, dest, overwrite = false) {
|
|
94
|
-
if (fs.existsSync(dest) && !overwrite) {
|
|
95
|
-
if (fs.statSync(src).isDirectory() && fs.statSync(dest).isDirectory()) {
|
|
96
|
-
const files = fs.readdirSync(src);
|
|
97
|
-
for (const file of files) {
|
|
98
|
-
copyWithMerge(path.join(src, file), path.join(dest, file), overwrite);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
fs.copySync(src, dest, { overwrite });
|
|
104
|
-
}
|
|
105
93
|
function log(message, type = "info") {
|
|
106
94
|
const colors = {
|
|
107
95
|
info: chalk.blue,
|
|
@@ -517,6 +505,7 @@ async function init(projectName, options) {
|
|
|
517
505
|
import path4 from "path";
|
|
518
506
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
519
507
|
import chalk4 from "chalk";
|
|
508
|
+
import { diffLines } from "diff";
|
|
520
509
|
import fs4 from "fs-extra";
|
|
521
510
|
import inquirer3 from "inquirer";
|
|
522
511
|
import ora3 from "ora";
|
|
@@ -534,6 +523,181 @@ function getTemplateDir3() {
|
|
|
534
523
|
}
|
|
535
524
|
throw new Error("Template directory not found. Please reinstall the package.");
|
|
536
525
|
}
|
|
526
|
+
function compareFiles(srcPath, destPath) {
|
|
527
|
+
if (!fs4.existsSync(destPath)) return false;
|
|
528
|
+
if (!fs4.existsSync(srcPath)) return false;
|
|
529
|
+
const srcStat = fs4.statSync(srcPath);
|
|
530
|
+
const destStat = fs4.statSync(destPath);
|
|
531
|
+
if (srcStat.isDirectory() || destStat.isDirectory()) return false;
|
|
532
|
+
const srcContent = fs4.readFileSync(srcPath, "utf-8");
|
|
533
|
+
const destContent = fs4.readFileSync(destPath, "utf-8");
|
|
534
|
+
return srcContent === destContent;
|
|
535
|
+
}
|
|
536
|
+
function collectFileChanges(srcDir, destDir, category, basePath = "", checkDeleted = true) {
|
|
537
|
+
const changes = [];
|
|
538
|
+
if (fs4.existsSync(srcDir)) {
|
|
539
|
+
const entries = fs4.readdirSync(srcDir, { withFileTypes: true });
|
|
540
|
+
for (const entry of entries) {
|
|
541
|
+
const srcPath = path4.join(srcDir, entry.name);
|
|
542
|
+
const destPath = path4.join(destDir, entry.name);
|
|
543
|
+
const relativePath = basePath ? path4.join(basePath, entry.name) : entry.name;
|
|
544
|
+
if (entry.isDirectory()) {
|
|
545
|
+
changes.push(...collectFileChanges(srcPath, destPath, category, relativePath, checkDeleted));
|
|
546
|
+
} else {
|
|
547
|
+
if (!fs4.existsSync(destPath)) {
|
|
548
|
+
changes.push({ type: "add", relativePath, srcPath, destPath, category });
|
|
549
|
+
} else if (!compareFiles(srcPath, destPath)) {
|
|
550
|
+
changes.push({ type: "modify", relativePath, srcPath, destPath, category });
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
if (checkDeleted && fs4.existsSync(destDir)) {
|
|
556
|
+
const destEntries = fs4.readdirSync(destDir, { withFileTypes: true });
|
|
557
|
+
for (const entry of destEntries) {
|
|
558
|
+
const srcPath = path4.join(srcDir, entry.name);
|
|
559
|
+
const destPath = path4.join(destDir, entry.name);
|
|
560
|
+
const relativePath = basePath ? path4.join(basePath, entry.name) : entry.name;
|
|
561
|
+
if (fs4.existsSync(srcPath)) continue;
|
|
562
|
+
if (entry.isDirectory()) {
|
|
563
|
+
changes.push(...collectDeletedFiles(destPath, category, relativePath));
|
|
564
|
+
} else {
|
|
565
|
+
changes.push({ type: "delete", relativePath, srcPath, destPath, category });
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
return changes;
|
|
570
|
+
}
|
|
571
|
+
function collectDeletedFiles(destDir, category, basePath) {
|
|
572
|
+
const changes = [];
|
|
573
|
+
if (!fs4.existsSync(destDir)) return changes;
|
|
574
|
+
const entries = fs4.readdirSync(destDir, { withFileTypes: true });
|
|
575
|
+
for (const entry of entries) {
|
|
576
|
+
const destPath = path4.join(destDir, entry.name);
|
|
577
|
+
const relativePath = path4.join(basePath, entry.name);
|
|
578
|
+
if (entry.isDirectory()) {
|
|
579
|
+
changes.push(...collectDeletedFiles(destPath, category, relativePath));
|
|
580
|
+
} else {
|
|
581
|
+
changes.push({ type: "delete", relativePath, srcPath: "", destPath, category });
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
return changes;
|
|
585
|
+
}
|
|
586
|
+
function showFileDiff(srcPath, destPath) {
|
|
587
|
+
if (!fs4.existsSync(destPath)) {
|
|
588
|
+
console.log(chalk4.green(" (new file)"));
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
const srcContent = fs4.readFileSync(srcPath, "utf-8");
|
|
592
|
+
const destContent = fs4.readFileSync(destPath, "utf-8");
|
|
593
|
+
const differences = diffLines(destContent, srcContent);
|
|
594
|
+
let hasChanges2 = false;
|
|
595
|
+
for (const part of differences) {
|
|
596
|
+
if (part.added || part.removed) {
|
|
597
|
+
hasChanges2 = true;
|
|
598
|
+
const lines = part.value.split("\n").filter((l) => l.length > 0);
|
|
599
|
+
for (const line of lines.slice(0, 5)) {
|
|
600
|
+
if (part.added) {
|
|
601
|
+
console.log(chalk4.green(` + ${line.substring(0, 80)}`));
|
|
602
|
+
} else {
|
|
603
|
+
console.log(chalk4.red(` - ${line.substring(0, 80)}`));
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
if (lines.length > 5) {
|
|
607
|
+
console.log(chalk4.gray(` ... and ${lines.length - 5} more lines`));
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
if (!hasChanges2) {
|
|
612
|
+
console.log(chalk4.gray(" (no visible changes)"));
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
function collectDependencyChanges(templatePkgPath, projectPkgPath) {
|
|
616
|
+
if (!fs4.existsSync(templatePkgPath) || !fs4.existsSync(projectPkgPath)) {
|
|
617
|
+
return null;
|
|
618
|
+
}
|
|
619
|
+
const templatePkg = fs4.readJsonSync(templatePkgPath);
|
|
620
|
+
const projectPkg = fs4.readJsonSync(projectPkgPath);
|
|
621
|
+
const changes = {
|
|
622
|
+
type: "package.json",
|
|
623
|
+
changes: {
|
|
624
|
+
added: { dependencies: {}, devDependencies: {}, scripts: {} },
|
|
625
|
+
updated: { dependencies: {}, devDependencies: {}, scripts: {} }
|
|
626
|
+
}
|
|
627
|
+
};
|
|
628
|
+
for (const depType of ["dependencies", "devDependencies"]) {
|
|
629
|
+
if (templatePkg[depType]) {
|
|
630
|
+
for (const [pkg, version] of Object.entries(templatePkg[depType])) {
|
|
631
|
+
if (!projectPkg[depType]?.[pkg]) {
|
|
632
|
+
changes.changes.added[depType][pkg] = version;
|
|
633
|
+
} else if (projectPkg[depType][pkg] !== version) {
|
|
634
|
+
changes.changes.updated[depType][pkg] = {
|
|
635
|
+
from: projectPkg[depType][pkg],
|
|
636
|
+
to: version
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
if (templatePkg.scripts) {
|
|
643
|
+
for (const [script, cmd] of Object.entries(templatePkg.scripts)) {
|
|
644
|
+
if (!projectPkg.scripts?.[script]) {
|
|
645
|
+
changes.changes.added.scripts[script] = cmd;
|
|
646
|
+
} else if (projectPkg.scripts[script] !== cmd) {
|
|
647
|
+
changes.changes.updated.scripts[script] = {
|
|
648
|
+
from: projectPkg.scripts[script],
|
|
649
|
+
to: cmd
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
return changes;
|
|
655
|
+
}
|
|
656
|
+
function displayDependencyChanges(changes) {
|
|
657
|
+
const { added, updated } = changes.changes;
|
|
658
|
+
if (Object.keys(added.dependencies).length > 0) {
|
|
659
|
+
console.log(chalk4.cyan("\n New dependencies:"));
|
|
660
|
+
for (const [pkg, version] of Object.entries(added.dependencies)) {
|
|
661
|
+
console.log(chalk4.green(` + ${pkg}: ${version}`));
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
if (Object.keys(added.devDependencies).length > 0) {
|
|
665
|
+
console.log(chalk4.cyan("\n New devDependencies:"));
|
|
666
|
+
for (const [pkg, version] of Object.entries(added.devDependencies)) {
|
|
667
|
+
console.log(chalk4.green(` + ${pkg}: ${version}`));
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
if (Object.keys(updated.dependencies).length > 0) {
|
|
671
|
+
console.log(chalk4.cyan("\n Updated dependencies:"));
|
|
672
|
+
for (const [pkg, { from, to }] of Object.entries(updated.dependencies)) {
|
|
673
|
+
console.log(chalk4.yellow(` ~ ${pkg}: ${from} \u2192 ${to}`));
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
if (Object.keys(updated.devDependencies).length > 0) {
|
|
677
|
+
console.log(chalk4.cyan("\n Updated devDependencies:"));
|
|
678
|
+
for (const [pkg, { from, to }] of Object.entries(updated.devDependencies)) {
|
|
679
|
+
console.log(chalk4.yellow(` ~ ${pkg}: ${from} \u2192 ${to}`));
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
if (Object.keys(added.scripts).length > 0) {
|
|
683
|
+
console.log(chalk4.cyan("\n New scripts:"));
|
|
684
|
+
for (const [script, cmd] of Object.entries(added.scripts)) {
|
|
685
|
+
console.log(chalk4.green(` + ${script}: ${String(cmd).substring(0, 50)}...`));
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
if (Object.keys(updated.scripts).length > 0) {
|
|
689
|
+
console.log(chalk4.cyan("\n Updated scripts:"));
|
|
690
|
+
for (const [script, { from, to }] of Object.entries(updated.scripts)) {
|
|
691
|
+
console.log(chalk4.yellow(` ~ ${script}:`));
|
|
692
|
+
console.log(chalk4.red(` - ${from.substring(0, 60)}${from.length > 60 ? "..." : ""}`));
|
|
693
|
+
console.log(chalk4.green(` + ${to.substring(0, 60)}${to.length > 60 ? "..." : ""}`));
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
function hasChanges(changes) {
|
|
698
|
+
const { added, updated } = changes.changes;
|
|
699
|
+
return Object.keys(added.dependencies).length > 0 || Object.keys(added.devDependencies).length > 0 || Object.keys(added.scripts).length > 0 || Object.keys(updated.dependencies).length > 0 || Object.keys(updated.devDependencies).length > 0 || Object.keys(updated.scripts).length > 0;
|
|
700
|
+
}
|
|
537
701
|
async function update(options) {
|
|
538
702
|
console.log(chalk4.bold("\n\u{1F504} Update Nexu Project\n"));
|
|
539
703
|
const projectDir = process.cwd();
|
|
@@ -544,28 +708,238 @@ async function update(options) {
|
|
|
544
708
|
);
|
|
545
709
|
process.exit(1);
|
|
546
710
|
}
|
|
547
|
-
const
|
|
711
|
+
const templateDir = getTemplateDir3();
|
|
712
|
+
const updateAll = options.all || !options.packages && !options.config && !options.workflows && !options.services && !options.scripts && !options.dependencies;
|
|
548
713
|
const updateTargets = {
|
|
549
714
|
packages: updateAll || options.packages,
|
|
550
715
|
config: updateAll || options.config,
|
|
551
716
|
workflows: updateAll || options.workflows,
|
|
552
|
-
services: updateAll || options.services
|
|
717
|
+
services: updateAll || options.services,
|
|
718
|
+
scripts: updateAll || options.scripts,
|
|
719
|
+
dependencies: updateAll || options.dependencies
|
|
553
720
|
};
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
if (updateTargets.
|
|
558
|
-
|
|
721
|
+
const spinner = ora3("Analyzing changes...").start();
|
|
722
|
+
const allFileChanges = [];
|
|
723
|
+
let dependencyChanges = null;
|
|
724
|
+
if (updateTargets.config) {
|
|
725
|
+
for (const file of TEMPLATE_DIRS.config) {
|
|
726
|
+
const srcFile = path4.join(templateDir, file);
|
|
727
|
+
const destFile = path4.join(projectDir, file);
|
|
728
|
+
if (fs4.existsSync(srcFile)) {
|
|
729
|
+
if (!fs4.existsSync(destFile)) {
|
|
730
|
+
allFileChanges.push({
|
|
731
|
+
type: "add",
|
|
732
|
+
relativePath: file,
|
|
733
|
+
srcPath: srcFile,
|
|
734
|
+
destPath: destFile,
|
|
735
|
+
category: "config"
|
|
736
|
+
});
|
|
737
|
+
} else if (!compareFiles(srcFile, destFile)) {
|
|
738
|
+
allFileChanges.push({
|
|
739
|
+
type: "modify",
|
|
740
|
+
relativePath: file,
|
|
741
|
+
srcPath: srcFile,
|
|
742
|
+
destPath: destFile,
|
|
743
|
+
category: "config"
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
allFileChanges.push(
|
|
749
|
+
...collectFileChanges(
|
|
750
|
+
path4.join(templateDir, ".husky"),
|
|
751
|
+
path4.join(projectDir, ".husky"),
|
|
752
|
+
"config",
|
|
753
|
+
".husky"
|
|
754
|
+
)
|
|
755
|
+
);
|
|
756
|
+
allFileChanges.push(
|
|
757
|
+
...collectFileChanges(
|
|
758
|
+
path4.join(templateDir, ".vscode"),
|
|
759
|
+
path4.join(projectDir, ".vscode"),
|
|
760
|
+
"config",
|
|
761
|
+
".vscode"
|
|
762
|
+
)
|
|
763
|
+
);
|
|
764
|
+
}
|
|
765
|
+
if (updateTargets.workflows) {
|
|
766
|
+
allFileChanges.push(
|
|
767
|
+
...collectFileChanges(
|
|
768
|
+
path4.join(templateDir, ".github"),
|
|
769
|
+
path4.join(projectDir, ".github"),
|
|
770
|
+
"workflows",
|
|
771
|
+
".github"
|
|
772
|
+
)
|
|
773
|
+
);
|
|
774
|
+
}
|
|
775
|
+
if (updateTargets.services) {
|
|
776
|
+
allFileChanges.push(
|
|
777
|
+
...collectFileChanges(
|
|
778
|
+
path4.join(templateDir, "services"),
|
|
779
|
+
path4.join(projectDir, "services"),
|
|
780
|
+
"services",
|
|
781
|
+
"services"
|
|
782
|
+
)
|
|
783
|
+
);
|
|
784
|
+
allFileChanges.push(
|
|
785
|
+
...collectFileChanges(
|
|
786
|
+
path4.join(templateDir, "docker"),
|
|
787
|
+
path4.join(projectDir, "docker"),
|
|
788
|
+
"services",
|
|
789
|
+
"docker"
|
|
790
|
+
)
|
|
791
|
+
);
|
|
792
|
+
}
|
|
793
|
+
if (updateTargets.scripts) {
|
|
794
|
+
allFileChanges.push(
|
|
795
|
+
...collectFileChanges(
|
|
796
|
+
path4.join(templateDir, "scripts"),
|
|
797
|
+
path4.join(projectDir, "scripts"),
|
|
798
|
+
"scripts",
|
|
799
|
+
"scripts"
|
|
800
|
+
)
|
|
801
|
+
);
|
|
802
|
+
}
|
|
803
|
+
if (updateTargets.packages) {
|
|
804
|
+
const existingPackages = fs4.existsSync(path4.join(projectDir, "packages")) ? fs4.readdirSync(path4.join(projectDir, "packages")).filter((f) => fs4.statSync(path4.join(projectDir, "packages", f)).isDirectory()) : [];
|
|
805
|
+
for (const pkg of SHARED_PACKAGES) {
|
|
806
|
+
if (existingPackages.includes(pkg)) {
|
|
807
|
+
allFileChanges.push(
|
|
808
|
+
...collectFileChanges(
|
|
809
|
+
path4.join(templateDir, "packages", pkg),
|
|
810
|
+
path4.join(projectDir, "packages", pkg),
|
|
811
|
+
"packages",
|
|
812
|
+
path4.join("packages", pkg)
|
|
813
|
+
)
|
|
814
|
+
);
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
if (updateTargets.dependencies) {
|
|
819
|
+
dependencyChanges = collectDependencyChanges(
|
|
820
|
+
path4.join(templateDir, "package.json"),
|
|
821
|
+
path4.join(projectDir, "package.json")
|
|
822
|
+
);
|
|
823
|
+
}
|
|
824
|
+
spinner.stop();
|
|
825
|
+
const addedFiles = allFileChanges.filter((c) => c.type === "add");
|
|
826
|
+
const modifiedFiles = allFileChanges.filter((c) => c.type === "modify");
|
|
827
|
+
const deletedFiles = allFileChanges.filter((c) => c.type === "delete");
|
|
828
|
+
if (addedFiles.length === 0 && modifiedFiles.length === 0 && deletedFiles.length === 0 && (!dependencyChanges || !hasChanges(dependencyChanges))) {
|
|
829
|
+
console.log(chalk4.green("\u2713 Your project is up to date! No changes needed.\n"));
|
|
830
|
+
return;
|
|
831
|
+
}
|
|
832
|
+
console.log(chalk4.bold("\u{1F4CB} Changes to apply:\n"));
|
|
833
|
+
const categories = [...new Set(allFileChanges.map((c) => c.category))];
|
|
834
|
+
for (const category of categories) {
|
|
835
|
+
const categoryChanges = allFileChanges.filter((c) => c.category === category);
|
|
836
|
+
if (categoryChanges.length === 0) continue;
|
|
837
|
+
const categoryNames = {
|
|
838
|
+
config: "Configuration files",
|
|
839
|
+
workflows: "GitHub workflows",
|
|
840
|
+
services: "Docker services",
|
|
841
|
+
scripts: "Scripts",
|
|
842
|
+
packages: "Shared packages"
|
|
843
|
+
};
|
|
844
|
+
console.log(chalk4.cyan.bold(`
|
|
845
|
+
${categoryNames[category] || category}:`));
|
|
846
|
+
const added = categoryChanges.filter((c) => c.type === "add");
|
|
847
|
+
const modified = categoryChanges.filter((c) => c.type === "modify");
|
|
848
|
+
const deleted = categoryChanges.filter((c) => c.type === "delete");
|
|
849
|
+
if (added.length > 0) {
|
|
850
|
+
console.log(chalk4.green(` New files (${added.length}):`));
|
|
851
|
+
for (const change of added.slice(0, 10)) {
|
|
852
|
+
console.log(chalk4.green(` + ${change.relativePath}`));
|
|
853
|
+
}
|
|
854
|
+
if (added.length > 10) {
|
|
855
|
+
console.log(chalk4.gray(` ... and ${added.length - 10} more files`));
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
if (modified.length > 0) {
|
|
859
|
+
console.log(chalk4.yellow(` Modified files (${modified.length}):`));
|
|
860
|
+
for (const change of modified.slice(0, 10)) {
|
|
861
|
+
console.log(chalk4.yellow(` ~ ${change.relativePath}`));
|
|
862
|
+
}
|
|
863
|
+
if (modified.length > 10) {
|
|
864
|
+
console.log(chalk4.gray(` ... and ${modified.length - 10} more files`));
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
if (deleted.length > 0) {
|
|
868
|
+
console.log(chalk4.red(` Deleted files (${deleted.length}):`));
|
|
869
|
+
for (const change of deleted.slice(0, 10)) {
|
|
870
|
+
console.log(chalk4.red(` - ${change.relativePath}`));
|
|
871
|
+
}
|
|
872
|
+
if (deleted.length > 10) {
|
|
873
|
+
console.log(chalk4.gray(` ... and ${deleted.length - 10} more files`));
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
if (dependencyChanges && hasChanges(dependencyChanges)) {
|
|
878
|
+
console.log(chalk4.cyan.bold("\nPackage.json changes:"));
|
|
879
|
+
displayDependencyChanges(dependencyChanges);
|
|
880
|
+
}
|
|
559
881
|
console.log("");
|
|
882
|
+
if (options.preview) {
|
|
883
|
+
const { showDiff } = await inquirer3.prompt([
|
|
884
|
+
{
|
|
885
|
+
type: "confirm",
|
|
886
|
+
name: "showDiff",
|
|
887
|
+
message: "Show detailed file differences?",
|
|
888
|
+
default: false
|
|
889
|
+
}
|
|
890
|
+
]);
|
|
891
|
+
if (showDiff) {
|
|
892
|
+
for (const change of modifiedFiles) {
|
|
893
|
+
console.log(chalk4.bold(`
|
|
894
|
+
${change.relativePath}:`));
|
|
895
|
+
showFileDiff(change.srcPath, change.destPath);
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
}
|
|
560
899
|
if (options.dryRun) {
|
|
561
|
-
log("Dry run mode - no changes
|
|
900
|
+
console.log(chalk4.blue("\ni Dry run mode - no changes were made.\n"));
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
903
|
+
const { selectedCategories } = await inquirer3.prompt([
|
|
904
|
+
{
|
|
905
|
+
type: "checkbox",
|
|
906
|
+
name: "selectedCategories",
|
|
907
|
+
message: "Select which changes to apply:",
|
|
908
|
+
choices: [
|
|
909
|
+
...categories.map((cat) => {
|
|
910
|
+
const count = allFileChanges.filter((c) => c.category === cat).length;
|
|
911
|
+
const categoryNames = {
|
|
912
|
+
config: "Configuration files",
|
|
913
|
+
workflows: "GitHub workflows",
|
|
914
|
+
services: "Docker services",
|
|
915
|
+
scripts: "Scripts",
|
|
916
|
+
packages: "Shared packages"
|
|
917
|
+
};
|
|
918
|
+
return {
|
|
919
|
+
name: `${categoryNames[cat] || cat} (${count} files)`,
|
|
920
|
+
value: cat,
|
|
921
|
+
checked: true
|
|
922
|
+
};
|
|
923
|
+
}),
|
|
924
|
+
...dependencyChanges && hasChanges(dependencyChanges) ? [
|
|
925
|
+
{
|
|
926
|
+
name: "Package.json dependencies",
|
|
927
|
+
value: "dependencies",
|
|
928
|
+
checked: true
|
|
929
|
+
}
|
|
930
|
+
] : []
|
|
931
|
+
]
|
|
932
|
+
}
|
|
933
|
+
]);
|
|
934
|
+
if (selectedCategories.length === 0) {
|
|
935
|
+
log("No changes selected. Update cancelled.", "warn");
|
|
562
936
|
return;
|
|
563
937
|
}
|
|
564
938
|
const { confirm } = await inquirer3.prompt([
|
|
565
939
|
{
|
|
566
940
|
type: "confirm",
|
|
567
941
|
name: "confirm",
|
|
568
|
-
message:
|
|
942
|
+
message: `Apply ${selectedCategories.length} category(ies) of changes?`,
|
|
569
943
|
default: true
|
|
570
944
|
}
|
|
571
945
|
]);
|
|
@@ -573,112 +947,88 @@ async function update(options) {
|
|
|
573
947
|
log("Update cancelled.", "warn");
|
|
574
948
|
return;
|
|
575
949
|
}
|
|
576
|
-
const
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
950
|
+
const applySpinner = ora3("Applying changes...").start();
|
|
951
|
+
let appliedFiles = 0;
|
|
952
|
+
let deletedFilesCount = 0;
|
|
953
|
+
for (const change of allFileChanges) {
|
|
954
|
+
if (!selectedCategories.includes(change.category)) continue;
|
|
955
|
+
if (change.type === "delete") {
|
|
956
|
+
if (fs4.existsSync(change.destPath)) {
|
|
957
|
+
fs4.removeSync(change.destPath);
|
|
958
|
+
deletedFilesCount++;
|
|
959
|
+
}
|
|
960
|
+
} else {
|
|
961
|
+
fs4.ensureDirSync(path4.dirname(change.destPath));
|
|
962
|
+
fs4.copySync(change.srcPath, change.destPath, { overwrite: true });
|
|
963
|
+
appliedFiles++;
|
|
964
|
+
}
|
|
582
965
|
}
|
|
583
|
-
if (
|
|
584
|
-
|
|
966
|
+
if (deletedFilesCount > 0) {
|
|
967
|
+
cleanEmptyDirectories(projectDir);
|
|
585
968
|
}
|
|
586
|
-
if (
|
|
587
|
-
|
|
969
|
+
if (selectedCategories.includes("dependencies") && dependencyChanges) {
|
|
970
|
+
const templatePkgPath = path4.join(templateDir, "package.json");
|
|
971
|
+
const projectPkgPath = path4.join(projectDir, "package.json");
|
|
972
|
+
const templatePkg = fs4.readJsonSync(templatePkgPath);
|
|
973
|
+
const projectPkg = fs4.readJsonSync(projectPkgPath);
|
|
974
|
+
if (templatePkg.devDependencies) {
|
|
975
|
+
projectPkg.devDependencies = {
|
|
976
|
+
...projectPkg.devDependencies,
|
|
977
|
+
...templatePkg.devDependencies
|
|
978
|
+
};
|
|
979
|
+
}
|
|
980
|
+
if (templatePkg.dependencies) {
|
|
981
|
+
projectPkg.dependencies = {
|
|
982
|
+
...projectPkg.dependencies,
|
|
983
|
+
...templatePkg.dependencies
|
|
984
|
+
};
|
|
985
|
+
}
|
|
986
|
+
if (templatePkg.scripts) {
|
|
987
|
+
projectPkg.scripts = {
|
|
988
|
+
...projectPkg.scripts,
|
|
989
|
+
...templatePkg.scripts
|
|
990
|
+
};
|
|
991
|
+
}
|
|
992
|
+
if (projectPkg.dependencies) {
|
|
993
|
+
projectPkg.dependencies = sortObject(projectPkg.dependencies);
|
|
994
|
+
}
|
|
995
|
+
if (projectPkg.devDependencies) {
|
|
996
|
+
projectPkg.devDependencies = sortObject(projectPkg.devDependencies);
|
|
997
|
+
}
|
|
998
|
+
fs4.writeJsonSync(projectPkgPath, projectPkg, { spaces: 2 });
|
|
588
999
|
}
|
|
1000
|
+
const summary = [];
|
|
1001
|
+
if (appliedFiles > 0) summary.push(`${appliedFiles} files updated`);
|
|
1002
|
+
if (deletedFilesCount > 0) summary.push(`${deletedFilesCount} files deleted`);
|
|
1003
|
+
applySpinner.succeed(summary.join(", ") || "No file changes applied");
|
|
589
1004
|
console.log("\n" + chalk4.green.bold("\u2728 Update complete!\n"));
|
|
590
|
-
|
|
1005
|
+
if (selectedCategories.includes("dependencies")) {
|
|
1006
|
+
console.log("Run your package manager install command to install any new dependencies.");
|
|
1007
|
+
}
|
|
591
1008
|
console.log("");
|
|
592
1009
|
}
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
const
|
|
596
|
-
const
|
|
597
|
-
{
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
value: pkg,
|
|
604
|
-
checked: existingPackages.includes(pkg)
|
|
605
|
-
}))
|
|
606
|
-
}
|
|
607
|
-
]);
|
|
608
|
-
spinner.start("Updating packages...");
|
|
609
|
-
for (const pkg of selectedPackages) {
|
|
610
|
-
const srcDir = path4.join(templateDir, "packages", pkg);
|
|
611
|
-
const destDir = path4.join(projectDir, "packages", pkg);
|
|
612
|
-
if (fs4.existsSync(srcDir)) {
|
|
613
|
-
let existingPkgJson = null;
|
|
614
|
-
const pkgJsonPath = path4.join(destDir, "package.json");
|
|
615
|
-
if (fs4.existsSync(pkgJsonPath)) {
|
|
616
|
-
existingPkgJson = fs4.readJsonSync(pkgJsonPath);
|
|
617
|
-
}
|
|
618
|
-
fs4.copySync(srcDir, destDir, { overwrite: true });
|
|
619
|
-
if (existingPkgJson) {
|
|
620
|
-
const newPkgJson = fs4.readJsonSync(pkgJsonPath);
|
|
621
|
-
newPkgJson.version = existingPkgJson.version;
|
|
622
|
-
fs4.writeJsonSync(pkgJsonPath, newPkgJson, { spaces: 2 });
|
|
1010
|
+
function cleanEmptyDirectories(dir) {
|
|
1011
|
+
if (!fs4.existsSync(dir)) return;
|
|
1012
|
+
const entries = fs4.readdirSync(dir, { withFileTypes: true });
|
|
1013
|
+
for (const entry of entries) {
|
|
1014
|
+
if (entry.isDirectory()) {
|
|
1015
|
+
const fullPath = path4.join(dir, entry.name);
|
|
1016
|
+
cleanEmptyDirectories(fullPath);
|
|
1017
|
+
const remaining = fs4.readdirSync(fullPath);
|
|
1018
|
+
if (remaining.length === 0) {
|
|
1019
|
+
fs4.rmdirSync(fullPath);
|
|
623
1020
|
}
|
|
624
1021
|
}
|
|
625
1022
|
}
|
|
626
|
-
spinner.succeed(`Updated ${selectedPackages.length} packages`);
|
|
627
|
-
}
|
|
628
|
-
async function updateConfig(projectDir, templateDir) {
|
|
629
|
-
const spinner = ora3("Updating configuration files...").start();
|
|
630
|
-
const configFiles = TEMPLATE_DIRS.config;
|
|
631
|
-
let updated = 0;
|
|
632
|
-
for (const file of configFiles) {
|
|
633
|
-
const srcFile = path4.join(templateDir, file);
|
|
634
|
-
const destFile = path4.join(projectDir, file);
|
|
635
|
-
if (fs4.existsSync(srcFile)) {
|
|
636
|
-
fs4.copySync(srcFile, destFile, { overwrite: true });
|
|
637
|
-
updated++;
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
const huskySrc = path4.join(templateDir, ".husky");
|
|
641
|
-
const huskyDest = path4.join(projectDir, ".husky");
|
|
642
|
-
if (fs4.existsSync(huskySrc)) {
|
|
643
|
-
fs4.copySync(huskySrc, huskyDest, { overwrite: true });
|
|
644
|
-
}
|
|
645
|
-
const vscodeSrc = path4.join(templateDir, ".vscode");
|
|
646
|
-
const vscodeDest = path4.join(projectDir, ".vscode");
|
|
647
|
-
if (fs4.existsSync(vscodeSrc)) {
|
|
648
|
-
fs4.copySync(vscodeSrc, vscodeDest, { overwrite: true });
|
|
649
|
-
}
|
|
650
|
-
spinner.succeed(`Updated ${updated} configuration files`);
|
|
651
|
-
}
|
|
652
|
-
async function updateWorkflows(projectDir, templateDir) {
|
|
653
|
-
const spinner = ora3("Updating GitHub workflows...").start();
|
|
654
|
-
const workflowsSrc = path4.join(templateDir, ".github", "workflows");
|
|
655
|
-
const workflowsDest = path4.join(projectDir, ".github", "workflows");
|
|
656
|
-
if (fs4.existsSync(workflowsSrc)) {
|
|
657
|
-
fs4.ensureDirSync(workflowsDest);
|
|
658
|
-
fs4.copySync(workflowsSrc, workflowsDest, { overwrite: true });
|
|
659
|
-
}
|
|
660
|
-
const actionsSrc = path4.join(templateDir, ".github", "actions");
|
|
661
|
-
const actionsDest = path4.join(projectDir, ".github", "actions");
|
|
662
|
-
if (fs4.existsSync(actionsSrc)) {
|
|
663
|
-
fs4.ensureDirSync(actionsDest);
|
|
664
|
-
fs4.copySync(actionsSrc, actionsDest, { overwrite: true });
|
|
665
|
-
}
|
|
666
|
-
const dependabotSrc = path4.join(templateDir, ".github", "dependabot.yml");
|
|
667
|
-
const dependabotDest = path4.join(projectDir, ".github", "dependabot.yml");
|
|
668
|
-
if (fs4.existsSync(dependabotSrc)) {
|
|
669
|
-
fs4.copySync(dependabotSrc, dependabotDest, { overwrite: true });
|
|
670
|
-
}
|
|
671
|
-
spinner.succeed("Updated GitHub workflows and actions");
|
|
672
1023
|
}
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
spinner.succeed("Updated Docker services");
|
|
1024
|
+
function sortObject(obj) {
|
|
1025
|
+
return Object.keys(obj).sort().reduce(
|
|
1026
|
+
(acc, key) => {
|
|
1027
|
+
acc[key] = obj[key];
|
|
1028
|
+
return acc;
|
|
1029
|
+
},
|
|
1030
|
+
{}
|
|
1031
|
+
);
|
|
682
1032
|
}
|
|
683
1033
|
|
|
684
1034
|
// src/index.ts
|
|
@@ -691,6 +1041,6 @@ program.argument("[project-name]", "Name of the project to create").option("-t,
|
|
|
691
1041
|
return init(projectName, options);
|
|
692
1042
|
}
|
|
693
1043
|
});
|
|
694
|
-
program.command("update").description("Update an existing Nexu project with latest features").option("-p, --packages", "Update only shared packages").option("-c, --config", "Update only configuration files").option("-w, --workflows", "Update only GitHub workflows").option("-s, --services", "Update only Docker services").option("--all", "Update everything (default)").option("--dry-run", "Show what would be updated without making changes").action(update);
|
|
1044
|
+
program.command("update").description("Update an existing Nexu project with latest features").option("-p, --packages", "Update only shared packages").option("-c, --config", "Update only configuration files").option("-w, --workflows", "Update only GitHub workflows").option("-s, --services", "Update only Docker services").option("--scripts", "Update only scripts").option("-d, --dependencies", "Update only package.json dependencies").option("--all", "Update everything (default)").option("--dry-run", "Show what would be updated without making changes").option("--preview", "Show detailed file differences before applying").action(update);
|
|
695
1045
|
program.command("add <component>").description("Add a component to your project (package, service)").option("-n, --name <name>", "Name for the component").action(add);
|
|
696
1046
|
program.parse();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-nexu",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"description": "CLI to create and update Nexu monorepo projects",
|
|
5
5
|
"author": "Nexu Team",
|
|
6
6
|
"license": "MIT",
|
|
@@ -23,18 +23,20 @@
|
|
|
23
23
|
"typecheck": "tsc --noEmit"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
+
"chalk": "^5.3.0",
|
|
26
27
|
"commander": "^12.0.0",
|
|
28
|
+
"diff": "^8.0.3",
|
|
27
29
|
"fs-extra": "^11.2.0",
|
|
28
30
|
"inquirer": "^9.2.0",
|
|
29
|
-
"chalk": "^5.3.0",
|
|
30
31
|
"ora": "^8.0.0",
|
|
31
32
|
"semver": "^7.6.0"
|
|
32
33
|
},
|
|
33
34
|
"devDependencies": {
|
|
35
|
+
"@types/diff": "^8.0.0",
|
|
34
36
|
"@types/fs-extra": "^11.0.0",
|
|
35
37
|
"@types/inquirer": "^9.0.0",
|
|
36
|
-
"@types/semver": "^7.5.0",
|
|
37
38
|
"@types/node": "^20.0.0",
|
|
39
|
+
"@types/semver": "^7.5.0",
|
|
38
40
|
"tsup": "^8.0.0",
|
|
39
41
|
"typescript": "^5.4.0"
|
|
40
42
|
}
|