@gravito/scaffold 1.0.0 → 1.1.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.cjs CHANGED
@@ -392,6 +392,7 @@ var BaseGenerator = class {
392
392
  "ARCHITECTURE.md",
393
393
  this.generateArchitectureDoc(context)
394
394
  );
395
+ await this.generateCheckScripts(context);
395
396
  }
396
397
  /**
397
398
  * Apply profile-specific overlays
@@ -479,6 +480,10 @@ var BaseGenerator = class {
479
480
  start: "bun run dist/bootstrap.js",
480
481
  test: "bun test",
481
482
  typecheck: "tsc --noEmit",
483
+ check: "bun run typecheck && bun run test",
484
+ "check:deps": "bun run scripts/check-dependencies.ts",
485
+ validate: "bun run check && bun run check:deps",
486
+ precommit: "bun run validate",
482
487
  "docker:build": `docker build -t ${context.nameKebabCase} .`,
483
488
  "docker:run": `docker run -it -p 3000:3000 ${context.nameKebabCase}`
484
489
  },
@@ -731,6 +736,430 @@ coverage/
731
736
  };
732
737
  return JSON.stringify(config, null, 2);
733
738
  }
739
+ /**
740
+ * Generate check scripts for project validation.
741
+ */
742
+ async generateCheckScripts(context) {
743
+ const scriptsDir = import_node_path2.default.resolve(context.targetDir, "scripts");
744
+ await import_promises2.default.mkdir(scriptsDir, { recursive: true });
745
+ await this.writeFile(
746
+ scriptsDir,
747
+ "check-dependencies.ts",
748
+ this.generateCheckDependenciesScript()
749
+ );
750
+ await this.writeFile(scriptsDir, "check.sh", this.generateCheckShellScript());
751
+ await this.writeFile(scriptsDir, "pre-commit.sh", this.generatePreCommitScript());
752
+ await this.writeFile(context.targetDir, "CHECK_SYSTEM.md", this.generateCheckSystemDoc(context));
753
+ }
754
+ /**
755
+ * Generate check-dependencies.ts script content.
756
+ */
757
+ generateCheckDependenciesScript() {
758
+ return `/**
759
+ * \u76F8\u4F9D\u5957\u4EF6\u7248\u672C\u6AA2\u67E5\u8173\u672C
760
+ *
761
+ * \u6AA2\u67E5 package.json \u4E2D\u7684\u5957\u4EF6\u662F\u5426\u70BA\u6700\u65B0\u7A69\u5B9A\u7248\u672C
762
+ * \u4E26\u63D0\u4F9B\u66F4\u65B0\u5EFA\u8B70
763
+ */
764
+
765
+ import { readFileSync } from 'fs'
766
+ import { join } from 'path'
767
+
768
+ interface PackageJson {
769
+ dependencies?: Record<string, string>
770
+ devDependencies?: Record<string, string>
771
+ }
772
+
773
+ interface PackageInfo {
774
+ name: string
775
+ current: string
776
+ latest: string
777
+ outdated: boolean
778
+ }
779
+
780
+ const colors = {
781
+ reset: '\\x1b[0m',
782
+ green: '\\x1b[32m',
783
+ yellow: '\\x1b[33m',
784
+ red: '\\x1b[31m',
785
+ blue: '\\x1b[36m',
786
+ }
787
+
788
+ function log(message: string, color: keyof typeof colors = 'reset') {
789
+ console.log(\`\${colors[color]}\${message}\${colors.reset}\`)
790
+ }
791
+
792
+ async function getLatestVersion(packageName: string): Promise<string | null> {
793
+ try {
794
+ const response = await fetch(\`https://registry.npmjs.org/\${packageName}/latest\`)
795
+ if (!response.ok) return null
796
+ const data = await response.json()
797
+ return data.version
798
+ } catch {
799
+ return null
800
+ }
801
+ }
802
+
803
+ function parseVersion(version: string): string {
804
+ // \u79FB\u9664 ^, ~, >= \u7B49\u524D\u7DB4
805
+ return version.replace(/^[\\^~>=<]/, '')
806
+ }
807
+
808
+ async function checkPackage(
809
+ name: string,
810
+ currentVersion: string
811
+ ): Promise<PackageInfo | null> {
812
+ // \u8DF3\u904E\u672C\u5730\u9023\u7D50\u7684\u5957\u4EF6
813
+ if (currentVersion.startsWith('link:') || currentVersion.startsWith('workspace:')) {
814
+ return null
815
+ }
816
+
817
+ const current = parseVersion(currentVersion)
818
+ const latest = await getLatestVersion(name)
819
+
820
+ if (!latest) {
821
+ return null
822
+ }
823
+
824
+ return {
825
+ name,
826
+ current,
827
+ latest,
828
+ outdated: current !== latest,
829
+ }
830
+ }
831
+
832
+ async function main() {
833
+ log('\\n=== \u76F8\u4F9D\u5957\u4EF6\u7248\u672C\u6AA2\u67E5 ===\\n', 'blue')
834
+
835
+ const packageJsonPath = join(process.cwd(), 'package.json')
836
+ const packageJson: PackageJson = JSON.parse(
837
+ readFileSync(packageJsonPath, 'utf-8')
838
+ )
839
+
840
+ const allDependencies = {
841
+ ...packageJson.dependencies,
842
+ ...packageJson.devDependencies,
843
+ }
844
+
845
+ log(\`\u6AA2\u67E5 \${Object.keys(allDependencies).length} \u500B\u5957\u4EF6...\\n\`, 'yellow')
846
+
847
+ const results: PackageInfo[] = []
848
+ const outdated: PackageInfo[] = []
849
+ const upToDate: PackageInfo[] = []
850
+
851
+ // \u6AA2\u67E5\u6240\u6709\u5957\u4EF6
852
+ for (const [name, version] of Object.entries(allDependencies)) {
853
+ const info = await checkPackage(name, version)
854
+ if (info) {
855
+ results.push(info)
856
+ if (info.outdated) {
857
+ outdated.push(info)
858
+ } else {
859
+ upToDate.push(info)
860
+ }
861
+ }
862
+ }
863
+
864
+ // \u986F\u793A\u7D50\u679C
865
+ if (upToDate.length > 0) {
866
+ log(\`\\n\u2713 \u5DF2\u662F\u6700\u65B0\u7248\u672C (\${upToDate.length}):\`, 'green')
867
+ upToDate.forEach((pkg) => {
868
+ log(\` \${pkg.name}: \${pkg.current}\`, 'green')
869
+ })
870
+ }
871
+
872
+ if (outdated.length > 0) {
873
+ log(\`\\n\u26A0 \u9700\u8981\u66F4\u65B0 (\${outdated.length}):\`, 'yellow')
874
+ outdated.forEach((pkg) => {
875
+ log(\` \${pkg.name}: \${pkg.current} \u2192 \${pkg.latest}\`, 'yellow')
876
+ })
877
+ }
878
+
879
+ // \u7E3D\u7D50
880
+ log('\\n=== \u6AA2\u67E5\u7D50\u679C ===', 'blue')
881
+ log(\`\u7E3D\u8A08: \${results.length} \u500B\u5957\u4EF6\`, 'blue')
882
+ log(\`\u6700\u65B0: \${upToDate.length} \u500B\`, 'green')
883
+ log(\`\u9700\u66F4\u65B0: \${outdated.length} \u500B\`, outdated.length > 0 ? 'yellow' : 'green')
884
+
885
+ // \u5982\u679C\u6709\u9700\u8981\u66F4\u65B0\u7684\u5957\u4EF6\uFF0C\u8FD4\u56DE\u975E\u96F6\u9000\u51FA\u78BC
886
+ if (outdated.length > 0) {
887
+ log('\\n\u5EFA\u8B70\u57F7\u884C\u4EE5\u4E0B\u547D\u4EE4\u66F4\u65B0\u5957\u4EF6\uFF1A', 'yellow')
888
+ log(' bun update', 'yellow')
889
+ process.exit(1)
890
+ } else {
891
+ log('\\n\u2713 \u6240\u6709\u5957\u4EF6\u90FD\u662F\u6700\u65B0\u7248\u672C\uFF01', 'green')
892
+ process.exit(0)
893
+ }
894
+ }
895
+
896
+ main().catch((error) => {
897
+ log(\`\\n\u932F\u8AA4: \${error.message}\`, 'red')
898
+ process.exit(1)
899
+ })
900
+ `;
901
+ }
902
+ /**
903
+ * Generate check.sh script content.
904
+ */
905
+ generateCheckShellScript() {
906
+ return `#!/bin/bash
907
+
908
+ # \u5C08\u6848\u6AA2\u67E5\u8173\u672C
909
+ # \u57F7\u884C\u6240\u6709\u5FC5\u8981\u7684\u6AA2\u67E5\uFF1A\u985E\u578B\u6AA2\u67E5\u3001\u6E2C\u8A66\u3001\u4F9D\u8CF4\u6AA2\u67E5\u7B49
910
+
911
+ set -e
912
+
913
+ # \u984F\u8272\u5B9A\u7FA9
914
+ GREEN='\\033[0;32m'
915
+ YELLOW='\\033[1;33m'
916
+ RED='\\033[0;31m'
917
+ BLUE='\\033[0;34m'
918
+ NC='\\033[0m' # No Color
919
+
920
+ echo -e "\${BLUE}=== \u5C08\u6848\u6AA2\u67E5 ===\${NC}\\n"
921
+
922
+ # \u6AA2\u67E5\u662F\u5426\u5728\u6B63\u78BA\u7684\u76EE\u9304
923
+ if [ ! -f "package.json" ]; then
924
+ echo -e "\${RED}\u932F\u8AA4: \u8ACB\u5728\u5C08\u6848\u6839\u76EE\u9304\u57F7\u884C\u6B64\u8173\u672C\${NC}"
925
+ exit 1
926
+ fi
927
+
928
+ # \u6AA2\u67E5 Bun \u662F\u5426\u5B89\u88DD
929
+ if ! command -v bun &> /dev/null; then
930
+ echo -e "\${RED}\u932F\u8AA4: \u672A\u627E\u5230 bun\uFF0C\u8ACB\u5148\u5B89\u88DD Bun\${NC}"
931
+ exit 1
932
+ fi
933
+
934
+ # 1. \u985E\u578B\u6AA2\u67E5
935
+ echo -e "\${YELLOW}[1/3] \u57F7\u884C\u985E\u578B\u6AA2\u67E5...\${NC}"
936
+ if bun run typecheck; then
937
+ echo -e "\${GREEN}\u2713 \u985E\u578B\u6AA2\u67E5\u901A\u904E\${NC}\\n"
938
+ else
939
+ echo -e "\${RED}\u2717 \u985E\u578B\u6AA2\u67E5\u5931\u6557\${NC}"
940
+ exit 1
941
+ fi
942
+
943
+ # 2. \u57F7\u884C\u6E2C\u8A66
944
+ echo -e "\${YELLOW}[2/3] \u57F7\u884C\u6E2C\u8A66...\${NC}"
945
+ if bun test; then
946
+ echo -e "\${GREEN}\u2713 \u6E2C\u8A66\u901A\u904E\${NC}\\n"
947
+ else
948
+ echo -e "\${RED}\u2717 \u6E2C\u8A66\u5931\u6557\${NC}"
949
+ exit 1
950
+ fi
951
+
952
+ # 3. \u6AA2\u67E5\u4F9D\u8CF4\u7248\u672C\uFF08\u53EF\u9078\uFF0C\u56E0\u70BA\u9700\u8981\u7DB2\u8DEF\u9023\u7DDA\uFF09
953
+ echo -e "\${YELLOW}[3/3] \u6AA2\u67E5\u4F9D\u8CF4\u7248\u672C...\${NC}"
954
+ if bun run check:deps; then
955
+ echo -e "\${GREEN}\u2713 \u4F9D\u8CF4\u6AA2\u67E5\u5B8C\u6210\${NC}\\n"
956
+ else
957
+ echo -e "\${YELLOW}\u26A0 \u4F9D\u8CF4\u6AA2\u67E5\u6709\u8B66\u544A\uFF08\u67D0\u4E9B\u5957\u4EF6\u53EF\u80FD\u9700\u8981\u66F4\u65B0\uFF09\${NC}\\n"
958
+ fi
959
+
960
+ echo -e "\${GREEN}=== \u6240\u6709\u6AA2\u67E5\u5B8C\u6210 ===\${NC}"
961
+ `;
962
+ }
963
+ /**
964
+ * Generate pre-commit.sh script content.
965
+ */
966
+ generatePreCommitScript() {
967
+ return `#!/bin/bash
968
+
969
+ # Pre-commit Hook
970
+ # \u5728 git commit \u524D\u81EA\u52D5\u57F7\u884C\u6AA2\u67E5
971
+ #
972
+ # \u5B89\u88DD\u65B9\u5F0F\uFF1A
973
+ # ln -s ../../scripts/pre-commit.sh .git/hooks/pre-commit
974
+ # \u6216
975
+ # cp scripts/pre-commit.sh .git/hooks/pre-commit
976
+ # chmod +x .git/hooks/pre-commit
977
+
978
+ set -e
979
+
980
+ # \u984F\u8272\u5B9A\u7FA9
981
+ GREEN='\\033[0;32m'
982
+ YELLOW='\\033[1;33m'
983
+ RED='\\033[0;31m'
984
+ BLUE='\\033[0;34m'
985
+ NC='\\033[0m' # No Color
986
+
987
+ echo -e "\${BLUE}=== Pre-commit \u6AA2\u67E5 ===\${NC}\\n"
988
+
989
+ # \u5207\u63DB\u5230\u5C08\u6848\u6839\u76EE\u9304
990
+ cd "$(git rev-parse --show-toplevel)"
991
+
992
+ # \u6AA2\u67E5\u662F\u5426\u5728\u6B63\u78BA\u7684\u76EE\u9304
993
+ if [ ! -f "package.json" ]; then
994
+ echo -e "\${RED}\u932F\u8AA4: \u627E\u4E0D\u5230 package.json\${NC}"
995
+ exit 1
996
+ fi
997
+
998
+ # \u6AA2\u67E5 Bun \u662F\u5426\u5B89\u88DD
999
+ if ! command -v bun &> /dev/null; then
1000
+ echo -e "\${RED}\u932F\u8AA4: \u672A\u627E\u5230 bun\uFF0C\u8ACB\u5148\u5B89\u88DD Bun\${NC}"
1001
+ exit 1
1002
+ fi
1003
+
1004
+ # 1. \u985E\u578B\u6AA2\u67E5\uFF08\u5FEB\u901F\u6AA2\u67E5\uFF09
1005
+ echo -e "\${YELLOW}[1/2] \u57F7\u884C\u985E\u578B\u6AA2\u67E5...\${NC}"
1006
+ if bun run typecheck; then
1007
+ echo -e "\${GREEN}\u2713 \u985E\u578B\u6AA2\u67E5\u901A\u904E\${NC}\\n"
1008
+ else
1009
+ echo -e "\${RED}\u2717 \u985E\u578B\u6AA2\u67E5\u5931\u6557\${NC}"
1010
+ echo -e "\${YELLOW}\u63D0\u793A: \u8ACB\u4FEE\u6B63\u985E\u578B\u932F\u8AA4\u5F8C\u518D\u63D0\u4EA4\${NC}"
1011
+ exit 1
1012
+ fi
1013
+
1014
+ # 2. \u57F7\u884C\u6E2C\u8A66\uFF08\u53EF\u9078\uFF0C\u5982\u679C\u6E2C\u8A66\u6642\u9593\u8F03\u9577\u53EF\u4EE5\u8A3B\u89E3\u6389\uFF09
1015
+ echo -e "\${YELLOW}[2/2] \u57F7\u884C\u6E2C\u8A66...\${NC}"
1016
+ if bun test; then
1017
+ echo -e "\${GREEN}\u2713 \u6E2C\u8A66\u901A\u904E\${NC}\\n"
1018
+ else
1019
+ echo -e "\${RED}\u2717 \u6E2C\u8A66\u5931\u6557\${NC}"
1020
+ echo -e "\${YELLOW}\u63D0\u793A: \u8ACB\u4FEE\u6B63\u6E2C\u8A66\u932F\u8AA4\u5F8C\u518D\u63D0\u4EA4\${NC}"
1021
+ exit 1
1022
+ fi
1023
+
1024
+ echo -e "\${GREEN}=== Pre-commit \u6AA2\u67E5\u901A\u904E ===\${NC}\\n"
1025
+ `;
1026
+ }
1027
+ /**
1028
+ * Generate CHECK_SYSTEM.md documentation.
1029
+ */
1030
+ generateCheckSystemDoc(context) {
1031
+ return `# \u5C08\u6848\u6AA2\u67E5\u7CFB\u7D71
1032
+
1033
+ \u672C\u5C08\u6848\u5DF2\u5EFA\u7ACB\u5B8C\u6574\u7684\u672C\u5730\u6AA2\u67E5\u6A5F\u5236\uFF0C\u7121\u9700\u4F9D\u8CF4 GitHub CI\u3002
1034
+
1035
+ ## \u5FEB\u901F\u958B\u59CB
1036
+
1037
+ ### \u57F7\u884C\u5B8C\u6574\u6AA2\u67E5
1038
+ \`\`\`bash
1039
+ bun run validate
1040
+ \`\`\`
1041
+
1042
+ ### \u57F7\u884C\u55AE\u9805\u6AA2\u67E5
1043
+ \`\`\`bash
1044
+ # \u985E\u578B\u6AA2\u67E5
1045
+ bun run typecheck
1046
+
1047
+ # \u6E2C\u8A66
1048
+ bun run test
1049
+
1050
+ # \u4F9D\u8CF4\u7248\u672C\u6AA2\u67E5
1051
+ bun run check:deps
1052
+ \`\`\`
1053
+
1054
+ ## \u53EF\u7528\u547D\u4EE4
1055
+
1056
+ ### Package.json \u8173\u672C
1057
+
1058
+ | \u547D\u4EE4 | \u8AAA\u660E |
1059
+ |------|------|
1060
+ | \`bun run typecheck\` | TypeScript \u985E\u578B\u6AA2\u67E5 |
1061
+ | \`bun run test\` | \u57F7\u884C\u6240\u6709\u6E2C\u8A66 |
1062
+ | \`bun run check\` | \u985E\u578B\u6AA2\u67E5 + \u6E2C\u8A66 |
1063
+ | \`bun run check:deps\` | \u6AA2\u67E5\u4F9D\u8CF4\u7248\u672C |
1064
+ | \`bun run validate\` | \u5B8C\u6574\u9A57\u8B49\uFF08\u985E\u578B + \u6E2C\u8A66 + \u4F9D\u8CF4\uFF09 |
1065
+ | \`bun run precommit\` | \u7B49\u540C\u65BC \`validate\` |
1066
+
1067
+ ### Shell \u8173\u672C
1068
+
1069
+ | \u8173\u672C | \u8AAA\u660E |
1070
+ |------|------|
1071
+ | \`./scripts/check.sh\` | \u5B8C\u6574\u5C08\u6848\u6AA2\u67E5\uFF08Shell \u7248\u672C\uFF09 |
1072
+ | \`./scripts/pre-commit.sh\` | Pre-commit hook \u8173\u672C |
1073
+
1074
+ ## Pre-commit Hook\uFF08\u63A8\u85A6\uFF09
1075
+
1076
+ \u5B89\u88DD pre-commit hook \u5F8C\uFF0C\u6BCF\u6B21 \`git commit\` \u524D\u6703\u81EA\u52D5\u57F7\u884C\u6AA2\u67E5\uFF1A
1077
+
1078
+ \`\`\`bash
1079
+ # \u5B89\u88DD pre-commit hook
1080
+ ln -s ../../scripts/pre-commit.sh .git/hooks/pre-commit
1081
+
1082
+ # \u6216\u4F7F\u7528\u8907\u88FD\u65B9\u5F0F
1083
+ cp scripts/pre-commit.sh .git/hooks/pre-commit
1084
+ chmod +x .git/hooks/pre-commit
1085
+ \`\`\`
1086
+
1087
+ **\u529F\u80FD\uFF1A**
1088
+ - \u2705 \u81EA\u52D5\u57F7\u884C\u985E\u578B\u6AA2\u67E5
1089
+ - \u2705 \u81EA\u52D5\u57F7\u884C\u6E2C\u8A66
1090
+ - \u274C \u6AA2\u67E5\u5931\u6557\u6642\u963B\u6B62\u63D0\u4EA4
1091
+
1092
+ **\u8DF3\u904E\u6AA2\u67E5\uFF08\u4E0D\u63A8\u85A6\uFF09\uFF1A**
1093
+ \`\`\`bash
1094
+ git commit --no-verify -m "\u7DCA\u6025\u4FEE\u5FA9"
1095
+ \`\`\`
1096
+
1097
+ ## \u6AA2\u67E5\u9805\u76EE
1098
+
1099
+ ### 1. \u985E\u578B\u6AA2\u67E5
1100
+ - \u4F7F\u7528 \`tsc --noEmit\` \u6AA2\u67E5 TypeScript \u985E\u578B
1101
+ - \u78BA\u4FDD\u6C92\u6709\u985E\u578B\u932F\u8AA4
1102
+
1103
+ ### 2. \u6E2C\u8A66
1104
+ - \u57F7\u884C\u6240\u6709\u55AE\u5143\u6E2C\u8A66\u548C\u6574\u5408\u6E2C\u8A66
1105
+ - \u78BA\u4FDD\u6E2C\u8A66\u901A\u904E
1106
+
1107
+ ### 3. \u4F9D\u8CF4\u6AA2\u67E5\uFF08\u53EF\u9078\uFF09
1108
+ - \u6AA2\u67E5\u5957\u4EF6\u7248\u672C\u662F\u5426\u70BA\u6700\u65B0
1109
+ - \u63D0\u4F9B\u66F4\u65B0\u5EFA\u8B70
1110
+ - \u9700\u8981\u7DB2\u8DEF\u9023\u7DDA
1111
+
1112
+ ## \u5DE5\u4F5C\u6D41\u7A0B\u5EFA\u8B70
1113
+
1114
+ ### \u958B\u767C\u6642
1115
+ 1. \u958B\u767C\u529F\u80FD
1116
+ 2. \u63D0\u4EA4\u524D\u57F7\u884C \`bun run validate\`
1117
+ 3. \u4FEE\u6B63\u554F\u984C
1118
+ 4. \u63D0\u4EA4\u7A0B\u5F0F\u78BC
1119
+
1120
+ ### \u4F7F\u7528 Pre-commit Hook\uFF08\u63A8\u85A6\uFF09
1121
+ 1. \u5B89\u88DD pre-commit hook\uFF08\u53EA\u9700\u4E00\u6B21\uFF09
1122
+ 2. \u6B63\u5E38\u958B\u767C\u548C\u63D0\u4EA4
1123
+ 3. \u6AA2\u67E5\u6703\u81EA\u52D5\u57F7\u884C
1124
+ 4. \u5982\u6709\u554F\u984C\uFF0C\u4FEE\u6B63\u5F8C\u91CD\u65B0\u63D0\u4EA4
1125
+
1126
+ ## \u6A94\u6848\u7D50\u69CB
1127
+
1128
+ \`\`\`
1129
+ ${context.nameKebabCase}/
1130
+ \u251C\u2500\u2500 package.json # \u6AA2\u67E5\u8173\u672C\u5B9A\u7FA9
1131
+ \u251C\u2500\u2500 scripts/
1132
+ \u2502 \u251C\u2500\u2500 check.sh # \u5B8C\u6574\u6AA2\u67E5\u8173\u672C\uFF08Shell\uFF09
1133
+ \u2502 \u251C\u2500\u2500 check-dependencies.ts # \u4F9D\u8CF4\u7248\u672C\u6AA2\u67E5
1134
+ \u2502 \u2514\u2500\u2500 pre-commit.sh # Pre-commit hook
1135
+ \u2514\u2500\u2500 CHECK_SYSTEM.md # \u672C\u6587\u4EF6
1136
+ \`\`\`
1137
+
1138
+ ## \u6CE8\u610F\u4E8B\u9805
1139
+
1140
+ 1. **\u4F9D\u8CF4\u6AA2\u67E5\u9700\u8981\u7DB2\u8DEF\u9023\u7DDA**\uFF1A\`check:deps\` \u9700\u8981\u9023\u63A5\u5230 npm registry
1141
+ 2. **\u6E2C\u8A66\u6642\u9593**\uFF1A\u5982\u679C\u6E2C\u8A66\u6642\u9593\u8F03\u9577\uFF0C\u53EF\u4EE5\u7DE8\u8F2F \`pre-commit.sh\` \u8A3B\u89E3\u6389\u6E2C\u8A66\u90E8\u5206
1142
+ 3. **\u985E\u578B\u932F\u8AA4**\uFF1A\u5C08\u6848\u4E2D\u53EF\u80FD\u9084\u6709\u4E00\u4E9B\u65E2\u6709\u7684\u985E\u578B\u932F\u8AA4\uFF0C\u5EFA\u8B70\u9010\u6B65\u4FEE\u6B63
1143
+
1144
+ ## \u6545\u969C\u6392\u9664
1145
+
1146
+ ### \u6AA2\u67E5\u5931\u6557
1147
+ 1. \u67E5\u770B\u932F\u8AA4\u8A0A\u606F
1148
+ 2. \u4FEE\u6B63\u554F\u984C
1149
+ 3. \u91CD\u65B0\u57F7\u884C\u6AA2\u67E5
1150
+
1151
+ ### \u8DF3\u904E\u6AA2\u67E5
1152
+ \u53EA\u6709\u5728\u7DCA\u6025\u60C5\u6CC1\u4E0B\u624D\u4F7F\u7528\uFF1A
1153
+ \`\`\`bash
1154
+ git commit --no-verify
1155
+ \`\`\`
1156
+
1157
+ ### \u79FB\u9664 Pre-commit Hook
1158
+ \`\`\`bash
1159
+ rm .git/hooks/pre-commit
1160
+ \`\`\`
1161
+ `;
1162
+ }
734
1163
  /**
735
1164
  * Log a message if verbose mode is enabled.
736
1165
  */
