@svton/cli 1.1.0 → 1.2.1
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 +252 -6
- package/dist/index.mjs +252 -6
- package/features.json +339 -0
- package/package.json +2 -1
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
|
|
44
|
-
var
|
|
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,225 @@ 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
|
+
if (!import_fs_extra4.default.existsSync(configPath)) {
|
|
690
|
+
const devPath = import_path3.default.join(__dirname, "../../features.json");
|
|
691
|
+
if (import_fs_extra4.default.existsSync(devPath)) {
|
|
692
|
+
return await import_fs_extra4.default.readJSON(devPath);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
return await import_fs_extra4.default.readJSON(configPath);
|
|
696
|
+
}
|
|
697
|
+
function getFeatureChoices(config) {
|
|
698
|
+
return Object.entries(config.features).map(([key, feature]) => ({
|
|
699
|
+
name: `${feature.name} - ${feature.description}`,
|
|
700
|
+
value: key,
|
|
701
|
+
checked: false
|
|
702
|
+
}));
|
|
703
|
+
}
|
|
704
|
+
function collectDependencies(features, config) {
|
|
705
|
+
const dependencies = {};
|
|
706
|
+
for (const featureKey of features) {
|
|
707
|
+
const feature = config.features[featureKey];
|
|
708
|
+
if (feature) {
|
|
709
|
+
Object.assign(dependencies, feature.packages.dependencies);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
return dependencies;
|
|
713
|
+
}
|
|
714
|
+
function collectEnvVars(features, config) {
|
|
715
|
+
const envVars = [];
|
|
716
|
+
const seen = /* @__PURE__ */ new Set();
|
|
717
|
+
for (const featureKey of features) {
|
|
718
|
+
const feature = config.features[featureKey];
|
|
719
|
+
if (feature) {
|
|
720
|
+
for (const envVar of feature.envVars) {
|
|
721
|
+
if (!seen.has(envVar.key)) {
|
|
722
|
+
envVars.push(envVar);
|
|
723
|
+
seen.add(envVar.key);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
return envVars;
|
|
729
|
+
}
|
|
730
|
+
async function generateEnvExample(features, config, targetPath) {
|
|
731
|
+
const envVars = collectEnvVars(features, config);
|
|
732
|
+
const content = [
|
|
733
|
+
"# Environment Variables",
|
|
734
|
+
"# Copy this file to .env and fill in the values",
|
|
735
|
+
"",
|
|
736
|
+
...envVars.map((envVar) => {
|
|
737
|
+
const lines = [];
|
|
738
|
+
if (envVar.description) {
|
|
739
|
+
lines.push(`# ${envVar.description}`);
|
|
740
|
+
}
|
|
741
|
+
lines.push(`${envVar.key}=${envVar.default}`);
|
|
742
|
+
lines.push("");
|
|
743
|
+
return lines.join("\n");
|
|
744
|
+
})
|
|
745
|
+
].join("\n");
|
|
746
|
+
await import_fs_extra4.default.writeFile(import_path3.default.join(targetPath, ".env.example"), content);
|
|
747
|
+
logger.info("Generated .env.example");
|
|
748
|
+
}
|
|
749
|
+
async function copyConfigFiles(features, config, templatePath, targetPath) {
|
|
750
|
+
for (const featureKey of features) {
|
|
751
|
+
const feature = config.features[featureKey];
|
|
752
|
+
if (feature && feature.configFiles) {
|
|
753
|
+
for (const configFile of feature.configFiles) {
|
|
754
|
+
const sourcePath = import_path3.default.join(templatePath, configFile.template);
|
|
755
|
+
const destPath = import_path3.default.join(targetPath, configFile.path);
|
|
756
|
+
await import_fs_extra4.default.ensureDir(import_path3.default.dirname(destPath));
|
|
757
|
+
await import_fs_extra4.default.copy(sourcePath, destPath);
|
|
758
|
+
logger.info(`Copied config: ${configFile.path}`);
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
async function copyExampleFiles(features, config, templatePath, targetPath) {
|
|
764
|
+
for (const featureKey of features) {
|
|
765
|
+
const feature = config.features[featureKey];
|
|
766
|
+
if (feature && feature.exampleFiles) {
|
|
767
|
+
const sourcePath = import_path3.default.join(templatePath, feature.exampleFiles.source);
|
|
768
|
+
const destPath = import_path3.default.join(targetPath, feature.exampleFiles.target);
|
|
769
|
+
if (await import_fs_extra4.default.pathExists(sourcePath)) {
|
|
770
|
+
await import_fs_extra4.default.ensureDir(import_path3.default.dirname(destPath));
|
|
771
|
+
await import_fs_extra4.default.copy(sourcePath, destPath);
|
|
772
|
+
logger.info(`Copied examples: ${feature.exampleFiles.target}`);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
async function copySkillFiles(features, config, templatePath, targetPath) {
|
|
778
|
+
const skillsDir = import_path3.default.join(targetPath, ".kiro/skills");
|
|
779
|
+
await import_fs_extra4.default.ensureDir(skillsDir);
|
|
780
|
+
const baseSkillSource = import_path3.default.join(templatePath, "skills/base.skill.md");
|
|
781
|
+
const baseSkillDest = import_path3.default.join(skillsDir, "project-capabilities.md");
|
|
782
|
+
if (await import_fs_extra4.default.pathExists(baseSkillSource)) {
|
|
783
|
+
await import_fs_extra4.default.copy(baseSkillSource, baseSkillDest);
|
|
784
|
+
logger.info("Copied base skill file");
|
|
785
|
+
}
|
|
786
|
+
for (const featureKey of features) {
|
|
787
|
+
const feature = config.features[featureKey];
|
|
788
|
+
if (feature && feature.skillFile) {
|
|
789
|
+
const sourcePath = import_path3.default.join(templatePath, feature.skillFile.template);
|
|
790
|
+
const destPath = import_path3.default.join(targetPath, feature.skillFile.target);
|
|
791
|
+
if (await import_fs_extra4.default.pathExists(sourcePath)) {
|
|
792
|
+
await import_fs_extra4.default.ensureDir(import_path3.default.dirname(destPath));
|
|
793
|
+
await import_fs_extra4.default.copy(sourcePath, destPath);
|
|
794
|
+
logger.info(`Copied skill: ${feature.skillFile.target}`);
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
await generateCapabilitiesIndex(features, config, targetPath);
|
|
799
|
+
}
|
|
800
|
+
async function generateCapabilitiesIndex(features, config, targetPath) {
|
|
801
|
+
const featuresList = features.map((featureKey) => {
|
|
802
|
+
const feature = config.features[featureKey];
|
|
803
|
+
if (!feature) return "";
|
|
804
|
+
const packages = Object.keys(feature.packages.dependencies).join(", ");
|
|
805
|
+
return `### ${feature.name}
|
|
806
|
+
|
|
807
|
+
${feature.description}
|
|
808
|
+
|
|
809
|
+
- \u{1F4E6} \u5305\uFF1A${packages}
|
|
810
|
+
- \u{1F4DD} \u793A\u4F8B\u4EE3\u7801\uFF1A\`${feature.exampleFiles.target}\`
|
|
811
|
+
- \u{1F4DA} \u8BE6\u7EC6\u6587\u6863\uFF1A\u67E5\u770B \`.kiro/skills/${featureKey}.md\`
|
|
812
|
+
`;
|
|
813
|
+
}).join("\n");
|
|
814
|
+
const content = `# \u9879\u76EE\u80FD\u529B\u7D22\u5F15
|
|
815
|
+
|
|
816
|
+
\u672C\u9879\u76EE\u57FA\u4E8E Svton \u6846\u67B6\u521B\u5EFA\uFF0C\u5DF2\u96C6\u6210\u4EE5\u4E0B\u529F\u80FD\u6A21\u5757\uFF1A
|
|
817
|
+
|
|
818
|
+
## \u5DF2\u542F\u7528\u7684\u529F\u80FD
|
|
819
|
+
|
|
820
|
+
${featuresList}
|
|
821
|
+
|
|
822
|
+
## \u4F7F\u7528\u5EFA\u8BAE
|
|
823
|
+
|
|
824
|
+
\u5F53\u4F60\u9700\u8981\u4F7F\u7528\u67D0\u4E2A\u529F\u80FD\u65F6\uFF0C\u53EF\u4EE5\uFF1A
|
|
825
|
+
|
|
826
|
+
1. \u67E5\u770B\u5BF9\u5E94\u7684 skill \u6587\u6863\u4E86\u89E3 API \u548C\u6700\u4F73\u5B9E\u8DF5
|
|
827
|
+
2. \u53C2\u8003 \`src/examples/\` \u76EE\u5F55\u4E0B\u7684\u793A\u4F8B\u4EE3\u7801
|
|
828
|
+
3. \u67E5\u770B\u5B98\u65B9\u6587\u6863\u83B7\u53D6\u66F4\u591A\u4FE1\u606F
|
|
829
|
+
|
|
830
|
+
## \u6587\u6863\u8D44\u6E90
|
|
831
|
+
|
|
832
|
+
- Svton \u5B98\u65B9\u6587\u6863\uFF1Ahttps://751848178.github.io/svton
|
|
833
|
+
- GitHub\uFF1Ahttps://github.com/751848178/svton
|
|
834
|
+
`;
|
|
835
|
+
const indexPath = import_path3.default.join(targetPath, ".kiro/skills/project-capabilities.md");
|
|
836
|
+
await import_fs_extra4.default.writeFile(indexPath, content);
|
|
837
|
+
logger.info("Generated capabilities index");
|
|
838
|
+
}
|
|
839
|
+
async function updatePackageJson(features, config, targetPath) {
|
|
840
|
+
const packageJsonPath = import_path3.default.join(targetPath, "package.json");
|
|
841
|
+
const packageJson = await import_fs_extra4.default.readJSON(packageJsonPath);
|
|
842
|
+
const dependencies = collectDependencies(features, config);
|
|
843
|
+
packageJson.dependencies = {
|
|
844
|
+
...packageJson.dependencies,
|
|
845
|
+
...dependencies
|
|
846
|
+
};
|
|
847
|
+
await import_fs_extra4.default.writeJSON(packageJsonPath, packageJson, { spaces: 2 });
|
|
848
|
+
logger.info("Updated package.json with feature dependencies");
|
|
849
|
+
}
|
|
850
|
+
function generateModuleImports(features, config) {
|
|
851
|
+
const imports = [];
|
|
852
|
+
for (const featureKey of features) {
|
|
853
|
+
const feature = config.features[featureKey];
|
|
854
|
+
if (feature && feature.moduleImports) {
|
|
855
|
+
for (const moduleImport of feature.moduleImports) {
|
|
856
|
+
imports.push(`import { ${moduleImport.import} } from '${moduleImport.from}';`);
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
return imports.join("\n");
|
|
861
|
+
}
|
|
862
|
+
function generateModuleRegistrations(features, config) {
|
|
863
|
+
const registrations = [];
|
|
864
|
+
for (const featureKey of features) {
|
|
865
|
+
const feature = config.features[featureKey];
|
|
866
|
+
if (feature && feature.moduleRegistration) {
|
|
867
|
+
const { module: moduleName, config: moduleConfig } = feature.moduleRegistration;
|
|
868
|
+
registrations.push(` ${moduleName}.${feature.moduleRegistration.type}({
|
|
869
|
+
useFactory: (configService: ConfigService) => ${moduleConfig},
|
|
870
|
+
inject: [ConfigService],
|
|
871
|
+
}),`);
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
return registrations.join("\n");
|
|
875
|
+
}
|
|
876
|
+
async function updateAppModule(features, config, targetPath) {
|
|
877
|
+
const appModulePath = import_path3.default.join(targetPath, "src/app.module.ts");
|
|
878
|
+
if (!await import_fs_extra4.default.pathExists(appModulePath)) {
|
|
879
|
+
logger.warn("app.module.ts not found, skipping module injection");
|
|
880
|
+
return;
|
|
881
|
+
}
|
|
882
|
+
let content = await import_fs_extra4.default.readFile(appModulePath, "utf-8");
|
|
883
|
+
const imports = generateModuleImports(features, config);
|
|
884
|
+
const registrations = generateModuleRegistrations(features, config);
|
|
885
|
+
const importsMatch = content.match(/imports:\s*\[([\s\S]*?)\]/);
|
|
886
|
+
if (importsMatch) {
|
|
887
|
+
const existingImports = importsMatch[1];
|
|
888
|
+
const newImports = `${existingImports}
|
|
889
|
+
${registrations}`;
|
|
890
|
+
content = content.replace(
|
|
891
|
+
/imports:\s*\[([\s\S]*?)\]/,
|
|
892
|
+
`imports: [${newImports}
|
|
893
|
+
]`
|
|
894
|
+
);
|
|
895
|
+
}
|
|
896
|
+
const lastImportIndex = content.lastIndexOf("import ");
|
|
897
|
+
const lastImportEnd = content.indexOf("\n", lastImportIndex);
|
|
898
|
+
content = content.slice(0, lastImportEnd + 1) + imports + "\n" + content.slice(lastImportEnd + 1);
|
|
899
|
+
await import_fs_extra4.default.writeFile(appModulePath, content);
|
|
900
|
+
logger.info("Updated app.module.ts with feature modules");
|
|
901
|
+
}
|
|
902
|
+
|
|
684
903
|
// src/commands/create.ts
|
|
685
904
|
async function createProject(projectName, options = {}) {
|
|
686
905
|
try {
|
|
@@ -692,18 +911,21 @@ async function createProject(projectName, options = {}) {
|
|
|
692
911
|
}
|
|
693
912
|
process.exit(1);
|
|
694
913
|
}
|
|
695
|
-
const projectPath =
|
|
696
|
-
if (await
|
|
914
|
+
const projectPath = import_path4.default.resolve(process.cwd(), projectName);
|
|
915
|
+
if (await import_fs_extra5.default.pathExists(projectPath)) {
|
|
697
916
|
logger.error(`Directory ${projectName} already exists!`);
|
|
698
917
|
process.exit(1);
|
|
699
918
|
}
|
|
700
919
|
logger.info(import_chalk2.default.blue("\u{1F680} Welcome to Svton App Generator!"));
|
|
701
920
|
logger.info("");
|
|
921
|
+
const featuresConfig = await loadFeaturesConfig();
|
|
702
922
|
let answers;
|
|
703
923
|
if (options.yes) {
|
|
704
924
|
answers = {
|
|
705
925
|
org: options.org || projectName,
|
|
706
926
|
template: options.template || "full-stack",
|
|
927
|
+
features: [],
|
|
928
|
+
// 默认不选择额外功能
|
|
707
929
|
packageManager: options.packageManager || "pnpm",
|
|
708
930
|
installDeps: !options.skipInstall,
|
|
709
931
|
initGit: !options.skipGit
|
|
@@ -732,6 +954,13 @@ async function createProject(projectName, options = {}) {
|
|
|
732
954
|
],
|
|
733
955
|
default: options.template || "full-stack"
|
|
734
956
|
},
|
|
957
|
+
{
|
|
958
|
+
type: "checkbox",
|
|
959
|
+
name: "features",
|
|
960
|
+
message: "Select features to include (use space to select, enter to confirm):",
|
|
961
|
+
choices: getFeatureChoices(featuresConfig),
|
|
962
|
+
when: (answers2) => answers2.template === "backend-only" || answers2.template === "full-stack"
|
|
963
|
+
},
|
|
735
964
|
{
|
|
736
965
|
type: "list",
|
|
737
966
|
name: "packageManager",
|
|
@@ -757,6 +986,7 @@ async function createProject(projectName, options = {}) {
|
|
|
757
986
|
projectName,
|
|
758
987
|
orgName: answers.org.startsWith("@") ? answers.org : `@${answers.org}`,
|
|
759
988
|
template: answers.template,
|
|
989
|
+
features: answers.features || [],
|
|
760
990
|
packageManager: answers.packageManager,
|
|
761
991
|
installDeps: answers.installDeps,
|
|
762
992
|
initGit: answers.initGit,
|
|
@@ -767,6 +997,9 @@ async function createProject(projectName, options = {}) {
|
|
|
767
997
|
logger.info(` Project Name: ${import_chalk2.default.white(config.projectName)}`);
|
|
768
998
|
logger.info(` Organization: ${import_chalk2.default.white(config.orgName)}`);
|
|
769
999
|
logger.info(` Template: ${import_chalk2.default.white(config.template)}`);
|
|
1000
|
+
if (config.features.length > 0) {
|
|
1001
|
+
logger.info(` Features: ${import_chalk2.default.white(config.features.join(", "))}`);
|
|
1002
|
+
}
|
|
770
1003
|
logger.info(` Package Manager: ${import_chalk2.default.white(config.packageManager)}`);
|
|
771
1004
|
logger.info(` Install Dependencies: ${import_chalk2.default.white(config.installDeps ? "Yes" : "No")}`);
|
|
772
1005
|
logger.info(` Initialize Git: ${import_chalk2.default.white(config.initGit ? "Yes" : "No")}`);
|
|
@@ -811,10 +1044,23 @@ async function createProject(projectName, options = {}) {
|
|
|
811
1044
|
async function createProjectFromTemplate(config) {
|
|
812
1045
|
const spinner = (0, import_ora.default)("Creating project...").start();
|
|
813
1046
|
try {
|
|
814
|
-
await
|
|
1047
|
+
await import_fs_extra5.default.ensureDir(config.projectPath);
|
|
815
1048
|
process.chdir(config.projectPath);
|
|
816
1049
|
spinner.text = "Generating project files...";
|
|
817
1050
|
await generateFromTemplate(config);
|
|
1051
|
+
if (config.features.length > 0) {
|
|
1052
|
+
spinner.text = "Integrating selected features...";
|
|
1053
|
+
const featuresConfig = await loadFeaturesConfig();
|
|
1054
|
+
const templatePath = import_path4.default.join(__dirname, "../../../templates");
|
|
1055
|
+
await updatePackageJson(config.features, featuresConfig, config.projectPath);
|
|
1056
|
+
await copyConfigFiles(config.features, featuresConfig, templatePath, config.projectPath);
|
|
1057
|
+
await copyExampleFiles(config.features, featuresConfig, templatePath, config.projectPath);
|
|
1058
|
+
await copySkillFiles(config.features, featuresConfig, templatePath, config.projectPath);
|
|
1059
|
+
await generateEnvExample(config.features, featuresConfig, config.projectPath);
|
|
1060
|
+
if (config.template === "backend-only" || config.template === "full-stack") {
|
|
1061
|
+
await updateAppModule(config.features, featuresConfig, config.projectPath);
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
818
1064
|
if (config.installDeps) {
|
|
819
1065
|
spinner.text = "Installing dependencies...";
|
|
820
1066
|
await installDependencies(config.packageManager);
|
|
@@ -831,7 +1077,7 @@ async function createProjectFromTemplate(config) {
|
|
|
831
1077
|
}
|
|
832
1078
|
|
|
833
1079
|
// package.json
|
|
834
|
-
var version = "1.1
|
|
1080
|
+
var version = "1.2.1";
|
|
835
1081
|
|
|
836
1082
|
// src/index.ts
|
|
837
1083
|
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
|
|
17
|
-
import
|
|
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,225 @@ 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
|
+
if (!fs4.existsSync(configPath)) {
|
|
663
|
+
const devPath = path3.join(__dirname, "../../features.json");
|
|
664
|
+
if (fs4.existsSync(devPath)) {
|
|
665
|
+
return await fs4.readJSON(devPath);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
return await fs4.readJSON(configPath);
|
|
669
|
+
}
|
|
670
|
+
function getFeatureChoices(config) {
|
|
671
|
+
return Object.entries(config.features).map(([key, feature]) => ({
|
|
672
|
+
name: `${feature.name} - ${feature.description}`,
|
|
673
|
+
value: key,
|
|
674
|
+
checked: false
|
|
675
|
+
}));
|
|
676
|
+
}
|
|
677
|
+
function collectDependencies(features, config) {
|
|
678
|
+
const dependencies = {};
|
|
679
|
+
for (const featureKey of features) {
|
|
680
|
+
const feature = config.features[featureKey];
|
|
681
|
+
if (feature) {
|
|
682
|
+
Object.assign(dependencies, feature.packages.dependencies);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
return dependencies;
|
|
686
|
+
}
|
|
687
|
+
function collectEnvVars(features, config) {
|
|
688
|
+
const envVars = [];
|
|
689
|
+
const seen = /* @__PURE__ */ new Set();
|
|
690
|
+
for (const featureKey of features) {
|
|
691
|
+
const feature = config.features[featureKey];
|
|
692
|
+
if (feature) {
|
|
693
|
+
for (const envVar of feature.envVars) {
|
|
694
|
+
if (!seen.has(envVar.key)) {
|
|
695
|
+
envVars.push(envVar);
|
|
696
|
+
seen.add(envVar.key);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
return envVars;
|
|
702
|
+
}
|
|
703
|
+
async function generateEnvExample(features, config, targetPath) {
|
|
704
|
+
const envVars = collectEnvVars(features, config);
|
|
705
|
+
const content = [
|
|
706
|
+
"# Environment Variables",
|
|
707
|
+
"# Copy this file to .env and fill in the values",
|
|
708
|
+
"",
|
|
709
|
+
...envVars.map((envVar) => {
|
|
710
|
+
const lines = [];
|
|
711
|
+
if (envVar.description) {
|
|
712
|
+
lines.push(`# ${envVar.description}`);
|
|
713
|
+
}
|
|
714
|
+
lines.push(`${envVar.key}=${envVar.default}`);
|
|
715
|
+
lines.push("");
|
|
716
|
+
return lines.join("\n");
|
|
717
|
+
})
|
|
718
|
+
].join("\n");
|
|
719
|
+
await fs4.writeFile(path3.join(targetPath, ".env.example"), content);
|
|
720
|
+
logger.info("Generated .env.example");
|
|
721
|
+
}
|
|
722
|
+
async function copyConfigFiles(features, config, templatePath, targetPath) {
|
|
723
|
+
for (const featureKey of features) {
|
|
724
|
+
const feature = config.features[featureKey];
|
|
725
|
+
if (feature && feature.configFiles) {
|
|
726
|
+
for (const configFile of feature.configFiles) {
|
|
727
|
+
const sourcePath = path3.join(templatePath, configFile.template);
|
|
728
|
+
const destPath = path3.join(targetPath, configFile.path);
|
|
729
|
+
await fs4.ensureDir(path3.dirname(destPath));
|
|
730
|
+
await fs4.copy(sourcePath, destPath);
|
|
731
|
+
logger.info(`Copied config: ${configFile.path}`);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
async function copyExampleFiles(features, config, templatePath, targetPath) {
|
|
737
|
+
for (const featureKey of features) {
|
|
738
|
+
const feature = config.features[featureKey];
|
|
739
|
+
if (feature && feature.exampleFiles) {
|
|
740
|
+
const sourcePath = path3.join(templatePath, feature.exampleFiles.source);
|
|
741
|
+
const destPath = path3.join(targetPath, feature.exampleFiles.target);
|
|
742
|
+
if (await fs4.pathExists(sourcePath)) {
|
|
743
|
+
await fs4.ensureDir(path3.dirname(destPath));
|
|
744
|
+
await fs4.copy(sourcePath, destPath);
|
|
745
|
+
logger.info(`Copied examples: ${feature.exampleFiles.target}`);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
async function copySkillFiles(features, config, templatePath, targetPath) {
|
|
751
|
+
const skillsDir = path3.join(targetPath, ".kiro/skills");
|
|
752
|
+
await fs4.ensureDir(skillsDir);
|
|
753
|
+
const baseSkillSource = path3.join(templatePath, "skills/base.skill.md");
|
|
754
|
+
const baseSkillDest = path3.join(skillsDir, "project-capabilities.md");
|
|
755
|
+
if (await fs4.pathExists(baseSkillSource)) {
|
|
756
|
+
await fs4.copy(baseSkillSource, baseSkillDest);
|
|
757
|
+
logger.info("Copied base skill file");
|
|
758
|
+
}
|
|
759
|
+
for (const featureKey of features) {
|
|
760
|
+
const feature = config.features[featureKey];
|
|
761
|
+
if (feature && feature.skillFile) {
|
|
762
|
+
const sourcePath = path3.join(templatePath, feature.skillFile.template);
|
|
763
|
+
const destPath = path3.join(targetPath, feature.skillFile.target);
|
|
764
|
+
if (await fs4.pathExists(sourcePath)) {
|
|
765
|
+
await fs4.ensureDir(path3.dirname(destPath));
|
|
766
|
+
await fs4.copy(sourcePath, destPath);
|
|
767
|
+
logger.info(`Copied skill: ${feature.skillFile.target}`);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
await generateCapabilitiesIndex(features, config, targetPath);
|
|
772
|
+
}
|
|
773
|
+
async function generateCapabilitiesIndex(features, config, targetPath) {
|
|
774
|
+
const featuresList = features.map((featureKey) => {
|
|
775
|
+
const feature = config.features[featureKey];
|
|
776
|
+
if (!feature) return "";
|
|
777
|
+
const packages = Object.keys(feature.packages.dependencies).join(", ");
|
|
778
|
+
return `### ${feature.name}
|
|
779
|
+
|
|
780
|
+
${feature.description}
|
|
781
|
+
|
|
782
|
+
- \u{1F4E6} \u5305\uFF1A${packages}
|
|
783
|
+
- \u{1F4DD} \u793A\u4F8B\u4EE3\u7801\uFF1A\`${feature.exampleFiles.target}\`
|
|
784
|
+
- \u{1F4DA} \u8BE6\u7EC6\u6587\u6863\uFF1A\u67E5\u770B \`.kiro/skills/${featureKey}.md\`
|
|
785
|
+
`;
|
|
786
|
+
}).join("\n");
|
|
787
|
+
const content = `# \u9879\u76EE\u80FD\u529B\u7D22\u5F15
|
|
788
|
+
|
|
789
|
+
\u672C\u9879\u76EE\u57FA\u4E8E Svton \u6846\u67B6\u521B\u5EFA\uFF0C\u5DF2\u96C6\u6210\u4EE5\u4E0B\u529F\u80FD\u6A21\u5757\uFF1A
|
|
790
|
+
|
|
791
|
+
## \u5DF2\u542F\u7528\u7684\u529F\u80FD
|
|
792
|
+
|
|
793
|
+
${featuresList}
|
|
794
|
+
|
|
795
|
+
## \u4F7F\u7528\u5EFA\u8BAE
|
|
796
|
+
|
|
797
|
+
\u5F53\u4F60\u9700\u8981\u4F7F\u7528\u67D0\u4E2A\u529F\u80FD\u65F6\uFF0C\u53EF\u4EE5\uFF1A
|
|
798
|
+
|
|
799
|
+
1. \u67E5\u770B\u5BF9\u5E94\u7684 skill \u6587\u6863\u4E86\u89E3 API \u548C\u6700\u4F73\u5B9E\u8DF5
|
|
800
|
+
2. \u53C2\u8003 \`src/examples/\` \u76EE\u5F55\u4E0B\u7684\u793A\u4F8B\u4EE3\u7801
|
|
801
|
+
3. \u67E5\u770B\u5B98\u65B9\u6587\u6863\u83B7\u53D6\u66F4\u591A\u4FE1\u606F
|
|
802
|
+
|
|
803
|
+
## \u6587\u6863\u8D44\u6E90
|
|
804
|
+
|
|
805
|
+
- Svton \u5B98\u65B9\u6587\u6863\uFF1Ahttps://751848178.github.io/svton
|
|
806
|
+
- GitHub\uFF1Ahttps://github.com/751848178/svton
|
|
807
|
+
`;
|
|
808
|
+
const indexPath = path3.join(targetPath, ".kiro/skills/project-capabilities.md");
|
|
809
|
+
await fs4.writeFile(indexPath, content);
|
|
810
|
+
logger.info("Generated capabilities index");
|
|
811
|
+
}
|
|
812
|
+
async function updatePackageJson(features, config, targetPath) {
|
|
813
|
+
const packageJsonPath = path3.join(targetPath, "package.json");
|
|
814
|
+
const packageJson = await fs4.readJSON(packageJsonPath);
|
|
815
|
+
const dependencies = collectDependencies(features, config);
|
|
816
|
+
packageJson.dependencies = {
|
|
817
|
+
...packageJson.dependencies,
|
|
818
|
+
...dependencies
|
|
819
|
+
};
|
|
820
|
+
await fs4.writeJSON(packageJsonPath, packageJson, { spaces: 2 });
|
|
821
|
+
logger.info("Updated package.json with feature dependencies");
|
|
822
|
+
}
|
|
823
|
+
function generateModuleImports(features, config) {
|
|
824
|
+
const imports = [];
|
|
825
|
+
for (const featureKey of features) {
|
|
826
|
+
const feature = config.features[featureKey];
|
|
827
|
+
if (feature && feature.moduleImports) {
|
|
828
|
+
for (const moduleImport of feature.moduleImports) {
|
|
829
|
+
imports.push(`import { ${moduleImport.import} } from '${moduleImport.from}';`);
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
return imports.join("\n");
|
|
834
|
+
}
|
|
835
|
+
function generateModuleRegistrations(features, config) {
|
|
836
|
+
const registrations = [];
|
|
837
|
+
for (const featureKey of features) {
|
|
838
|
+
const feature = config.features[featureKey];
|
|
839
|
+
if (feature && feature.moduleRegistration) {
|
|
840
|
+
const { module: moduleName, config: moduleConfig } = feature.moduleRegistration;
|
|
841
|
+
registrations.push(` ${moduleName}.${feature.moduleRegistration.type}({
|
|
842
|
+
useFactory: (configService: ConfigService) => ${moduleConfig},
|
|
843
|
+
inject: [ConfigService],
|
|
844
|
+
}),`);
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
return registrations.join("\n");
|
|
848
|
+
}
|
|
849
|
+
async function updateAppModule(features, config, targetPath) {
|
|
850
|
+
const appModulePath = path3.join(targetPath, "src/app.module.ts");
|
|
851
|
+
if (!await fs4.pathExists(appModulePath)) {
|
|
852
|
+
logger.warn("app.module.ts not found, skipping module injection");
|
|
853
|
+
return;
|
|
854
|
+
}
|
|
855
|
+
let content = await fs4.readFile(appModulePath, "utf-8");
|
|
856
|
+
const imports = generateModuleImports(features, config);
|
|
857
|
+
const registrations = generateModuleRegistrations(features, config);
|
|
858
|
+
const importsMatch = content.match(/imports:\s*\[([\s\S]*?)\]/);
|
|
859
|
+
if (importsMatch) {
|
|
860
|
+
const existingImports = importsMatch[1];
|
|
861
|
+
const newImports = `${existingImports}
|
|
862
|
+
${registrations}`;
|
|
863
|
+
content = content.replace(
|
|
864
|
+
/imports:\s*\[([\s\S]*?)\]/,
|
|
865
|
+
`imports: [${newImports}
|
|
866
|
+
]`
|
|
867
|
+
);
|
|
868
|
+
}
|
|
869
|
+
const lastImportIndex = content.lastIndexOf("import ");
|
|
870
|
+
const lastImportEnd = content.indexOf("\n", lastImportIndex);
|
|
871
|
+
content = content.slice(0, lastImportEnd + 1) + imports + "\n" + content.slice(lastImportEnd + 1);
|
|
872
|
+
await fs4.writeFile(appModulePath, content);
|
|
873
|
+
logger.info("Updated app.module.ts with feature modules");
|
|
874
|
+
}
|
|
875
|
+
|
|
657
876
|
// src/commands/create.ts
|
|
658
877
|
async function createProject(projectName, options = {}) {
|
|
659
878
|
try {
|
|
@@ -665,18 +884,21 @@ async function createProject(projectName, options = {}) {
|
|
|
665
884
|
}
|
|
666
885
|
process.exit(1);
|
|
667
886
|
}
|
|
668
|
-
const projectPath =
|
|
669
|
-
if (await
|
|
887
|
+
const projectPath = path4.resolve(process.cwd(), projectName);
|
|
888
|
+
if (await fs5.pathExists(projectPath)) {
|
|
670
889
|
logger.error(`Directory ${projectName} already exists!`);
|
|
671
890
|
process.exit(1);
|
|
672
891
|
}
|
|
673
892
|
logger.info(chalk2.blue("\u{1F680} Welcome to Svton App Generator!"));
|
|
674
893
|
logger.info("");
|
|
894
|
+
const featuresConfig = await loadFeaturesConfig();
|
|
675
895
|
let answers;
|
|
676
896
|
if (options.yes) {
|
|
677
897
|
answers = {
|
|
678
898
|
org: options.org || projectName,
|
|
679
899
|
template: options.template || "full-stack",
|
|
900
|
+
features: [],
|
|
901
|
+
// 默认不选择额外功能
|
|
680
902
|
packageManager: options.packageManager || "pnpm",
|
|
681
903
|
installDeps: !options.skipInstall,
|
|
682
904
|
initGit: !options.skipGit
|
|
@@ -705,6 +927,13 @@ async function createProject(projectName, options = {}) {
|
|
|
705
927
|
],
|
|
706
928
|
default: options.template || "full-stack"
|
|
707
929
|
},
|
|
930
|
+
{
|
|
931
|
+
type: "checkbox",
|
|
932
|
+
name: "features",
|
|
933
|
+
message: "Select features to include (use space to select, enter to confirm):",
|
|
934
|
+
choices: getFeatureChoices(featuresConfig),
|
|
935
|
+
when: (answers2) => answers2.template === "backend-only" || answers2.template === "full-stack"
|
|
936
|
+
},
|
|
708
937
|
{
|
|
709
938
|
type: "list",
|
|
710
939
|
name: "packageManager",
|
|
@@ -730,6 +959,7 @@ async function createProject(projectName, options = {}) {
|
|
|
730
959
|
projectName,
|
|
731
960
|
orgName: answers.org.startsWith("@") ? answers.org : `@${answers.org}`,
|
|
732
961
|
template: answers.template,
|
|
962
|
+
features: answers.features || [],
|
|
733
963
|
packageManager: answers.packageManager,
|
|
734
964
|
installDeps: answers.installDeps,
|
|
735
965
|
initGit: answers.initGit,
|
|
@@ -740,6 +970,9 @@ async function createProject(projectName, options = {}) {
|
|
|
740
970
|
logger.info(` Project Name: ${chalk2.white(config.projectName)}`);
|
|
741
971
|
logger.info(` Organization: ${chalk2.white(config.orgName)}`);
|
|
742
972
|
logger.info(` Template: ${chalk2.white(config.template)}`);
|
|
973
|
+
if (config.features.length > 0) {
|
|
974
|
+
logger.info(` Features: ${chalk2.white(config.features.join(", "))}`);
|
|
975
|
+
}
|
|
743
976
|
logger.info(` Package Manager: ${chalk2.white(config.packageManager)}`);
|
|
744
977
|
logger.info(` Install Dependencies: ${chalk2.white(config.installDeps ? "Yes" : "No")}`);
|
|
745
978
|
logger.info(` Initialize Git: ${chalk2.white(config.initGit ? "Yes" : "No")}`);
|
|
@@ -784,10 +1017,23 @@ async function createProject(projectName, options = {}) {
|
|
|
784
1017
|
async function createProjectFromTemplate(config) {
|
|
785
1018
|
const spinner = ora("Creating project...").start();
|
|
786
1019
|
try {
|
|
787
|
-
await
|
|
1020
|
+
await fs5.ensureDir(config.projectPath);
|
|
788
1021
|
process.chdir(config.projectPath);
|
|
789
1022
|
spinner.text = "Generating project files...";
|
|
790
1023
|
await generateFromTemplate(config);
|
|
1024
|
+
if (config.features.length > 0) {
|
|
1025
|
+
spinner.text = "Integrating selected features...";
|
|
1026
|
+
const featuresConfig = await loadFeaturesConfig();
|
|
1027
|
+
const templatePath = path4.join(__dirname, "../../../templates");
|
|
1028
|
+
await updatePackageJson(config.features, featuresConfig, config.projectPath);
|
|
1029
|
+
await copyConfigFiles(config.features, featuresConfig, templatePath, config.projectPath);
|
|
1030
|
+
await copyExampleFiles(config.features, featuresConfig, templatePath, config.projectPath);
|
|
1031
|
+
await copySkillFiles(config.features, featuresConfig, templatePath, config.projectPath);
|
|
1032
|
+
await generateEnvExample(config.features, featuresConfig, config.projectPath);
|
|
1033
|
+
if (config.template === "backend-only" || config.template === "full-stack") {
|
|
1034
|
+
await updateAppModule(config.features, featuresConfig, config.projectPath);
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
791
1037
|
if (config.installDeps) {
|
|
792
1038
|
spinner.text = "Installing dependencies...";
|
|
793
1039
|
await installDependencies(config.packageManager);
|
|
@@ -804,7 +1050,7 @@ async function createProjectFromTemplate(config) {
|
|
|
804
1050
|
}
|
|
805
1051
|
|
|
806
1052
|
// package.json
|
|
807
|
-
var version = "1.1
|
|
1053
|
+
var version = "1.2.1";
|
|
808
1054
|
|
|
809
1055
|
// src/index.ts
|
|
810
1056
|
async function cli() {
|
package/features.json
ADDED
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
{
|
|
2
|
+
"features": {
|
|
3
|
+
"cache": {
|
|
4
|
+
"name": "缓存",
|
|
5
|
+
"description": "基于 Redis 的声明式缓存",
|
|
6
|
+
"category": "backend",
|
|
7
|
+
"packages": {
|
|
8
|
+
"dependencies": {
|
|
9
|
+
"@svton/nestjs-cache": "latest",
|
|
10
|
+
"@svton/nestjs-redis": "latest"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"envVars": [
|
|
14
|
+
{ "key": "REDIS_HOST", "default": "localhost", "description": "Redis 主机地址" },
|
|
15
|
+
{ "key": "REDIS_PORT", "default": "6379", "description": "Redis 端口" },
|
|
16
|
+
{ "key": "REDIS_PASSWORD", "default": "", "description": "Redis 密码(可选)" }
|
|
17
|
+
],
|
|
18
|
+
"configFiles": [
|
|
19
|
+
{
|
|
20
|
+
"path": "src/config/cache.config.ts",
|
|
21
|
+
"template": "configs/cache.config.ts"
|
|
22
|
+
}
|
|
23
|
+
],
|
|
24
|
+
"moduleImports": [
|
|
25
|
+
{
|
|
26
|
+
"from": "@svton/nestjs-cache",
|
|
27
|
+
"import": "CacheModule"
|
|
28
|
+
}
|
|
29
|
+
],
|
|
30
|
+
"moduleRegistration": {
|
|
31
|
+
"type": "forRootAsync",
|
|
32
|
+
"module": "CacheModule",
|
|
33
|
+
"config": "useCacheConfig(configService)"
|
|
34
|
+
},
|
|
35
|
+
"exampleFiles": {
|
|
36
|
+
"source": "examples/cache",
|
|
37
|
+
"target": "src/examples/cache",
|
|
38
|
+
"description": "缓存装饰器使用示例"
|
|
39
|
+
},
|
|
40
|
+
"skillFile": {
|
|
41
|
+
"template": "skills/cache.skill.md",
|
|
42
|
+
"target": ".kiro/skills/cache.md"
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"queue": {
|
|
46
|
+
"name": "消息队列",
|
|
47
|
+
"description": "基于 BullMQ 的异步任务处理",
|
|
48
|
+
"category": "backend",
|
|
49
|
+
"packages": {
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"@svton/nestjs-queue": "latest",
|
|
52
|
+
"@svton/nestjs-redis": "latest"
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
"envVars": [
|
|
56
|
+
{ "key": "REDIS_HOST", "default": "localhost", "description": "Redis 主机地址" },
|
|
57
|
+
{ "key": "REDIS_PORT", "default": "6379", "description": "Redis 端口" }
|
|
58
|
+
],
|
|
59
|
+
"configFiles": [
|
|
60
|
+
{
|
|
61
|
+
"path": "src/config/queue.config.ts",
|
|
62
|
+
"template": "configs/queue.config.ts"
|
|
63
|
+
}
|
|
64
|
+
],
|
|
65
|
+
"moduleImports": [
|
|
66
|
+
{
|
|
67
|
+
"from": "@svton/nestjs-queue",
|
|
68
|
+
"import": "QueueModule"
|
|
69
|
+
}
|
|
70
|
+
],
|
|
71
|
+
"moduleRegistration": {
|
|
72
|
+
"type": "forRootAsync",
|
|
73
|
+
"module": "QueueModule",
|
|
74
|
+
"config": "useQueueConfig(configService)"
|
|
75
|
+
},
|
|
76
|
+
"exampleFiles": {
|
|
77
|
+
"source": "examples/queue",
|
|
78
|
+
"target": "src/examples/queue",
|
|
79
|
+
"description": "队列任务处理示例"
|
|
80
|
+
},
|
|
81
|
+
"skillFile": {
|
|
82
|
+
"template": "skills/queue.skill.md",
|
|
83
|
+
"target": ".kiro/skills/queue.md"
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
"payment": {
|
|
87
|
+
"name": "支付",
|
|
88
|
+
"description": "微信支付 + 支付宝",
|
|
89
|
+
"category": "backend",
|
|
90
|
+
"packages": {
|
|
91
|
+
"dependencies": {
|
|
92
|
+
"@svton/nestjs-payment": "latest"
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
"envVars": [
|
|
96
|
+
{ "key": "WECHAT_MCH_ID", "default": "", "description": "微信商户号" },
|
|
97
|
+
{ "key": "WECHAT_PRIVATE_KEY", "default": "", "description": "微信商户 API 私钥路径" },
|
|
98
|
+
{ "key": "WECHAT_SERIAL_NO", "default": "", "description": "微信商户 API 证书序列号" },
|
|
99
|
+
{ "key": "WECHAT_API_V3_KEY", "default": "", "description": "微信 APIv3 密钥" },
|
|
100
|
+
{ "key": "WECHAT_APP_ID", "default": "", "description": "微信关联的 AppID" },
|
|
101
|
+
{ "key": "ALIPAY_APP_ID", "default": "", "description": "支付宝应用 ID" },
|
|
102
|
+
{ "key": "ALIPAY_PRIVATE_KEY", "default": "", "description": "支付宝应用私钥路径" },
|
|
103
|
+
{ "key": "ALIPAY_PUBLIC_KEY", "default": "", "description": "支付宝公钥路径" }
|
|
104
|
+
],
|
|
105
|
+
"configFiles": [
|
|
106
|
+
{
|
|
107
|
+
"path": "src/config/payment.config.ts",
|
|
108
|
+
"template": "configs/payment.config.ts"
|
|
109
|
+
}
|
|
110
|
+
],
|
|
111
|
+
"moduleImports": [
|
|
112
|
+
{
|
|
113
|
+
"from": "@svton/nestjs-payment",
|
|
114
|
+
"import": "PaymentModule"
|
|
115
|
+
}
|
|
116
|
+
],
|
|
117
|
+
"moduleRegistration": {
|
|
118
|
+
"type": "forRootAsync",
|
|
119
|
+
"module": "PaymentModule",
|
|
120
|
+
"config": "usePaymentConfig(configService)"
|
|
121
|
+
},
|
|
122
|
+
"exampleFiles": {
|
|
123
|
+
"source": "examples/payment",
|
|
124
|
+
"target": "src/examples/payment",
|
|
125
|
+
"description": "支付功能示例(微信/支付宝)"
|
|
126
|
+
},
|
|
127
|
+
"skillFile": {
|
|
128
|
+
"template": "skills/payment.skill.md",
|
|
129
|
+
"target": ".kiro/skills/payment.md"
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
"oauth": {
|
|
133
|
+
"name": "OAuth 登录",
|
|
134
|
+
"description": "微信登录(开放平台/公众号/小程序)",
|
|
135
|
+
"category": "backend",
|
|
136
|
+
"packages": {
|
|
137
|
+
"dependencies": {
|
|
138
|
+
"@svton/nestjs-oauth": "latest"
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
"envVars": [
|
|
142
|
+
{ "key": "WECHAT_OPEN_APP_ID", "default": "", "description": "微信开放平台 AppID" },
|
|
143
|
+
{ "key": "WECHAT_OPEN_APP_SECRET", "default": "", "description": "微信开放平台 AppSecret" },
|
|
144
|
+
{ "key": "WECHAT_MINI_APP_ID", "default": "", "description": "微信小程序 AppID" },
|
|
145
|
+
{ "key": "WECHAT_MINI_APP_SECRET", "default": "", "description": "微信小程序 AppSecret" }
|
|
146
|
+
],
|
|
147
|
+
"configFiles": [
|
|
148
|
+
{
|
|
149
|
+
"path": "src/config/oauth.config.ts",
|
|
150
|
+
"template": "configs/oauth.config.ts"
|
|
151
|
+
}
|
|
152
|
+
],
|
|
153
|
+
"moduleImports": [
|
|
154
|
+
{
|
|
155
|
+
"from": "@svton/nestjs-oauth",
|
|
156
|
+
"import": "OAuthModule"
|
|
157
|
+
}
|
|
158
|
+
],
|
|
159
|
+
"moduleRegistration": {
|
|
160
|
+
"type": "forRootAsync",
|
|
161
|
+
"module": "OAuthModule",
|
|
162
|
+
"config": "useOAuthConfig(configService)"
|
|
163
|
+
},
|
|
164
|
+
"exampleFiles": {
|
|
165
|
+
"source": "examples/oauth",
|
|
166
|
+
"target": "src/examples/oauth",
|
|
167
|
+
"description": "OAuth 登录示例"
|
|
168
|
+
},
|
|
169
|
+
"skillFile": {
|
|
170
|
+
"template": "skills/oauth.skill.md",
|
|
171
|
+
"target": ".kiro/skills/oauth.md"
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
"sms": {
|
|
175
|
+
"name": "短信",
|
|
176
|
+
"description": "阿里云/腾讯云短信发送",
|
|
177
|
+
"category": "backend",
|
|
178
|
+
"packages": {
|
|
179
|
+
"dependencies": {
|
|
180
|
+
"@svton/nestjs-sms": "latest"
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
"envVars": [
|
|
184
|
+
{ "key": "SMS_PROVIDER", "default": "aliyun", "description": "短信服务商 (aliyun/tencent)" },
|
|
185
|
+
{ "key": "SMS_ACCESS_KEY_ID", "default": "", "description": "AccessKey ID" },
|
|
186
|
+
{ "key": "SMS_ACCESS_KEY_SECRET", "default": "", "description": "AccessKey Secret" },
|
|
187
|
+
{ "key": "SMS_SIGN_NAME", "default": "", "description": "短信签名" }
|
|
188
|
+
],
|
|
189
|
+
"configFiles": [
|
|
190
|
+
{
|
|
191
|
+
"path": "src/config/sms.config.ts",
|
|
192
|
+
"template": "configs/sms.config.ts"
|
|
193
|
+
}
|
|
194
|
+
],
|
|
195
|
+
"moduleImports": [
|
|
196
|
+
{
|
|
197
|
+
"from": "@svton/nestjs-sms",
|
|
198
|
+
"import": "SmsModule"
|
|
199
|
+
}
|
|
200
|
+
],
|
|
201
|
+
"moduleRegistration": {
|
|
202
|
+
"type": "forRootAsync",
|
|
203
|
+
"module": "SmsModule",
|
|
204
|
+
"config": "useSmsConfig(configService)"
|
|
205
|
+
},
|
|
206
|
+
"exampleFiles": {
|
|
207
|
+
"source": "examples/sms",
|
|
208
|
+
"target": "src/examples/sms",
|
|
209
|
+
"description": "短信发送示例"
|
|
210
|
+
},
|
|
211
|
+
"skillFile": {
|
|
212
|
+
"template": "skills/sms.skill.md",
|
|
213
|
+
"target": ".kiro/skills/sms.md"
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
"storage": {
|
|
217
|
+
"name": "对象存储",
|
|
218
|
+
"description": "七牛云/阿里云 OSS",
|
|
219
|
+
"category": "backend",
|
|
220
|
+
"packages": {
|
|
221
|
+
"dependencies": {
|
|
222
|
+
"@svton/nestjs-object-storage": "latest",
|
|
223
|
+
"@svton/nestjs-object-storage-qiniu-kodo": "latest"
|
|
224
|
+
}
|
|
225
|
+
},
|
|
226
|
+
"envVars": [
|
|
227
|
+
{ "key": "STORAGE_PROVIDER", "default": "qiniu", "description": "存储服务商 (qiniu/aliyun)" },
|
|
228
|
+
{ "key": "QINIU_ACCESS_KEY", "default": "", "description": "七牛云 AccessKey" },
|
|
229
|
+
{ "key": "QINIU_SECRET_KEY", "default": "", "description": "七牛云 SecretKey" },
|
|
230
|
+
{ "key": "QINIU_BUCKET", "default": "", "description": "七牛云存储空间名称" },
|
|
231
|
+
{ "key": "QINIU_DOMAIN", "default": "", "description": "七牛云 CDN 域名" }
|
|
232
|
+
],
|
|
233
|
+
"configFiles": [
|
|
234
|
+
{
|
|
235
|
+
"path": "src/config/storage.config.ts",
|
|
236
|
+
"template": "configs/storage.config.ts"
|
|
237
|
+
}
|
|
238
|
+
],
|
|
239
|
+
"moduleImports": [
|
|
240
|
+
{
|
|
241
|
+
"from": "@svton/nestjs-object-storage",
|
|
242
|
+
"import": "ObjectStorageModule"
|
|
243
|
+
}
|
|
244
|
+
],
|
|
245
|
+
"moduleRegistration": {
|
|
246
|
+
"type": "forRootAsync",
|
|
247
|
+
"module": "ObjectStorageModule",
|
|
248
|
+
"config": "useStorageConfig(configService)"
|
|
249
|
+
},
|
|
250
|
+
"exampleFiles": {
|
|
251
|
+
"source": "examples/storage",
|
|
252
|
+
"target": "src/examples/storage",
|
|
253
|
+
"description": "文件上传示例"
|
|
254
|
+
},
|
|
255
|
+
"skillFile": {
|
|
256
|
+
"template": "skills/storage.skill.md",
|
|
257
|
+
"target": ".kiro/skills/storage.md"
|
|
258
|
+
}
|
|
259
|
+
},
|
|
260
|
+
"rateLimit": {
|
|
261
|
+
"name": "限流",
|
|
262
|
+
"description": "接口访问频率限制",
|
|
263
|
+
"category": "backend",
|
|
264
|
+
"packages": {
|
|
265
|
+
"dependencies": {
|
|
266
|
+
"@svton/nestjs-rate-limit": "latest",
|
|
267
|
+
"@svton/nestjs-redis": "latest"
|
|
268
|
+
}
|
|
269
|
+
},
|
|
270
|
+
"envVars": [
|
|
271
|
+
{ "key": "REDIS_HOST", "default": "localhost", "description": "Redis 主机地址" },
|
|
272
|
+
{ "key": "REDIS_PORT", "default": "6379", "description": "Redis 端口" }
|
|
273
|
+
],
|
|
274
|
+
"configFiles": [
|
|
275
|
+
{
|
|
276
|
+
"path": "src/config/rate-limit.config.ts",
|
|
277
|
+
"template": "configs/rate-limit.config.ts"
|
|
278
|
+
}
|
|
279
|
+
],
|
|
280
|
+
"moduleImports": [
|
|
281
|
+
{
|
|
282
|
+
"from": "@svton/nestjs-rate-limit",
|
|
283
|
+
"import": "RateLimitModule"
|
|
284
|
+
}
|
|
285
|
+
],
|
|
286
|
+
"moduleRegistration": {
|
|
287
|
+
"type": "forRootAsync",
|
|
288
|
+
"module": "RateLimitModule",
|
|
289
|
+
"config": "useRateLimitConfig(configService)"
|
|
290
|
+
},
|
|
291
|
+
"exampleFiles": {
|
|
292
|
+
"source": "examples/rate-limit",
|
|
293
|
+
"target": "src/examples/rate-limit",
|
|
294
|
+
"description": "限流使用示例"
|
|
295
|
+
},
|
|
296
|
+
"skillFile": {
|
|
297
|
+
"template": "skills/rate-limit.skill.md",
|
|
298
|
+
"target": ".kiro/skills/rate-limit.md"
|
|
299
|
+
}
|
|
300
|
+
},
|
|
301
|
+
"authz": {
|
|
302
|
+
"name": "权限控制",
|
|
303
|
+
"description": "RBAC 权限管理",
|
|
304
|
+
"category": "backend",
|
|
305
|
+
"packages": {
|
|
306
|
+
"dependencies": {
|
|
307
|
+
"@svton/nestjs-authz": "latest"
|
|
308
|
+
}
|
|
309
|
+
},
|
|
310
|
+
"envVars": [],
|
|
311
|
+
"configFiles": [
|
|
312
|
+
{
|
|
313
|
+
"path": "src/config/authz.config.ts",
|
|
314
|
+
"template": "configs/authz.config.ts"
|
|
315
|
+
}
|
|
316
|
+
],
|
|
317
|
+
"moduleImports": [
|
|
318
|
+
{
|
|
319
|
+
"from": "@svton/nestjs-authz",
|
|
320
|
+
"import": "AuthzModule"
|
|
321
|
+
}
|
|
322
|
+
],
|
|
323
|
+
"moduleRegistration": {
|
|
324
|
+
"type": "forRootAsync",
|
|
325
|
+
"module": "AuthzModule",
|
|
326
|
+
"config": "useAuthzConfig(configService)"
|
|
327
|
+
},
|
|
328
|
+
"exampleFiles": {
|
|
329
|
+
"source": "examples/authz",
|
|
330
|
+
"target": "src/examples/authz",
|
|
331
|
+
"description": "权限控制示例"
|
|
332
|
+
},
|
|
333
|
+
"skillFile": {
|
|
334
|
+
"template": "skills/authz.skill.md",
|
|
335
|
+
"target": ".kiro/skills/authz.md"
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@svton/cli",
|
|
3
|
-
"version": "1.1
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "Svton CLI - Create full-stack applications with NestJS, Next.js, and Taro",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"files": [
|
|
31
31
|
"dist",
|
|
32
32
|
"bin",
|
|
33
|
+
"features.json",
|
|
33
34
|
"README.md",
|
|
34
35
|
"LICENSE"
|
|
35
36
|
],
|