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.
Files changed (2) hide show
  1. package/dist/index.js +468 -118
  2. 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 updateAll = options.all || !options.packages && !options.config && !options.workflows && !options.services;
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
- console.log("Components to update:");
555
- if (updateTargets.packages) console.log(chalk4.cyan(" - Shared packages"));
556
- if (updateTargets.config) console.log(chalk4.cyan(" - Configuration files"));
557
- if (updateTargets.workflows) console.log(chalk4.cyan(" - GitHub workflows"));
558
- if (updateTargets.services) console.log(chalk4.cyan(" - Docker services"));
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 will be made", "info");
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: "Proceed with update?",
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 templateDir = getTemplateDir3();
577
- if (updateTargets.packages) {
578
- await updatePackages(projectDir, templateDir);
579
- }
580
- if (updateTargets.config) {
581
- await updateConfig(projectDir, templateDir);
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 (updateTargets.workflows) {
584
- await updateWorkflows(projectDir, templateDir);
966
+ if (deletedFilesCount > 0) {
967
+ cleanEmptyDirectories(projectDir);
585
968
  }
586
- if (updateTargets.services) {
587
- await updateServices(projectDir, templateDir);
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
- console.log("Run " + chalk4.cyan("pnpm install") + " to install any new dependencies.");
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
- async function updatePackages(projectDir, templateDir) {
594
- const spinner = ora3("Updating shared packages...").start();
595
- const existingPackages = fs4.existsSync(path4.join(projectDir, "packages")) ? fs4.readdirSync(path4.join(projectDir, "packages")).filter((f) => fs4.statSync(path4.join(projectDir, "packages", f)).isDirectory()) : [];
596
- const { selectedPackages } = await inquirer3.prompt([
597
- {
598
- type: "checkbox",
599
- name: "selectedPackages",
600
- message: "Select packages to update:",
601
- choices: SHARED_PACKAGES.map((pkg) => ({
602
- name: pkg + (existingPackages.includes(pkg) ? " (update)" : " (add new)"),
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
- async function updateServices(projectDir, templateDir) {
674
- const spinner = ora3("Updating Docker services...").start();
675
- const servicesSrc = path4.join(templateDir, "services");
676
- const servicesDest = path4.join(projectDir, "services");
677
- if (fs4.existsSync(servicesSrc)) {
678
- fs4.ensureDirSync(servicesDest);
679
- copyWithMerge(servicesSrc, servicesDest, true);
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.0",
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
  }