@@ -911,6 +1340,11 @@ var CleanArchitectureGenerator = class extends BaseGenerator {
911
1340
  type: "directory",
912
1341
  name: "Providers",
913
1342
  children: [
1343
+ {
1344
+ type: "file",
1345
+ name: "index.ts",
1346
+ content: this.generateProvidersIndex()
1347
+ },
914
1348
  {
915
1349
  type: "file",
916
1350
  name: "AppServiceProvider.ts",
@@ -920,6 +1354,16 @@ var CleanArchitectureGenerator = class extends BaseGenerator {
920
1354
  type: "file",
921
1355
  name: "RepositoryServiceProvider.ts",
922
1356
  content: this.generateRepositoryServiceProvider()
1357
+ },
1358
+ {
1359
+ type: "file",
1360
+ name: "MiddlewareProvider.ts",
1361
+ content: this.generateMiddlewareProvider()
1362
+ },
1363
+ {
1364
+ type: "file",
1365
+ name: "RouteProvider.ts",
1366
+ content: this.generateRouteProvider()
923
1367
  }
924
1368
  ]
925
1369
  }
@@ -1413,6 +1857,65 @@ export class RepositoryServiceProvider extends ServiceProvider {
1413
1857
  container.singleton('mailService', () => new MailService())
1414
1858
  }
1415
1859
  }
