@svton/cli 1.1.0 → 1.2.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/dist/index.js CHANGED
@@ -40,8 +40,8 @@ var import_commander = require("commander");
40
40
  var import_inquirer = __toESM(require("inquirer"));
41
41
  var import_chalk2 = __toESM(require("chalk"));
42
42
  var import_ora = __toESM(require("ora"));
43
- var import_fs_extra4 = __toESM(require("fs-extra"));
44
- var import_path3 = __toESM(require("path"));
43
+ var import_fs_extra5 = __toESM(require("fs-extra"));
44
+ var import_path4 = __toESM(require("path"));
45
45
  var import_validate_npm_package_name = __toESM(require("validate-npm-package-name"));
46
46
 
47
47
  // src/utils/template.ts
@@ -681,6 +681,219 @@ async function initGit(projectName) {
681
681
  }
682
682
  }
683
683
 
684
+ // src/utils/features.ts
685
+ var import_fs_extra4 = __toESM(require("fs-extra"));
686
+ var import_path3 = __toESM(require("path"));
687
+ async function loadFeaturesConfig() {
688
+ const configPath = import_path3.default.join(__dirname, "../../features.json");
689
+ return await import_fs_extra4.default.readJSON(configPath);
690
+ }
691
+ function getFeatureChoices(config) {
692
+ return Object.entries(config.features).map(([key, feature]) => ({
693
+ name: `${feature.name} - ${feature.description}`,
694
+ value: key,
695
+ checked: false
696
+ }));
697
+ }
698
+ function collectDependencies(features, config) {
699
+ const dependencies = {};
700
+ for (const featureKey of features) {
701
+ const feature = config.features[featureKey];
702
+ if (feature) {
703
+ Object.assign(dependencies, feature.packages.dependencies);
704
+ }
705
+ }
706
+ return dependencies;
707
+ }
708
+ function collectEnvVars(features, config) {
709
+ const envVars = [];
710
+ const seen = /* @__PURE__ */ new Set();
711
+ for (const featureKey of features) {
712
+ const feature = config.features[featureKey];
713
+ if (feature) {
714
+ for (const envVar of feature.envVars) {
715
+ if (!seen.has(envVar.key)) {
716
+ envVars.push(envVar);
717
+ seen.add(envVar.key);
718
+ }
719
+ }
720
+ }
721
+ }
722
+ return envVars;
723
+ }
724
+ async function generateEnvExample(features, config, targetPath) {
725
+ const envVars = collectEnvVars(features, config);
726
+ const content = [
727
+ "# Environment Variables",
728
+ "# Copy this file to .env and fill in the values",
729
+ "",
730
+ ...envVars.map((envVar) => {
731
+ const lines = [];
732
+ if (envVar.description) {
733
+ lines.push(`# ${envVar.description}`);
734
+ }
735
+ lines.push(`${envVar.key}=${envVar.default}`);
736
+ lines.push("");
737
+ return lines.join("\n");
738
+ })
739
+ ].join("\n");
740
+ await import_fs_extra4.default.writeFile(import_path3.default.join(targetPath, ".env.example"), content);
741
+ logger.info("Generated .env.example");
742
+ }
743
+ async function copyConfigFiles(features, config, templatePath, targetPath) {
744
+ for (const featureKey of features) {
745
+ const feature = config.features[featureKey];
746
+ if (feature && feature.configFiles) {
747
+ for (const configFile of feature.configFiles) {
748
+ const sourcePath = import_path3.default.join(templatePath, configFile.template);
749
+ const destPath = import_path3.default.join(targetPath, configFile.path);
750
+ await import_fs_extra4.default.ensureDir(import_path3.default.dirname(destPath));
751
+ await import_fs_extra4.default.copy(sourcePath, destPath);
752
+ logger.info(`Copied config: ${configFile.path}`);
753
+ }
754
+ }
755
+ }
756
+ }
757
+ async function copyExampleFiles(features, config, templatePath, targetPath) {
758
+ for (const featureKey of features) {
759
+ const feature = config.features[featureKey];
760
+ if (feature && feature.exampleFiles) {
761
+ const sourcePath = import_path3.default.join(templatePath, feature.exampleFiles.source);
762
+ const destPath = import_path3.default.join(targetPath, feature.exampleFiles.target);
763
+ if (await import_fs_extra4.default.pathExists(sourcePath)) {
764
+ await import_fs_extra4.default.ensureDir(import_path3.default.dirname(destPath));
765
+ await import_fs_extra4.default.copy(sourcePath, destPath);
766
+ logger.info(`Copied examples: ${feature.exampleFiles.target}`);
767
+ }
768
+ }
769
+ }
770
+ }
771
+ async function copySkillFiles(features, config, templatePath, targetPath) {
772
+ const skillsDir = import_path3.default.join(targetPath, ".kiro/skills");
773
+ await import_fs_extra4.default.ensureDir(skillsDir);
774
+ const baseSkillSource = import_path3.default.join(templatePath, "skills/base.skill.md");
775
+ const baseSkillDest = import_path3.default.join(skillsDir, "project-capabilities.md");
776
+ if (await import_fs_extra4.default.pathExists(baseSkillSource)) {
777
+ await import_fs_extra4.default.copy(baseSkillSource, baseSkillDest);
778
+ logger.info("Copied base skill file");
779
+ }
780
+ for (const featureKey of features) {
781
+ const feature = config.features[featureKey];
782
+ if (feature && feature.skillFile) {
783
+ const sourcePath = import_path3.default.join(templatePath, feature.skillFile.template);
784
+ const destPath = import_path3.default.join(targetPath, feature.skillFile.target);
785
+ if (await import_fs_extra4.default.pathExists(sourcePath)) {
786
+ await import_fs_extra4.default.ensureDir(import_path3.default.dirname(destPath));
787
+ await import_fs_extra4.default.copy(sourcePath, destPath);
788
+ logger.info(`Copied skill: ${feature.skillFile.target}`);
789
+ }
790
+ }
791
+ }
792
+ await generateCapabilitiesIndex(features, config, targetPath);
793
+ }
794
+ async function generateCapabilitiesIndex(features, config, targetPath) {
795
+ const featuresList = features.map((featureKey) => {
796
+ const feature = config.features[featureKey];
797
+ if (!feature) return "";
798
+ const packages = Object.keys(feature.packages.dependencies).join(", ");
799
+ return `### ${feature.name}
800
+
801
+ ${feature.description}
802
+
803
+ - \u{1F4E6} \u5305\uFF1A${packages}
804
+ - \u{1F4DD} \u793A\u4F8B\u4EE3\u7801\uFF1A\`${feature.exampleFiles.target}\`
805
+ - \u{1F4DA} \u8BE6\u7EC6\u6587\u6863\uFF1A\u67E5\u770B \`.kiro/skills/${featureKey}.md\`
806
+ `;
807
+ }).join("\n");
808
+ const content = `# \u9879\u76EE\u80FD\u529B\u7D22\u5F15
809
+
810
+ \u672C\u9879\u76EE\u57FA\u4E8E Svton \u6846\u67B6\u521B\u5EFA\uFF0C\u5DF2\u96C6\u6210\u4EE5\u4E0B\u529F\u80FD\u6A21\u5757\uFF1A
811
+
812
+ ## \u5DF2\u542F\u7528\u7684\u529F\u80FD
813
+
814
+ ${featuresList}
815
+
816
+ ## \u4F7F\u7528\u5EFA\u8BAE
817
+
818
+ \u5F53\u4F60\u9700\u8981\u4F7F\u7528\u67D0\u4E2A\u529F\u80FD\u65F6\uFF0C\u53EF\u4EE5\uFF1A
819
+
820
+ 1. \u67E5\u770B\u5BF9\u5E94\u7684 skill \u6587\u6863\u4E86\u89E3 API \u548C\u6700\u4F73\u5B9E\u8DF5
821
+ 2. \u53C2\u8003 \`src/examples/\` \u76EE\u5F55\u4E0B\u7684\u793A\u4F8B\u4EE3\u7801
822
+ 3. \u67E5\u770B\u5B98\u65B9\u6587\u6863\u83B7\u53D6\u66F4\u591A\u4FE1\u606F
823
+
824
+ ## \u6587\u6863\u8D44\u6E90
825
+
826
+ - Svton \u5B98\u65B9\u6587\u6863\uFF1Ahttps://751848178.github.io/svton
827
+ - GitHub\uFF1Ahttps://github.com/751848178/svton
828
+ `;
829
+ const indexPath = import_path3.default.join(targetPath, ".kiro/skills/project-capabilities.md");
830
+ await import_fs_extra4.default.writeFile(indexPath, content);
831
+ logger.info("Generated capabilities index");
832
+ }
833
+ async function updatePackageJson(features, config, targetPath) {
834
+ const packageJsonPath = import_path3.default.join(targetPath, "package.json");
835
+ const packageJson = await import_fs_extra4.default.readJSON(packageJsonPath);
836
+ const dependencies = collectDependencies(features, config);
837
+ packageJson.dependencies = {
838
+ ...packageJson.dependencies,
839
+ ...dependencies
840
+ };
841
+ await import_fs_extra4.default.writeJSON(packageJsonPath, packageJson, { spaces: 2 });
842
+ logger.info("Updated package.json with feature dependencies");
843
+ }
844
+ function generateModuleImports(features, config) {
845
+ const imports = [];
846
+ for (const featureKey of features) {
847
+ const feature = config.features[featureKey];
848
+ if (feature && feature.moduleImports) {
849
+ for (const moduleImport of feature.moduleImports) {
850
+ imports.push(`import { ${moduleImport.import} } from '${moduleImport.from}';`);
851
+ }
852
+ }
853
+ }
854
+ return imports.join("\n");
855
+ }
856
+ function generateModuleRegistrations(features, config) {
857
+ const registrations = [];
858
+ for (const featureKey of features) {
859
+ const feature = config.features[featureKey];
860
+ if (feature && feature.moduleRegistration) {
861
+ const { module: moduleName, config: moduleConfig } = feature.moduleRegistration;
862
+ registrations.push(` ${moduleName}.${feature.moduleRegistration.type}({
863
+ useFactory: (configService: ConfigService) => ${moduleConfig},
864
+ inject: [ConfigService],
865
+ }),`);
866
+ }
867
+ }
868
+ return registrations.join("\n");
869
+ }
870
+ async function updateAppModule(features, config, targetPath) {
871
+ const appModulePath = import_path3.default.join(targetPath, "src/app.module.ts");
872
+ if (!await import_fs_extra4.default.pathExists(appModulePath)) {
873
+ logger.warn("app.module.ts not found, skipping module injection");
874
+ return;
875
+ }
876
+ let content = await import_fs_extra4.default.readFile(appModulePath, "utf-8");
877
+ const imports = generateModuleImports(features, config);
878
+ const registrations = generateModuleRegistrations(features, config);
879
+ const importsMatch = content.match(/imports:\s*\[([\s\S]*?)\]/);
880
+ if (importsMatch) {
881
+ const existingImports = importsMatch[1];
882
+ const newImports = `${existingImports}
883
+ ${registrations}`;
884
+ content = content.replace(
885
+ /imports:\s*\[([\s\S]*?)\]/,
886
+ `imports: [${newImports}
887
+ ]`
888
+ );
889
+ }
890
+ const lastImportIndex = content.lastIndexOf("import ");
891
+ const lastImportEnd = content.indexOf("\n", lastImportIndex);
892
+ content = content.slice(0, lastImportEnd + 1) + imports + "\n" + content.slice(lastImportEnd + 1);
893
+ await import_fs_extra4.default.writeFile(appModulePath, content);
894
+ logger.info("Updated app.module.ts with feature modules");
895
+ }
896
+
684
897
  // src/commands/create.ts