1860
+ `;
1861
+ }
1862
+ generateProvidersIndex() {
1863
+ return `/**
1864
+ * Application Service Providers
1865
+ */
1866
+
1867
+ export { AppServiceProvider } from './AppServiceProvider'
1868
+ export { RepositoryServiceProvider } from './RepositoryServiceProvider'
1869
+ export { MiddlewareProvider } from './MiddlewareProvider'
1870
+ export { RouteProvider } from './RouteProvider'
1871
+ `;
1872
+ }
1873
+ generateMiddlewareProvider() {
1874
+ return `/**
1875
+ * Middleware Service Provider
1876
+ */
1877
+
1878
+ import {
1879
+ ServiceProvider,
1880
+ type Container,
1881
+ type PlanetCore,
1882
+ bodySizeLimit,
1883
+ securityHeaders,
1884
+ } from '@gravito/core'
1885
+
1886
+ export class MiddlewareProvider extends ServiceProvider {
1887
+ register(_container: Container): void {}
1888
+
1889
+ boot(core: PlanetCore): void {
1890
+ const isDev = process.env.NODE_ENV !== 'production'
1891
+
1892
+ core.adapter.use('*', securityHeaders({
1893
+ contentSecurityPolicy: isDev ? false : undefined,
1894
+ }))
1895
+
1896
+ core.adapter.use('*', bodySizeLimit(10 * 1024 * 1024))
1897
+
1898
+ core.logger.info('\u{1F6E1}\uFE0F Middleware registered')
1899
+ }
1900
+ }
1901
+ `;
1902
+ }
1903
+ generateRouteProvider() {
1904
+ return `/**
1905
+ * Route Service Provider
1906
+ */
1907
+
1908
+ import { ServiceProvider, type Container, type PlanetCore } from '@gravito/core'
1909
+ import { registerApiRoutes } from '../../Interface/Http/Routes/api'
1910
+
1911
+ export class RouteProvider extends ServiceProvider {
1912
+ register(_container: Container): void {}
1913
+
1914
+ boot(core: PlanetCore): void {
1915
+ registerApiRoutes(core.router)
1916
+ core.logger.info('\u{1F6E4}\uFE0F Routes registered')
1917
+ }
1918
+ }
1416
1919
  `;
1417
1920
  }
1418
1921
  // ─────────────────────────────────────────────────────────────
@@ -1497,64 +2000,55 @@ export class UserPresenter {
1497
2000
  }
1498
2001
  `;
1499
2002
  }
1500
- generateBootstrap(context) {
2003
+ generateBootstrap(_context) {
1501
2004
  return `/**
1502
2005
  * Application Bootstrap
2006
+ *
2007
+ * The entry point for your Clean Architecture application.
2008
+ * Uses the ServiceProvider pattern for modular initialization.
2009
+ *
2010
+ * Lifecycle:
2011
+ * 1. Configure: Load app config and orbits
2012
+ * 2. Boot: Initialize PlanetCore
2013
+ * 3. Register Providers: Bind services to container
2014
+ * 4. Bootstrap: Boot all providers
1503
2015
  */
1504
2016
 
1505
- import { bodySizeLimit, PlanetCore, securityHeaders } from '@gravito/core'
2017
+ import { defineConfig, PlanetCore } from '@gravito/core'
1506
2018
  import { OrbitAtlas } from '@gravito/atlas'
1507
- import databaseConfig from '../config/database'
1508
- import { AppServiceProvider } from './Infrastructure/Providers/AppServiceProvider'
1509
- import { RepositoryServiceProvider } from './Infrastructure/Providers/RepositoryServiceProvider'
1510
- import { registerApiRoutes } from './Interface/Http/Routes/api'
1511
-
1512
- const core = new PlanetCore({
1513
- config: {
1514
- APP_NAME: '${context.name}',
1515
- database: databaseConfig,
1516
- },
1517
- })
1518
-
1519
- const defaultCsp = [
1520
- "default-src 'self'",
1521
- "script-src 'self' 'unsafe-inline'",
1522
- "style-src 'self' 'unsafe-inline'",
1523
- "img-src 'self' data:",
1524
- "object-src 'none'",
1525
- "base-uri 'self'",
1526
- "frame-ancestors 'none'",
1527
- ].join('; ')
1528
- const cspValue = process.env.APP_CSP
1529
- const csp = cspValue === 'false' ? false : (cspValue ?? defaultCsp)
1530
- const hstsMaxAge = Number.parseInt(process.env.APP_HSTS_MAX_AGE ?? '15552000', 10)
1531
- const bodyLimit = Number.parseInt(process.env.APP_BODY_LIMIT ?? '1048576', 10)
1532
- const requireLength = process.env.APP_BODY_REQUIRE_LENGTH === 'true'
1533
-
1534
- core.adapter.use(
1535
- '*',
1536
- securityHeaders({
1537
- contentSecurityPolicy: csp,
1538
- hsts:
1539
- process.env.NODE_ENV === 'production'
1540
- ? { maxAge: Number.isNaN(hstsMaxAge) ? 15552000 : hstsMaxAge, includeSubDomains: true }
1541
- : false,
2019
+ import appConfig from '../config/app'
2020
+ import {
2021
+ AppServiceProvider,
2022
+ RepositoryServiceProvider,
2023
+ MiddlewareProvider,
2024
+ RouteProvider,
2025
+ } from './Infrastructure/Providers'
2026
+
2027
+ export async function bootstrap() {
2028
+ // 1. Configure
2029
+ const config = defineConfig({
2030
+ config: appConfig,
2031
+ orbits: [new OrbitAtlas()],
1542
2032
  })
1543
- )
1544
- if (!Number.isNaN(bodyLimit) && bodyLimit > 0) {
1545
- core.adapter.use('*', bodySizeLimit(bodyLimit, { requireContentLength: requireLength }))
1546
- }
1547
2033
 
1548
- await core.orbit(new OrbitAtlas())
2034
+ // 2. Boot Core
2035
+ const core = await PlanetCore.boot(config)
2036
+ core.registerGlobalErrorHandlers()
1549
2037
 
1550
- core.register(new RepositoryServiceProvider())
1551
- core.register(new AppServiceProvider())
2038
+ // 3. Register Providers
2039
+ core.register(new RepositoryServiceProvider())
2040
+ core.register(new AppServiceProvider())
2041
+ core.register(new MiddlewareProvider())
2042
+ core.register(new RouteProvider())
1552
2043
 
1553
- await core.bootstrap()
2044
+ // 4. Bootstrap All Providers
2045
+ await core.bootstrap()
1554
2046
 
1555
- // Register routes
1556
- registerApiRoutes(core.router)
2047
+ return core
2048
+ }
1557
2049
 
2050
+ // Application Entry Point
2051
+ const core = await bootstrap()
1558
2052
  export default core.liftoff()
1559
2053
  `;
1560
2054
  }
@@ -1566,6 +2060,15 @@ export default core.liftoff()
1566
2060
  This project follows **Clean Architecture** (by Robert C. Martin).
1567
2061
  The key principle is the **Dependency Rule**: dependencies point inward.
1568
2062
 
2063
+ ## Service Providers
2064
+
2065
+ Service providers are the central place to configure your application. They follow the ServiceProvider pattern with \`register()\` and \`boot()\` lifecycle methods.
2066
+
2067
+ ### Provider Lifecycle
2068
+
2069
+ 1. **register()**: Bind services to the container (sync or async).
2070
+ 2. **boot()**: Called after ALL providers have registered. Safe to use other services.
2071
+
1569
2072
  ## Layer Structure
1570
2073
 
1571
2074
  \`\`\`
@@ -1637,6 +2140,10 @@ Created with \u2764\uFE0F using Gravito Framework
1637
2140
  start: "bun run dist/bootstrap.js",
1638
2141
  test: "bun test",
1639
2142
  typecheck: "tsc --noEmit",
2143
+ check: "bun run typecheck && bun run test",
2144
+ "check:deps": "bun run scripts/check-dependencies.ts",
2145
+ validate: "bun run check && bun run check:deps",
2146
+ precommit: "bun run validate",
1640
2147
  "docker:build": `docker build -t ${context.nameKebabCase} .`,
1641
2148
  "docker:run": `docker run -it -p 3000:3000 ${context.nameKebabCase}`
1642
2149
  },
@@ -1960,63 +2467,46 @@ var DddGenerator = class extends BaseGenerator {
1960
2467
  // ─────────────────────────────────────────────────────────────
1961
2468
  // Bootstrap File Generators
1962
2469
  // ─────────────────────────────────────────────────────────────
1963
- generateBootstrapApp(context) {
2470
+ generateBootstrapApp(_context) {
1964
2471
  return `/**
1965
2472
  * Application Bootstrap
1966
2473
  *
1967
- * Central configuration and initialization of the application.
2474
+ * Central configuration and initialization using the ServiceProvider pattern.
2475
+ *
2476
+ * Lifecycle:
2477
+ * 1. Configure: Load app config and orbits
2478
+ * 2. Boot: Initialize PlanetCore
2479
+ * 3. Register Providers: Bind services to container
2480
+ * 4. Bootstrap: Boot all providers
1968
2481
  */
1969
2482
 
1970
- import { bodySizeLimit, PlanetCore, securityHeaders } from '@gravito/core'
2483
+ import { defineConfig, PlanetCore } from '@gravito/core'
2484
+ import { OrbitAtlas } from '@gravito/atlas'
2485
+ import appConfig from '../../config/app'
1971
2486
  import { registerProviders } from './providers'
1972
2487
  import { registerRoutes } from './routes'
1973
2488
 
1974
2489
  export async function createApp(): Promise<PlanetCore> {
1975
- const core = new PlanetCore({
1976
- config: {
1977
- APP_NAME: '${context.name}',
1978
- },
1979
- })
2490
+ // 1. Configure
2491
+ const config = defineConfig({
2492
+ config: appConfig,
2493
+ orbits: [new OrbitAtlas()],
2494
+ })
1980
2495
 
1981
- const defaultCsp = [
1982
- "default-src 'self'",
1983
- "script-src 'self' 'unsafe-inline'",
1984
- "style-src 'self' 'unsafe-inline'",
1985
- "img-src 'self' data:",
1986
- "object-src 'none'",
1987
- "base-uri 'self'",
1988
- "frame-ancestors 'none'",
1989
- ].join('; ')
1990
- const cspValue = process.env.APP_CSP
1991
- const csp = cspValue === 'false' ? false : (cspValue ?? defaultCsp)
1992
- const hstsMaxAge = Number.parseInt(process.env.APP_HSTS_MAX_AGE ?? '15552000', 10)
1993
- const bodyLimit = Number.parseInt(process.env.APP_BODY_LIMIT ?? '1048576', 10)
1994
- const requireLength = process.env.APP_BODY_REQUIRE_LENGTH === 'true'
1995
-
1996
- core.adapter.use(
1997
- '*',
1998
- securityHeaders({
1999
- contentSecurityPolicy: csp,
2000
- hsts:
2001
- process.env.NODE_ENV === 'production'
2002
- ? { maxAge: Number.isNaN(hstsMaxAge) ? 15552000 : hstsMaxAge, includeSubDomains: true }
2003
- : false,
2004
- })
2005
- )
2006
- if (!Number.isNaN(bodyLimit) && bodyLimit > 0) {
2007
- core.adapter.use('*', bodySizeLimit(bodyLimit, { requireContentLength: requireLength }))
2008
- }
2496
+ // 2. Boot Core
2497
+ const core = await PlanetCore.boot(config)
2498
+ core.registerGlobalErrorHandlers()
2009
2499
 
2010
- // Register all service providers
2011
- await registerProviders(core)
2500
+ // 3. Register Providers
2501
+ await registerProviders(core)
2012
2502
 
2013
- // Bootstrap the application
2014
- await core.bootstrap()
2503
+ // 4. Bootstrap All Providers
2504
+ await core.bootstrap()
2015
2505
 
2016
- // Register routes
2017
- registerRoutes(core.router)
2506
+ // Register routes after bootstrap
2507
+ registerRoutes(core.router)
2018
2508
 
2019
- return core
2509
+ return core
2020
2510
  }
2021
2511
  `;
2022
2512
  }
@@ -2024,19 +2514,48 @@ export async function createApp(): Promise<PlanetCore> {
2024
2514
  return `/**
2025
2515
  * Service Providers Registry
2026
2516
  *
2027
- * Register all module service providers here.
2517
+ * Register all service providers here.
2518
+ * Include both global and module-specific providers.
2028
2519
  */
2029
2520
 
2030
- import type { PlanetCore } from '@gravito/core'
2521
+ import {
2522
+ ServiceProvider,
2523
+ type Container,
2524
+ type PlanetCore,
2525
+ bodySizeLimit,
2526
+ securityHeaders,
2527
+ } from '@gravito/core'
2031
2528
  import { OrderingServiceProvider } from '../Modules/Ordering/Infrastructure/Providers/OrderingServiceProvider'
2032
2529
  import { CatalogServiceProvider } from '../Modules/Catalog/Infrastructure/Providers/CatalogServiceProvider'
2033
2530
 
2531
+ /**
2532
+ * Middleware Provider - Global middleware registration
2533
+ */
2534
+ export class MiddlewareProvider extends ServiceProvider {
2535
+ register(_container: Container): void {}
2536
+
2537
+ boot(core: PlanetCore): void {
2538
+ const isDev = process.env.NODE_ENV !== 'production'
2539
+
2540
+ core.adapter.use('*', securityHeaders({
2541
+ contentSecurityPolicy: isDev ? false : undefined,
2542
+ }))
2543
+
2544
+ core.adapter.use('*', bodySizeLimit(10 * 1024 * 1024))
2545
+
2546
+ core.logger.info('\u{1F6E1}\uFE0F Global middleware registered')
2547
+ }
2548
+ }
2549
+
2034
2550
  export async function registerProviders(core: PlanetCore): Promise<void> {
2035
- // Register module providers
2036
- core.register(new OrderingServiceProvider())
2037
- core.register(new CatalogServiceProvider())
2551
+ // Global Providers
2552
+ core.register(new MiddlewareProvider())
2553
+
2554
+ // Module Providers
2555
+ core.register(new OrderingServiceProvider())
2556
+ core.register(new CatalogServiceProvider())
2038
2557
 
2039
- // Add more providers as needed
2558
+ // Add more providers as needed
2040
2559
  }
2041
2560
  `;
2042
2561
  }
@@ -2150,6 +2669,10 @@ export class ${name}ServiceProvider extends ServiceProvider {
2150
2669
  start: "bun run dist/main.js",
2151
2670
  test: "bun test",
2152
2671
  typecheck: "tsc --noEmit",
2672
+ check: "bun run typecheck && bun run test",
2673
+ "check:deps": "bun run scripts/check-dependencies.ts",
2674
+ validate: "bun run check && bun run check:deps",
2675
+ precommit: "bun run validate",
2153
2676
  "docker:build": `docker build -t ${context.nameKebabCase} .`,
2154
2677
  "docker:run": `docker run -it -p 3000:3000 ${context.nameKebabCase}`
2155
2678
  },
@@ -2590,6 +3113,16 @@ export function report(error: unknown): void {
2590
3113
 
2591
3114
  This project follows **Domain-Driven Design (DDD)** with strategic and tactical patterns.
2592
3115
 
3116
+ ## Service Providers
3117
+
3118
+ Service providers are the central place to configure your application and modules. They follow the ServiceProvider pattern with \`register()\` and \`boot()\` lifecycle methods.
3119
+
3120
+ ### Internal Bootstrapping
3121
+
3122
+ 1. **Bootstrap/app.ts**: Orchestrates the 4-step lifecycle (Configure, Boot, Register, Bootstrap).
3123
+ 2. **Bootstrap/providers.ts**: Central registry for all global and module-specific providers.
3124
+ 3. **Infrastructure/Providers/[Module]ServiceProvider.ts**: Module-specific service registration.
3125
+
2593
3126
  ## Bounded Contexts
2594
3127
 
2595
3128
  \`\`\`
@@ -2727,6 +3260,11 @@ var EnterpriseMvcGenerator = class extends BaseGenerator {
2727
3260
  type: "directory",
2728
3261
  name: "Providers",
2729
3262
  children: [
3263
+ {
3264
+ type: "file",
3265
+ name: "index.ts",
3266
+ content: this.generateProvidersIndex()
3267
+ },
2730
3268
  {
2731
3269
  type: "file",
2732
3270
  name: "AppServiceProvider.ts",
@@ -2734,8 +3272,18 @@ var EnterpriseMvcGenerator = class extends BaseGenerator {
2734
3272
  },
2735
3273
  {
2736
3274
  type: "file",
2737
- name: "RouteServiceProvider.ts",
2738
- content: this.generateRouteServiceProvider(context)
3275
+ name: "DatabaseProvider.ts",
3276
+ content: this.generateDatabaseProvider()
3277
+ },
3278
+ {
3279
+ type: "file",
3280
+ name: "MiddlewareProvider.ts",
3281
+ content: this.generateMiddlewareProvider()
3282
+ },
3283
+ {
3284
+ type: "file",
3285
+ name: "RouteProvider.ts",
3286
+ content: this.generateRouteProvider()
2739
3287
  }
2740
3288
  ]
2741
3289
  },
@@ -3202,6 +3750,135 @@ export class RouteServiceProvider extends ServiceProvider {
3202
3750
  registerRoutes(core.router)
3203
3751
  }
3204
3752
  }
3753
+ `;
3754
+ }
3755
+ // ─────────────────────────────────────────────────────────────
3756
+ // Modern Provider Generators (ServiceProvider Pattern)
3757
+ // ─────────────────────────────────────────────────────────────
3758
+ generateProvidersIndex() {
3759
+ return `/**
3760
+ * Application Service Providers
3761
+ *
3762
+ * Export all providers for easy importing in bootstrap.
3763
+ * Providers are registered in the order they are listed.
3764
+ */
3765
+
3766
+ export { AppServiceProvider } from './AppServiceProvider'
3767
+ export { DatabaseProvider } from './DatabaseProvider'
3768
+ export { MiddlewareProvider } from './MiddlewareProvider'
3769
+ export { RouteProvider } from './RouteProvider'
3770
+ `;
3771
+ }
3772
+ generateDatabaseProvider() {
3773
+ return `/**
3774
+ * Database Service Provider
3775
+ *
3776
+ * Handles database initialization and migrations.
3777
+ *
3778
+ * Lifecycle:
3779
+ * - register(): Bind database config to container
3780
+ * - boot(): Run migrations and seeders
3781
+ */
3782
+
3783
+ import { ServiceProvider, type Container, type PlanetCore } from '@gravito/core'
3784
+ import databaseConfig from '../../config/database'
3785
+
3786
+ export class DatabaseProvider extends ServiceProvider {
3787
+ /**
3788
+ * Register database configuration.
3789
+ */
3790
+ register(_container: Container): void {
3791
+ this.mergeConfig(this.core!.config, 'database', databaseConfig)
3792
+ }
3793
+
3794
+ /**
3795
+ * Initialize database connections.
3796
+ */
3797
+ async boot(core: PlanetCore): Promise<void> {
3798
+ // Database initialization will be handled by Atlas orbit
3799
+ core.logger.info('\u{1F4E6} Database provider booted')
3800
+ }
3801
+ }
3802
+ `;
3803
+ }
3804
+ generateMiddlewareProvider() {
3805
+ return `/**
3806
+ * Middleware Service Provider
3807
+ *
3808
+ * Registers global middleware stack.
3809
+ * Provides a centralized location for middleware configuration.
3810
+ *
3811
+ * Lifecycle:
3812
+ * - register(): N/A (no container bindings)
3813
+ * - boot(): Register global middleware
3814
+ */
3815
+
3816
+ import {
3817
+ ServiceProvider,
3818
+ type Container,
3819
+ type PlanetCore,
3820
+ bodySizeLimit,
3821
+ securityHeaders,
3822
+ } from '@gravito/core'
3823
+
3824
+ export class MiddlewareProvider extends ServiceProvider {
3825
+ /**
3826
+ * No container bindings needed.
3827
+ */
3828
+ register(_container: Container): void {
3829
+ // Middleware doesn't require container bindings
3830
+ }
3831
+
3832
+ /**
3833
+ * Register global middleware stack.
3834
+ */
3835
+ boot(core: PlanetCore): void {
3836
+ const isDev = process.env.NODE_ENV !== 'production'
3837
+
3838
+ // Security Headers
3839
+ core.adapter.use('*', securityHeaders({
3840
+ contentSecurityPolicy: isDev ? false : undefined,
3841
+ }))
3842
+
3843
+ // Body Parser Limits
3844
+ core.adapter.use('*', bodySizeLimit(10 * 1024 * 1024)) // 10MB limit
3845
+
3846
+ core.logger.info('\u{1F6E1}\uFE0F Middleware registered')
3847
+ }
3848
+ }
3849
+ `;
3850
+ }
3851
+ generateRouteProvider() {
3852
+ return `/**
3853
+ * Route Service Provider
3854
+ *
3855
+ * Registers application routes.
3856
+ * Routes are registered in the boot phase after all services are available.
3857
+ *
3858
+ * Lifecycle:
3859
+ * - register(): N/A
3860
+ * - boot(): Register routes
3861
+ */
3862
+
3863
+ import { ServiceProvider, type Container, type PlanetCore } from '@gravito/core'
3864
+ import { registerRoutes } from '../routes'
3865
+
3866
+ export class RouteProvider extends ServiceProvider {
3867
+ /**
3868
+ * No container bindings needed.
3869
+ */
3870
+ register(_container: Container): void {
3871
+ // Routes don't require container bindings
3872
+ }
3873
+
3874
+ /**
3875
+ * Register application routes.
3876
+ */
3877
+ boot(core: PlanetCore): void {
3878
+ registerRoutes(core.router)
3879
+ core.logger.info('\u{1F6E4}\uFE0F Routes registered')
3880
+ }
3881
+ }
3205
3882
  `;
3206
3883
  }