685
898
  async function createProject(projectName, options = {}) {
686
899
  try {
@@ -692,18 +905,21 @@ async function createProject(projectName, options = {}) {
692
905
  }
693
906
  process.exit(1);
694
907
  }
695
- const projectPath = import_path3.default.resolve(process.cwd(), projectName);
696
- if (await import_fs_extra4.default.pathExists(projectPath)) {
908
+ const projectPath = import_path4.default.resolve(process.cwd(), projectName);
909
+ if (await import_fs_extra5.default.pathExists(projectPath)) {
697
910
  logger.error(`Directory ${projectName} already exists!`);
698
911
  process.exit(1);
699
912
  }
700
913
  logger.info(import_chalk2.default.blue("\u{1F680} Welcome to Svton App Generator!"));
701
914
  logger.info("");
915
+ const featuresConfig = await loadFeaturesConfig();
702
916
  let answers;
703
917
  if (options.yes) {
704
918
  answers = {
705
919
  org: options.org || projectName,
706
920
  template: options.template || "full-stack",
921
+ features: [],
922
+ // 默认不选择额外功能
707
923
  packageManager: options.packageManager || "pnpm",
708
924
  installDeps: !options.skipInstall,
709
925
  initGit: !options.skipGit
@@ -732,6 +948,13 @@ async function createProject(projectName, options = {}) {
732
948
  ],
733
949
  default: options.template || "full-stack"
734
950
  },
951
+ {
952
+ type: "checkbox",
953
+ name: "features",
954
+ message: "Select features to include (use space to select, enter to confirm):",
955
+ choices: getFeatureChoices(featuresConfig),
956
+ when: (answers2) => answers2.template === "backend-only" || answers2.template === "full-stack"
957
+ },
735
958
  {
736
959
  type: "list",
737
960
  name: "packageManager",
@@ -757,6 +980,7 @@ async function createProject(projectName, options = {}) {
757
980
  projectName,
758
981
  orgName: answers.org.startsWith("@") ? answers.org : `@${answers.org}`,
759
982
  template: answers.template,
983
+ features: answers.features || [],
760
984
  packageManager: answers.packageManager,
761
985
  installDeps: answers.installDeps,
762
986
  initGit: answers.initGit,
@@ -767,6 +991,9 @@ async function createProject(projectName, options = {}) {
767
991
  logger.info(` Project Name: ${import_chalk2.default.white(config.projectName)}`);
768
992
  logger.info(` Organization: ${import_chalk2.default.white(config.orgName)}`);
769
993
  logger.info(` Template: ${import_chalk2.default.white(config.template)}`);
994
+ if (config.features.length > 0) {
995
+ logger.info(` Features: ${import_chalk2.default.white(config.features.join(", "))}`);
996
+ }
770
997
  logger.info(` Package Manager: ${import_chalk2.default.white(config.packageManager)}`);
771
998
  logger.info(` Install Dependencies: ${import_chalk2.default.white(config.installDeps ? "Yes" : "No")}`);
772
999
  logger.info(` Initialize Git: ${import_chalk2.default.white(config.initGit ? "Yes" : "No")}`);
@@ -811,10 +1038,23 @@ async function createProject(projectName, options = {}) {
811
1038
  async function createProjectFromTemplate(config) {
812
1039
  const spinner = (0, import_ora.default)("Creating project...").start();
813
1040
  try {
814
- await import_fs_extra4.default.ensureDir(config.projectPath);
1041
+ await import_fs_extra5.default.ensureDir(config.projectPath);
815
1042
  process.chdir(config.projectPath);
816
1043
  spinner.text = "Generating project files...";
817
1044
  await generateFromTemplate(config);
1045
+ if (config.features.length > 0) {
1046
+ spinner.text = "Integrating selected features...";
1047
+ const featuresConfig = await loadFeaturesConfig();
1048
+ const templatePath = import_path4.default.join(__dirname, "../../../templates");
1049
+ await updatePackageJson(config.features, featuresConfig, config.projectPath);
1050
+ await copyConfigFiles(config.features, featuresConfig, templatePath, config.projectPath);
1051
+ await copyExampleFiles(config.features, featuresConfig, templatePath, config.projectPath);
1052
+ await copySkillFiles(config.features, featuresConfig, templatePath, config.projectPath);
1053
+ await generateEnvExample(config.features, featuresConfig, config.projectPath);
1054
+ if (config.template === "backend-only" || config.template === "full-stack") {
1055
+ await updateAppModule(config.features, featuresConfig, config.projectPath);
1056
+ }
1057
+ }
818
1058
  if (config.installDeps) {
819
1059
  spinner.text = "Installing dependencies...";
820
1060
  await installDependencies(config.packageManager);
@@ -831,7 +1071,7 @@ async function createProjectFromTemplate(config) {
831
1071
  }
832
1072
 
833
1073
  // package.json
834
- var version = "1.1.0";
1074
+ var version = "1.2.0";
835
1075
 
836
1076
  // src/index.ts
837
1077
  async function cli() {
package/dist/index.mjs CHANGED
@@ -13,8 +13,8 @@ import { Command } from "commander";
13
13
  import inquirer from "inquirer";
14
14
  import chalk2 from "chalk";
15
15
  import ora from "ora";
16
- import fs4 from "fs-extra";
17
- import path3 from "path";
16
+ import fs5 from "fs-extra";
17
+ import path4 from "path";
18
18
  import validateNpmPackageName from "validate-npm-package-name";
19
19
 
20
20
  // src/utils/template.ts
@@ -654,6 +654,219 @@ async function initGit(projectName) {
654
654
  }
655
655
  }
656
656
 
657
+ // src/utils/features.ts
658
+ import fs4 from "fs-extra";
659
+ import path3 from "path";
660
+ async function loadFeaturesConfig() {
661
+ const configPath = path3.join(__dirname, "../../features.json");
662
+ return await fs4.readJSON(configPath);
663
+ }
664
+ function getFeatureChoices(config) {
665
+ return Object.entries(config.features).map(([key, feature]) => ({
666
+ name: `${feature.name} - ${feature.description}`,
667
+ value: key,
668
+ checked: false
669
+ }));
670
+ }
671
+ function collectDependencies(features, config) {
672
+ const dependencies = {};
673
+ for (const featureKey of features) {
674
+ const feature = config.features[featureKey];
675
+ if (feature) {
676
+ Object.assign(dependencies, feature.packages.dependencies);
677
+ }
678
+ }
679
+ return dependencies;
680
+ }
681
+ function collectEnvVars(features, config) {
682
+ const envVars = [];
683
+ const seen = /* @__PURE__ */ new Set();
684
+ for (const featureKey of features) {
685
+ const feature = config.features[featureKey];
686
+ if (feature) {
687
+ for (const envVar of feature.envVars) {
688
+ if (!seen.has(envVar.key)) {
689
+ envVars.push(envVar);
690
+ seen.add(envVar.key);
691
+ }
692
+ }
693
+ }
694
+ }
695
+ return envVars;
696
+ }
697
+ async function generateEnvExample(features, config, targetPath) {
698
+ const envVars = collectEnvVars(features, config);
699
+ const content = [
700
+ "# Environment Variables",
701
+ "# Copy this file to .env and fill in the values",
702
+ "",
703
+ ...envVars.map((envVar) => {
704
+ const lines = [];
705
+ if (envVar.description) {
706
+ lines.push(`# ${envVar.description}`);
707
+ }
708
+ lines.push(`${envVar.key}=${envVar.default}`);
709
+ lines.push("");
710
+ return lines.join("\n");
711
+ })
712
+ ].join("\n");
713
+ await fs4.writeFile(path3.join(targetPath, ".env.example"), content);
714
+ logger.info("Generated .env.example");
715
+ }
716
+ async function copyConfigFiles(features, config, templatePath, targetPath) {
717
+ for (const featureKey of features) {
718
+ const feature = config.features[featureKey];
719
+ if (feature && feature.configFiles) {
720
+ for (const configFile of feature.configFiles) {
721
+ const sourcePath = path3.join(templatePath, configFile.template);
722
+ const destPath = path3.join(targetPath, configFile.path);
723
+ await fs4.ensureDir(path3.dirname(destPath));
724
+ await fs4.copy(sourcePath, destPath);
725
+ logger.info(`Copied config: ${configFile.path}`);
726
+ }
727
+ }
728
+ }
729
+ }
730
+ async function copyExampleFiles(features, config, templatePath, targetPath) {
731
+ for (const featureKey of features) {
732
+ const feature = config.features[featureKey];
733
+ if (feature && feature.exampleFiles) {
734
+ const sourcePath = path3.join(templatePath, feature.exampleFiles.source);
735
+ const destPath = path3.join(targetPath, feature.exampleFiles.target);
736
+ if (await fs4.pathExists(sourcePath)) {
737
+ await fs4.ensureDir(path3.dirname(destPath));
738
+ await fs4.copy(sourcePath, destPath);
739
+ logger.info(`Copied examples: ${feature.exampleFiles.target}`);
740
+ }
741
+ }
742
+ }
743
+ }
744
+ async function copySkillFiles(features, config, templatePath, targetPath) {
745
+ const skillsDir = path3.join(targetPath, ".kiro/skills");
746
+ await fs4.ensureDir(skillsDir);
747
+ const baseSkillSource = path3.join(templatePath, "skills/base.skill.md");
748
+ const baseSkillDest = path3.join(skillsDir, "project-capabilities.md");
749
+ if (await fs4.pathExists(baseSkillSource)) {
750
+ await fs4.copy(baseSkillSource, baseSkillDest);
751
+ logger.info("Copied base skill file");
752
+ }
753
+ for (const featureKey of features) {
754
+ const feature = config.features[featureKey];
755
+ if (feature && feature.skillFile) {
756
+ const sourcePath = path3.join(templatePath, feature.skillFile.template);
757
+ const destPath = path3.join(targetPath, feature.skillFile.target);
758
+ if (await fs4.pathExists(sourcePath)) {
759
+ await fs4.ensureDir(path3.dirname(destPath));
760
+ await fs4.copy(sourcePath, destPath);
761
+ logger.info(`Copied skill: ${feature.skillFile.target}`);
762
+ }
763
+ }
764
+ }
765
+ await generateCapabilitiesIndex(features, config, targetPath);
766
+ }
767
+ async function generateCapabilitiesIndex(features, config, targetPath) {
768
+ const featuresList = features.map((featureKey) => {
769
+ const feature = config.features[featureKey];
770
+ if (!feature) return "";
771
+ const packages = Object.keys(feature.packages.dependencies).join(", ");
772
+ return `### ${feature.name}
773
+
774
+ ${feature.description}
775
+
776
+ - \u{1F4E6} \u5305\uFF1A${packages}
777
+ - \u{1F4DD} \u793A\u4F8B\u4EE3\u7801\uFF1A\`${feature.exampleFiles.target}\`
778
+ - \u{1F4DA} \u8BE6\u7EC6\u6587\u6863\uFF1A\u67E5\u770B \`.kiro/skills/${featureKey}.md\`
779
+ `;
780
+ }).join("\n");
781
+ const content = `# \u9879\u76EE\u80FD\u529B\u7D22\u5F15
782
+
783
+ \u672C\u9879\u76EE\u57FA\u4E8E Svton \u6846\u67B6\u521B\u5EFA\uFF0C\u5DF2\u96C6\u6210\u4EE5\u4E0B\u529F\u80FD\u6A21\u5757\uFF1A
784
+
785
+ ## \u5DF2\u542F\u7528\u7684\u529F\u80FD
786
+
787
+ ${featuresList}
788
+
789
+ ## \u4F7F\u7528\u5EFA\u8BAE
790
+
791
+ \u5F53\u4F60\u9700\u8981\u4F7F\u7528\u67D0\u4E2A\u529F\u80FD\u65F6\uFF0C\u53EF\u4EE5\uFF1A
792
+
793
+ 1. \u67E5\u770B\u5BF9\u5E94\u7684 skill \u6587\u6863\u4E86\u89E3 API \u548C\u6700\u4F73\u5B9E\u8DF5
794
+ 2. \u53C2\u8003 \`src/examples/\` \u76EE\u5F55\u4E0B\u7684\u793A\u4F8B\u4EE3\u7801
795
+ 3. \u67E5\u770B\u5B98\u65B9\u6587\u6863\u83B7\u53D6\u66F4\u591A\u4FE1\u606F
796
+
797
+ ## \u6587\u6863\u8D44\u6E90
798
+
799
+ - Svton \u5B98\u65B9\u6587\u6863\uFF1Ahttps://751848178.github.io/svton
800
+ - GitHub\uFF1Ahttps://github.com/751848178/svton
801
+ `;
802
+ const indexPath = path3.join(targetPath, ".kiro/skills/project-capabilities.md");
803
+ await fs4.writeFile(indexPath, content);
804
+ logger.info("Generated capabilities index");
805
+ }
806
+ async function updatePackageJson(features, config, targetPath) {
807
+ const packageJsonPath = path3.join(targetPath, "package.json");
808
+ const packageJson = await fs4.readJSON(packageJsonPath);
809
+ const dependencies = collectDependencies(features, config);
810
+ packageJson.dependencies = {
811
+ ...packageJson.dependencies,
812
+ ...dependencies
813
+ };
814
+ await fs4.writeJSON(packageJsonPath, packageJson, { spaces: 2 });
815
+ logger.info("Updated package.json with feature dependencies");
816
+ }
817
+ function generateModuleImports(features, config) {
818
+ const imports = [];
819
+ for (const featureKey of features) {
820
+ const feature = config.features[featureKey];
821
+ if (feature && feature.moduleImports) {
822
+ for (const moduleImport of feature.moduleImports) {
823
+ imports.push(`import { ${moduleImport.import} } from '${moduleImport.from}';`);
824
+ }
825
+ }
826
+ }
827
+ return imports.join("\n");
828
+ }
829
+ function generateModuleRegistrations(features, config) {
830
+ const registrations = [];
831
+ for (const featureKey of features) {
832
+ const feature = config.features[featureKey];
833
+ if (feature && feature.moduleRegistration) {
834
+ const { module: moduleName, config: moduleConfig } = feature.moduleRegistration;
835
+ registrations.push(` ${moduleName}.${feature.moduleRegistration.type}({
836
+ useFactory: (configService: ConfigService) => ${moduleConfig},
837
+ inject: [ConfigService],
838
+ }),`);
839
+ }
840
+ }
841
+ return registrations.join("\n");
842
+ }
843
+ async function updateAppModule(features, config, targetPath) {
844
+ const appModulePath = path3.join(targetPath, "src/app.module.ts");
845
+ if (!await fs4.pathExists(appModulePath)) {
846
+ logger.warn("app.module.ts not found, skipping module injection");
847
+ return;
848
+ }
849
+ let content = await fs4.readFile(appModulePath, "utf-8");
850
+ const imports = generateModuleImports(features, config);
851
+ const registrations = generateModuleRegistrations(features, config);
852
+ const importsMatch = content.match(/imports:\s*\[([\s\S]*?)\]/);
853
+ if (importsMatch) {
854
+ const existingImports = importsMatch[1];
855
+ const newImports = `${existingImports}
856
+ ${registrations}`;
857
+ content = content.replace(
858
+ /imports:\s*\[([\s\S]*?)\]/,
859
+ `imports: [${newImports}
860
+ ]`
861
+ );
862
+ }
863
+ const lastImportIndex = content.lastIndexOf("import ");
864
+ const lastImportEnd = content.indexOf("\n", lastImportIndex);
865
+ content = content.slice(0, lastImportEnd + 1) + imports + "\n" + content.slice(lastImportEnd + 1);
866
+ await fs4.writeFile(appModulePath, content);
867
+ logger.info("Updated app.module.ts with feature modules");
868
+ }
869
+
657
870
  // src/commands/create.ts
658
871
  async function createProject(projectName, options = {}) {
659
872
  try {
@@ -665,18 +878,21 @@ async function createProject(projectName, options = {}) {
665
878
  }
666
879
  process.exit(1);
667
880
  }
668
- const projectPath = path3.resolve(process.cwd(), projectName);
669
- if (await fs4.pathExists(projectPath)) {
881
+ const projectPath = path4.resolve(process.cwd(), projectName);
882
+ if (await fs5.pathExists(projectPath)) {
670
883
  logger.error(`Directory ${projectName} already exists!`);
671
884
  process.exit(1);
672
885
  }
673
886
  logger.info(chalk2.blue("\u{1F680} Welcome to Svton App Generator!"));
674
887
  logger.info("");
888
+ const featuresConfig = await loadFeaturesConfig();
675
889
  let answers;
676
890
  if (options.yes) {
677
891
  answers = {
678
892
  org: options.org || projectName,
679
893
  template: options.template || "full-stack",
894
+ features: [],
895
+ // 默认不选择额外功能
680
896
  packageManager: options.packageManager || "pnpm",
681
897
  installDeps: !options.skipInstall,
682
898
  initGit: !options.skipGit
@@ -705,6 +921,13 @@ async function createProject(projectName, options = {}) {
705
921
  ],
706
922
  default: options.template || "full-stack"
707
923
  },
924
+ {
925
+ type: "checkbox",
926
+ name: "features",
927
+ message: "Select features to include (use space to select, enter to confirm):",
928
+ choices: getFeatureChoices(featuresConfig),
929
+ when: (answers2) => answers2.template === "backend-only" || answers2.template === "full-stack"
930
+ },
708
931
  {
709
932
  type: "list",
710
933
  name: "packageManager",
@@ -730,6 +953,7 @@ async function createProject(projectName, options = {}) {
730
953
  projectName,
731
954
  orgName: answers.org.startsWith("@") ? answers.org : `@${answers.org}`,
732
955
  template: answers.template,
956
+ features: answers.features || [],
733
957
  packageManager: answers.packageManager,
734
958
  installDeps: answers.installDeps,
735
959
  initGit: answers.initGit,
@@ -740,6 +964,9 @@ async function createProject(projectName, options = {}) {
740
964
  logger.info(` Project Name: ${chalk2.white(config.projectName)}`);
741
965
  logger.info(` Organization: ${chalk2.white(config.orgName)}`);
742
966
  logger.info(` Template: ${chalk2.white(config.template)}`);
967
+ if (config.features.length > 0) {
968
+ logger.info(` Features: ${chalk2.white(config.features.join(", "))}`);
969
+ }
743
970
  logger.info(` Package Manager: ${chalk2.white(config.packageManager)}`);
744
971
  logger.info(` Install Dependencies: ${chalk2.white(config.installDeps ? "Yes" : "No")}`);
745
972
  logger.info(` Initialize Git: ${chalk2.white(config.initGit ? "Yes" : "No")}`);
@@ -784,10 +1011,23 @@ async function createProject(projectName, options = {}) {
784
1011
  async function createProjectFromTemplate(config) {
785
1012
  const spinner = ora("Creating project...").start();
786
1013
  try {
787
- await fs4.ensureDir(config.projectPath);
1014
+ await fs5.ensureDir(config.projectPath);
788
1015
  process.chdir(config.projectPath);
789
1016
  spinner.text = "Generating project files...";
790
1017
  await generateFromTemplate(config);
1018
+ if (config.features.length > 0) {
1019
+ spinner.text = "Integrating selected features...";
1020
+ const featuresConfig = await loadFeaturesConfig();
1021
+ const templatePath = path4.join(__dirname, "../../../templates");
1022
+ await updatePackageJson(config.features, featuresConfig, config.projectPath);
1023
+ await copyConfigFiles(config.features, featuresConfig, templatePath, config.projectPath);
1024
+ await copyExampleFiles(config.features, featuresConfig, templatePath, config.projectPath);
1025
+ await copySkillFiles(config.features, featuresConfig, templatePath, config.projectPath);
1026
+ await generateEnvExample(config.features, featuresConfig, config.projectPath);
1027
+ if (config.template === "backend-only" || config.template === "full-stack") {
1028
+ await updateAppModule(config.features, featuresConfig, config.projectPath);
1029
+ }
1030
+ }
791
1031
  if (config.installDeps) {
792
1032
  spinner.text = "Installing dependencies...";
793
1033
  await installDependencies(config.packageManager);
@@ -804,7 +1044,7 @@ async function createProjectFromTemplate(config) {
804
1044
  }
805
1045
 
806
1046
  // package.json
807
- var version = "1.1.0";
1047
+ var version = "1.2.0";
808
1048
 
809
1049
  // src/index.ts
810
1050
  async function cli() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@svton/cli",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Svton CLI - Create full-stack applications with NestJS, Next.js, and Taro",
5
5
  "keywords": [
6
6
  "cli",