3207
3884
  generateExceptionHandler() {
@@ -3250,74 +3927,75 @@ export const dontReport: string[] = [
3250
3927
  }
3251
3928
  generateBootstrap(context) {
3252
3929
  const spectrumImport = context.withSpectrum ? "import { SpectrumOrbit } from '@gravito/spectrum'\n" : "";
3253
- const spectrumOrbit = context.withSpectrum ? `
3254
- // Enable Debug Dashboard
3255
- if (process.env.APP_DEBUG === 'true') {
3256
- await core.orbit(new SpectrumOrbit())
3257
- }
3258
- ` : "";
3930
+ const spectrumOrbit = context.withSpectrum ? " new SpectrumOrbit()," : "";
3259
3931
  return `/**
3260
3932
  * Application Bootstrap
3261
3933
  *
3262
- * This is the entry point for your application.
3263
- * It initializes the core and registers all providers.
3934
+ * The entry point for your Gravito application.
3935
+ * Uses the ServiceProvider pattern for modular, maintainable initialization.
3936
+ *
3937
+ * Lifecycle:
3938
+ * 1. Configure: Load app config and orbits
3939
+ * 2. Boot: Initialize PlanetCore
3940
+ * 3. Register Providers: Bind services to container
3941
+ * 4. Bootstrap: Boot all providers
3942
+ *
3943
+ * @module bootstrap
3264
3944
  */
3265
3945
 
3266
- import { bodySizeLimit, PlanetCore, securityHeaders } from '@gravito/core'
3946
+ import { defineConfig, PlanetCore } from '@gravito/core'
3267
3947
  import { OrbitAtlas } from '@gravito/atlas'
3268
- import databaseConfig from '../config/database'
3269
- ${spectrumImport}import { AppServiceProvider } from './Providers/AppServiceProvider'
3270
- import { RouteServiceProvider } from './Providers/RouteServiceProvider'
3271
-
3272
- // Load environment variables
3273
- // Bun automatically loads .env
3274
-
3275
- // Create application core
3276
- const core = new PlanetCore({
3277
- config: {
3278
- APP_NAME: process.env.APP_NAME ?? '${context.name}',
3279
- database: databaseConfig,
3280
- },
3281
- })
3282
- const defaultCsp = [
3283
- "default-src 'self'",
3284
- "script-src 'self' 'unsafe-inline'",
3285
- "style-src 'self' 'unsafe-inline'",
3286
- "img-src 'self' data:",
3287
- "object-src 'none'",
3288
- "base-uri 'self'",
3289
- "frame-ancestors 'none'",
3290
- ].join('; ')
3291
- const cspValue = process.env.APP_CSP
3292
- const csp = cspValue === 'false' ? false : (cspValue ?? defaultCsp)
3293
- const hstsMaxAge = Number.parseInt(process.env.APP_HSTS_MAX_AGE ?? '15552000', 10)
3294
- const bodyLimit = Number.parseInt(process.env.APP_BODY_LIMIT ?? '1048576', 10)
3295
- const requireLength = process.env.APP_BODY_REQUIRE_LENGTH === 'true'
3296
-
3297
- core.adapter.use(
3298
- '*',
3299
- securityHeaders({
3300
- contentSecurityPolicy: csp,
3301
- hsts:
3302
- process.env.NODE_ENV === 'production'
3303
- ? { maxAge: Number.isNaN(hstsMaxAge) ? 15552000 : hstsMaxAge, includeSubDomains: true }
3304
- : false,
3305
- })
3306
- )
3307
- if (!Number.isNaN(bodyLimit) && bodyLimit > 0) {
3308
- core.adapter.use('*', bodySizeLimit(bodyLimit, { requireContentLength: requireLength }))
3309
- }
3948
+ import appConfig from '../config/app'
3949
+ ${spectrumImport}import {
3950
+ AppServiceProvider,
3951
+ DatabaseProvider,
3952
+ MiddlewareProvider,
3953
+ RouteProvider,
3954
+ } from './Providers'
3310
3955
 
3311
- await core.orbit(new OrbitAtlas())
3956
+ /**
3957
+ * Bootstrap the application with service providers.
3958
+ *
3959
+ * @returns The booted PlanetCore instance
3960
+ */
3961
+ export async function bootstrap() {
3962
+ // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
3963
+ // 1. Configure
3964
+ // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
3965
+ const config = defineConfig({
3966
+ config: appConfig,
3967
+ orbits: [
3968
+ new OrbitAtlas(),
3312
3969
  ${spectrumOrbit}
3313
- // Register service providers
3314
- core.register(new AppServiceProvider())
3315
- core.register(new RouteServiceProvider())
3970
+ ],
3971
+ })
3316
3972
 
3317
- // Bootstrap the application
3318
- await core.bootstrap()
3973
+ // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
3974
+ // 2. Boot Core
3975
+ // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
3976
+ const core = await PlanetCore.boot(config)
3977
+ core.registerGlobalErrorHandlers()
3978
+
3979
+ // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
3980
+ // 3. Register Providers
3981
+ // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
3982
+ core.register(new AppServiceProvider())
3983
+ core.register(new DatabaseProvider())
3984
+ core.register(new MiddlewareProvider())
3985
+ core.register(new RouteProvider())
3986
+
3987
+ // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
3988
+ // 4. Bootstrap All Providers
3989
+ // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
3990
+ await core.bootstrap()
3991
+
3992
+ return core
3993
+ }
3319
3994
 
3320
- // Export for Bun.serve()
3995
+ // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
3996
+ // Application Entry Point
3997
+ // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
3998
+ const core = await bootstrap()
3321
3999
  export default core.liftoff()
3322
4000
  `;
3323
4001
  }
@@ -3744,7 +4422,9 @@ export class ${name}ServiceProvider extends ServiceProvider {
3744
4422
  scripts: {
3745
4423
  build: "tsup src/index.ts --format cjs,esm --dts",
3746
4424
  test: "bun test",
3747
- typecheck: "tsc --noEmit"
4425
+ typecheck: "tsc --noEmit",
4426
+ check: "bun run typecheck && bun run test",
4427
+ validate: "bun run check"
3748
4428
  },
3749
4429
  dependencies: {
3750
4430
  "@gravito/core": depVersion,
@@ -4042,10 +4722,25 @@ var ActionDomainGenerator = class extends BaseGenerator {
4042
4722
  type: "directory",
4043
4723
  name: "providers",
4044
4724
  children: [
4725
+ {
4726
+ type: "file",
4727
+ name: "index.ts",
4728
+ content: this.generateProvidersIndex()
4729
+ },
4045
4730
  {
4046
4731
  type: "file",
4047
4732
  name: "AppServiceProvider.ts",
4048
4733
  content: this.generateAppServiceProvider(context)
4734
+ },
4735
+ {
4736
+ type: "file",
4737
+ name: "MiddlewareProvider.ts",
4738
+ content: this.generateMiddlewareProvider()
4739
+ },
4740
+ {
4741
+ type: "file",
4742
+ name: "RouteProvider.ts",
4743
+ content: this.generateRouteProvider()
4049
4744
  }
4050
4745
  ]
4051
4746
  },
@@ -4223,7 +4918,7 @@ export function registerApiRoutes(router: Router) {
4223
4918
  import { ServiceProvider, type Container, type PlanetCore } from '@gravito/core'
4224
4919
 
4225
4920
  export class AppServiceProvider extends ServiceProvider {
4226
- register(container: Container): void {
4921
+ register(_container: Container): void {
4227
4922
  // Register global services here
4228
4923
  }
4229
4924
 
@@ -4233,47 +4928,140 @@ export class AppServiceProvider extends ServiceProvider {
4233
4928
  }
4234
4929
  `;
4235
4930
  }
4236
- generateBootstrap(context) {
4931
+ generateProvidersIndex() {
4237
4932
  return `/**
4238
- * Application Entry Point
4933
+ * Application Service Providers
4239
4934
  */
4240
4935
 
4241
- import { PlanetCore, securityHeaders, bodySizeLimit } from '@gravito/core'
4242
- import { OrbitAtlas } from '@gravito/atlas'
4243
- import databaseConfig from '../config/database'
4244
- import { AppServiceProvider } from './providers/AppServiceProvider'
4245
- import { registerApiRoutes } from './routes/api'
4246
-
4247
- // Initialize Core
4248
- const core = new PlanetCore({
4249
- config: {
4250
- APP_NAME: '${context.name}',
4251
- database: databaseConfig
4252
- },
4253
- })
4936
+ export { AppServiceProvider } from './AppServiceProvider'
4937
+ export { MiddlewareProvider } from './MiddlewareProvider'
4938
+ export { RouteProvider } from './RouteProvider'
4939
+ `;
4940
+ }
4941
+ generateMiddlewareProvider() {
4942
+ return `/**
4943
+ * Middleware Service Provider
4944
+ */
4254
4945
 
4255
- // Middleware
4256
- core.adapter.use('*', securityHeaders())
4257
- core.adapter.use('*', bodySizeLimit(1024 * 1024)) // 1MB
4946
+ import {
4947
+ ServiceProvider,
4948
+ type Container,
4949
+ type PlanetCore,
4950
+ bodySizeLimit,
4951
+ securityHeaders,
4952
+ } from '@gravito/core'
4258
4953
 
4259
- // Install Orbits
4260
- await core.orbit(new OrbitAtlas())
4954
+ export class MiddlewareProvider extends ServiceProvider {
4955
+ register(_container: Container): void {}
4261
4956
 
4262
- // Service Providers
4263
- core.register(new AppServiceProvider())
4957
+ boot(core: PlanetCore): void {
4958
+ core.adapter.use('*', securityHeaders())
4959
+ core.adapter.use('*', bodySizeLimit(1024 * 1024))
4960
+ core.logger.info('\u{1F6E1}\uFE0F Middleware registered')
4961
+ }
4962
+ }
4963
+ `;
4964
+ }
4965
+ generateRouteProvider() {
4966
+ return `/**
4967
+ * Route Service Provider
4968
+ */
4969
+
4970
+ import { ServiceProvider, type Container, type PlanetCore } from '@gravito/core'
4971
+ import { registerApiRoutes } from '../routes/api'
4972
+
4973
+ export class RouteProvider extends ServiceProvider {
4974
+ register(_container: Container): void {}
4975
+
4976
+ boot(core: PlanetCore): void {
4977
+ registerApiRoutes(core.router)
4978
+ core.logger.info('\u{1F6E4}\uFE0F Routes registered')
4979
+ }
4980
+ }
4981
+ `;
4982
+ }
4983
+ generateBootstrap(_context) {
4984
+ return `/**
4985
+ * Application Bootstrap
4986
+ *
4987
+ * Uses the ServiceProvider pattern for modular initialization.
4988
+ */
4989
+
4990
+ import { defineConfig, PlanetCore } from '@gravito/core'
4991
+ import { OrbitAtlas } from '@gravito/atlas'
4992
+ import appConfig from '../config/app'
4993
+ import {
4994
+ AppServiceProvider,
4995
+ MiddlewareProvider,
4996
+ RouteProvider,
4997
+ } from './providers'
4998
+
4999
+ export async function bootstrap() {
5000
+ const config = defineConfig({
5001
+ config: appConfig,
5002
+ orbits: [new OrbitAtlas()],
5003
+ })
5004
+
5005
+ const core = await PlanetCore.boot(config)
5006
+ core.registerGlobalErrorHandlers()
4264
5007
 
4265
- // Bootstrap
4266
- await core.bootstrap()
5008
+ core.register(new AppServiceProvider())
5009
+ core.register(new MiddlewareProvider())
5010
+ core.register(new RouteProvider())
4267
5011
 
4268
- // Routes
4269
- registerApiRoutes(core.router)
5012
+ await core.bootstrap()
4270
5013
 
4271
- // Liftoff
5014
+ return core
5015
+ }
5016
+
5017
+ const core = await bootstrap()
4272
5018
  export default core.liftoff()
4273
5019
  `;
4274
5020
  }
4275
5021
  generateArchitectureDoc(context) {
4276
- return "# " + context.name + ' - Action Domain Architecture\n\n## Overview\n\nThis project uses the **Action Domain** pattern, designed for high-clarity API implementations.\n\n## Directory Structure\n\n```\nsrc/\n\u251C\u2500\u2500 actions/ # Single Responsibility Business Logic\n\u2502 \u251C\u2500\u2500 Action.ts # Base Action class\n\u2502 \u2514\u2500\u2500 [Domain]/ # Domain-specific actions\n\u251C\u2500\u2500 controllers/ # HTTP Request Handlers\n\u2502 \u2514\u2500\u2500 api/v1/ # API Controllers\n\u251C\u2500\u2500 types/ # TypeScript Definitions\n\u2502 \u251C\u2500\u2500 requests/ # Request Payloads\n\u2502 \u2514\u2500\u2500 responses/ # Response Structures\n\u251C\u2500\u2500 repositories/ # Data Access Layer\n\u251C\u2500\u2500 routes/ # Route Definitions\n\u2514\u2500\u2500 config/ # Configuration\n```\n\n## Core Concepts\n\n### Actions\nEvery business operation is an "Action". An action:\n- Does ONE thing.\n- Takes specific input.\n- Returns specific output.\n- Is framework-agnostic (ideally).\n\n### Controllers\nControllers are thin. They:\n1. Parse the request.\n2. Instantiate/Call the Action.\n3. Return the response.\n\nCreated with \u2764\uFE0F using Gravito Framework\n';
5022
+ return `# ${context.name} - Action Domain Architecture
5023
+
5024
+ ## Overview
5025
+
5026
+ This project uses the **Action Domain** pattern, designed for high-clarity API implementations.
5027
+
5028
+ ## Service Providers
5029
+
5030
+ Service providers are the central place to configure your application. They follow the ServiceProvider pattern with \`register()\` and \`boot()\` lifecycle methods.
5031
+
5032
+ ## Directory Structure
5033
+
5034
+ \`\`\`
5035
+ src/
5036
+ \u251C\u2500\u2500 actions/ # Single Responsibility Business Logic
5037
+ \u2502 \u251C\u2500\u2500 Action.ts # Base Action class
5038
+ \u2502 \u2514\u2500\u2500 [Domain]/ # Domain-specific actions
5039
+ \u251C\u2500\u2500 controllers/ # HTTP Request Handlers
5040
+ \u2502 \u2514\u2500\u2500 api/v1/ # API Controllers
5041
+ \u251C\u2500\u2500 types/ # TypeScript Definitions
5042
+ \u251C\u2500\u2500 repositories/ # Data Access Layer
5043
+ \u251C\u2500\u2500 routes/ # Route Definitions
5044
+ \u251C\u2500\u2500 providers/ # Service Providers
5045
+ \u2514\u2500\u2500 config/ # Configuration
5046
+ \`\`\`
5047
+
5048
+ ## Core Concepts
5049
+
5050
+ ### Actions
5051
+ Every business operation is an "Action". An action:
5052
+ - Does ONE thing.
5053
+ - Takes specific input.
5054
+ - Returns specific output.
5055
+ - Is framework-agnostic (ideally).
5056
+
5057
+ ### Controllers
5058
+ Controllers are thin. They:
5059
+ 1. Parse the request.
5060
+ 2. Instantiate/Call the Action.
5061
+ 3. Return the response.
5062
+
5063
+ Created with \u2764\uFE0F using Gravito Framework
5064
+ `;
4277
5065
  }
4278
5066
  generatePackageJson(context) {
4279
5067
  const pkg = {
@@ -4285,7 +5073,11 @@ export default core.liftoff()
4285
5073
  build: "bun build ./src/bootstrap.ts --outdir ./dist --target bun",
4286
5074
  start: "bun run dist/bootstrap.js",
4287
5075
  test: "bun test",
4288
- typecheck: "tsc --noEmit"
5076
+ typecheck: "tsc --noEmit",
5077
+ check: "bun run typecheck && bun run test",
5078
+ "check:deps": "bun run scripts/check-dependencies.ts",
5079
+ validate: "bun run check && bun run check:deps",
5080
+ precommit: "bun run validate"
4289
5081
  },
4290
5082
  dependencies: {
4291
5083
  "@gravito/core": "workspace:*",