@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.js CHANGED
@@ -346,6 +346,7 @@ var BaseGenerator = class {
346
346
  "ARCHITECTURE.md",
347
347
  this.generateArchitectureDoc(context)
348
348
  );
349
+ await this.generateCheckScripts(context);
349
350
  }
350
351
  /**
351
352
  * Apply profile-specific overlays
@@ -433,6 +434,10 @@ var BaseGenerator = class {
433
434
  start: "bun run dist/bootstrap.js",
434
435
  test: "bun test",
435
436
  typecheck: "tsc --noEmit",
437
+ check: "bun run typecheck && bun run test",
438
+ "check:deps": "bun run scripts/check-dependencies.ts",
439
+ validate: "bun run check && bun run check:deps",
440
+ precommit: "bun run validate",
436
441
  "docker:build": `docker build -t ${context.nameKebabCase} .`,
437
442
  "docker:run": `docker run -it -p 3000:3000 ${context.nameKebabCase}`
438
443
  },
@@ -685,6 +690,430 @@ coverage/
685
690
  };
686
691
  return JSON.stringify(config, null, 2);
687
692
  }
693
+ /**
694
+ * Generate check scripts for project validation.
695
+ */
696
+ async generateCheckScripts(context) {
697
+ const scriptsDir = path2.resolve(context.targetDir, "scripts");
698
+ await fs2.mkdir(scriptsDir, { recursive: true });
699
+ await this.writeFile(
700
+ scriptsDir,
701
+ "check-dependencies.ts",
702
+ this.generateCheckDependenciesScript()
703
+ );
704
+ await this.writeFile(scriptsDir, "check.sh", this.generateCheckShellScript());
705
+ await this.writeFile(scriptsDir, "pre-commit.sh", this.generatePreCommitScript());
706
+ await this.writeFile(context.targetDir, "CHECK_SYSTEM.md", this.generateCheckSystemDoc(context));
707
+ }
708
+ /**
709
+ * Generate check-dependencies.ts script content.
710
+ */
711
+ generateCheckDependenciesScript() {
712
+ return `/**
713
+ * \u76F8\u4F9D\u5957\u4EF6\u7248\u672C\u6AA2\u67E5\u8173\u672C
714
+ *
715
+ * \u6AA2\u67E5 package.json \u4E2D\u7684\u5957\u4EF6\u662F\u5426\u70BA\u6700\u65B0\u7A69\u5B9A\u7248\u672C
716
+ * \u4E26\u63D0\u4F9B\u66F4\u65B0\u5EFA\u8B70
717
+ */
718
+
719
+ import { readFileSync } from 'fs'
720
+ import { join } from 'path'
721
+
722
+ interface PackageJson {
723
+ dependencies?: Record<string, string>
724
+ devDependencies?: Record<string, string>
725
+ }
726
+
727
+ interface PackageInfo {
728
+ name: string
729
+ current: string
730
+ latest: string
731
+ outdated: boolean
732
+ }
733
+
734
+ const colors = {
735
+ reset: '\\x1b[0m',
736
+ green: '\\x1b[32m',
737
+ yellow: '\\x1b[33m',
738
+ red: '\\x1b[31m',
739
+ blue: '\\x1b[36m',
740
+ }
741
+
742
+ function log(message: string, color: keyof typeof colors = 'reset') {
743
+ console.log(\`\${colors[color]}\${message}\${colors.reset}\`)
744
+ }
745
+
746
+ async function getLatestVersion(packageName: string): Promise<string | null> {
747
+ try {
748
+ const response = await fetch(\`https://registry.npmjs.org/\${packageName}/latest\`)
749
+ if (!response.ok) return null
750
+ const data = await response.json()
751
+ return data.version
752
+ } catch {
753
+ return null
754
+ }
755
+ }
756
+
757
+ function parseVersion(version: string): string {
758
+ // \u79FB\u9664 ^, ~, >= \u7B49\u524D\u7DB4
759
+ return version.replace(/^[\\^~>=<]/, '')
760
+ }
761
+
762
+ async function checkPackage(
763
+ name: string,
764
+ currentVersion: string
765
+ ): Promise<PackageInfo | null> {
766
+ // \u8DF3\u904E\u672C\u5730\u9023\u7D50\u7684\u5957\u4EF6
767
+ if (currentVersion.startsWith('link:') || currentVersion.startsWith('workspace:')) {
768
+ return null
769
+ }
770
+
771
+ const current = parseVersion(currentVersion)
772
+ const latest = await getLatestVersion(name)
773
+
774
+ if (!latest) {
775
+ return null
776
+ }
777
+
778
+ return {
779
+ name,
780
+ current,
781
+ latest,
782
+ outdated: current !== latest,
783
+ }
784
+ }
785
+
786
+ async function main() {
787
+ log('\\n=== \u76F8\u4F9D\u5957\u4EF6\u7248\u672C\u6AA2\u67E5 ===\\n', 'blue')
788
+
789
+ const packageJsonPath = join(process.cwd(), 'package.json')
790
+ const packageJson: PackageJson = JSON.parse(
791
+ readFileSync(packageJsonPath, 'utf-8')
792
+ )
793
+
794
+ const allDependencies = {
795
+ ...packageJson.dependencies,
796
+ ...packageJson.devDependencies,
797
+ }
798
+
799
+ log(\`\u6AA2\u67E5 \${Object.keys(allDependencies).length} \u500B\u5957\u4EF6...\\n\`, 'yellow')
800
+
801
+ const results: PackageInfo[] = []
802
+ const outdated: PackageInfo[] = []
803
+ const upToDate: PackageInfo[] = []
804
+
805
+ // \u6AA2\u67E5\u6240\u6709\u5957\u4EF6
806
+ for (const [name, version] of Object.entries(allDependencies)) {
807
+ const info = await checkPackage(name, version)
808
+ if (info) {
809
+ results.push(info)
810
+ if (info.outdated) {
811
+ outdated.push(info)
812
+ } else {
813
+ upToDate.push(info)
814
+ }
815
+ }
816
+ }
817
+
818
+ // \u986F\u793A\u7D50\u679C
819
+ if (upToDate.length > 0) {
820
+ log(\`\\n\u2713 \u5DF2\u662F\u6700\u65B0\u7248\u672C (\${upToDate.length}):\`, 'green')
821
+ upToDate.forEach((pkg) => {
822
+ log(\` \${pkg.name}: \${pkg.current}\`, 'green')
823
+ })
824
+ }
825
+
826
+ if (outdated.length > 0) {
827
+ log(\`\\n\u26A0 \u9700\u8981\u66F4\u65B0 (\${outdated.length}):\`, 'yellow')
828
+ outdated.forEach((pkg) => {
829
+ log(\` \${pkg.name}: \${pkg.current} \u2192 \${pkg.latest}\`, 'yellow')
830
+ })
831
+ }
832
+
833
+ // \u7E3D\u7D50
834
+ log('\\n=== \u6AA2\u67E5\u7D50\u679C ===', 'blue')
835
+ log(\`\u7E3D\u8A08: \${results.length} \u500B\u5957\u4EF6\`, 'blue')
836
+ log(\`\u6700\u65B0: \${upToDate.length} \u500B\`, 'green')
837
+ log(\`\u9700\u66F4\u65B0: \${outdated.length} \u500B\`, outdated.length > 0 ? 'yellow' : 'green')
838
+
839
+ // \u5982\u679C\u6709\u9700\u8981\u66F4\u65B0\u7684\u5957\u4EF6\uFF0C\u8FD4\u56DE\u975E\u96F6\u9000\u51FA\u78BC
840
+ if (outdated.length > 0) {
841
+ log('\\n\u5EFA\u8B70\u57F7\u884C\u4EE5\u4E0B\u547D\u4EE4\u66F4\u65B0\u5957\u4EF6\uFF1A', 'yellow')
842
+ log(' bun update', 'yellow')
843
+ process.exit(1)
844
+ } else {
845
+ log('\\n\u2713 \u6240\u6709\u5957\u4EF6\u90FD\u662F\u6700\u65B0\u7248\u672C\uFF01', 'green')
846
+ process.exit(0)
847
+ }
848
+ }
849
+
850
+ main().catch((error) => {
851
+ log(\`\\n\u932F\u8AA4: \${error.message}\`, 'red')
852
+ process.exit(1)
853
+ })
854
+ `;
855
+ }
856
+ /**
857
+ * Generate check.sh script content.
858
+ */
859
+ generateCheckShellScript() {
860
+ return `#!/bin/bash
861
+
862
+ # \u5C08\u6848\u6AA2\u67E5\u8173\u672C
863
+ # \u57F7\u884C\u6240\u6709\u5FC5\u8981\u7684\u6AA2\u67E5\uFF1A\u985E\u578B\u6AA2\u67E5\u3001\u6E2C\u8A66\u3001\u4F9D\u8CF4\u6AA2\u67E5\u7B49
864
+
865
+ set -e
866
+
867
+ # \u984F\u8272\u5B9A\u7FA9
868
+ GREEN='\\033[0;32m'
869
+ YELLOW='\\033[1;33m'
870
+ RED='\\033[0;31m'
871
+ BLUE='\\033[0;34m'
872
+ NC='\\033[0m' # No Color
873
+
874
+ echo -e "\${BLUE}=== \u5C08\u6848\u6AA2\u67E5 ===\${NC}\\n"
875
+
876
+ # \u6AA2\u67E5\u662F\u5426\u5728\u6B63\u78BA\u7684\u76EE\u9304
877
+ if [ ! -f "package.json" ]; then
878
+ echo -e "\${RED}\u932F\u8AA4: \u8ACB\u5728\u5C08\u6848\u6839\u76EE\u9304\u57F7\u884C\u6B64\u8173\u672C\${NC}"
879
+ exit 1
880
+ fi
881
+
882
+ # \u6AA2\u67E5 Bun \u662F\u5426\u5B89\u88DD
883
+ if ! command -v bun &> /dev/null; then
884
+ echo -e "\${RED}\u932F\u8AA4: \u672A\u627E\u5230 bun\uFF0C\u8ACB\u5148\u5B89\u88DD Bun\${NC}"
885
+ exit 1
886
+ fi
887
+
888
+ # 1. \u985E\u578B\u6AA2\u67E5
889
+ echo -e "\${YELLOW}[1/3] \u57F7\u884C\u985E\u578B\u6AA2\u67E5...\${NC}"
890
+ if bun run typecheck; then
891
+ echo -e "\${GREEN}\u2713 \u985E\u578B\u6AA2\u67E5\u901A\u904E\${NC}\\n"
892
+ else
893
+ echo -e "\${RED}\u2717 \u985E\u578B\u6AA2\u67E5\u5931\u6557\${NC}"
894
+ exit 1
895
+ fi
896
+
897
+ # 2. \u57F7\u884C\u6E2C\u8A66
898
+ echo -e "\${YELLOW}[2/3] \u57F7\u884C\u6E2C\u8A66...\${NC}"
899
+ if bun test; then
900
+ echo -e "\${GREEN}\u2713 \u6E2C\u8A66\u901A\u904E\${NC}\\n"
901
+ else
902
+ echo -e "\${RED}\u2717 \u6E2C\u8A66\u5931\u6557\${NC}"
903
+ exit 1
904
+ fi
905
+
906
+ # 3. \u6AA2\u67E5\u4F9D\u8CF4\u7248\u672C\uFF08\u53EF\u9078\uFF0C\u56E0\u70BA\u9700\u8981\u7DB2\u8DEF\u9023\u7DDA\uFF09
907
+ echo -e "\${YELLOW}[3/3] \u6AA2\u67E5\u4F9D\u8CF4\u7248\u672C...\${NC}"
908
+ if bun run check:deps; then
909
+ echo -e "\${GREEN}\u2713 \u4F9D\u8CF4\u6AA2\u67E5\u5B8C\u6210\${NC}\\n"
910
+ else
911
+ 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"
912
+ fi
913
+
914
+ echo -e "\${GREEN}=== \u6240\u6709\u6AA2\u67E5\u5B8C\u6210 ===\${NC}"
915
+ `;
916
+ }
917
+ /**
918
+ * Generate pre-commit.sh script content.
919
+ */
920
+ generatePreCommitScript() {
921
+ return `#!/bin/bash
922
+
923
+ # Pre-commit Hook
924
+ # \u5728 git commit \u524D\u81EA\u52D5\u57F7\u884C\u6AA2\u67E5
925
+ #
926
+ # \u5B89\u88DD\u65B9\u5F0F\uFF1A
927
+ # ln -s ../../scripts/pre-commit.sh .git/hooks/pre-commit
928
+ # \u6216
929
+ # cp scripts/pre-commit.sh .git/hooks/pre-commit
930
+ # chmod +x .git/hooks/pre-commit
931
+
932
+ set -e
933
+
934
+ # \u984F\u8272\u5B9A\u7FA9
935
+ GREEN='\\033[0;32m'
936
+ YELLOW='\\033[1;33m'
937
+ RED='\\033[0;31m'
938
+ BLUE='\\033[0;34m'
939
+ NC='\\033[0m' # No Color
940
+
941
+ echo -e "\${BLUE}=== Pre-commit \u6AA2\u67E5 ===\${NC}\\n"
942
+
943
+ # \u5207\u63DB\u5230\u5C08\u6848\u6839\u76EE\u9304
944
+ cd "$(git rev-parse --show-toplevel)"
945
+
946
+ # \u6AA2\u67E5\u662F\u5426\u5728\u6B63\u78BA\u7684\u76EE\u9304
947
+ if [ ! -f "package.json" ]; then
948
+ echo -e "\${RED}\u932F\u8AA4: \u627E\u4E0D\u5230 package.json\${NC}"
949
+ exit 1
950
+ fi
951
+
952
+ # \u6AA2\u67E5 Bun \u662F\u5426\u5B89\u88DD
953
+ if ! command -v bun &> /dev/null; then
954
+ echo -e "\${RED}\u932F\u8AA4: \u672A\u627E\u5230 bun\uFF0C\u8ACB\u5148\u5B89\u88DD Bun\${NC}"
955
+ exit 1
956
+ fi
957
+
958
+ # 1. \u985E\u578B\u6AA2\u67E5\uFF08\u5FEB\u901F\u6AA2\u67E5\uFF09
959
+ echo -e "\${YELLOW}[1/2] \u57F7\u884C\u985E\u578B\u6AA2\u67E5...\${NC}"
960
+ if bun run typecheck; then
961
+ echo -e "\${GREEN}\u2713 \u985E\u578B\u6AA2\u67E5\u901A\u904E\${NC}\\n"
962
+ else
963
+ echo -e "\${RED}\u2717 \u985E\u578B\u6AA2\u67E5\u5931\u6557\${NC}"
964
+ echo -e "\${YELLOW}\u63D0\u793A: \u8ACB\u4FEE\u6B63\u985E\u578B\u932F\u8AA4\u5F8C\u518D\u63D0\u4EA4\${NC}"
965
+ exit 1
966
+ fi
967
+
968
+ # 2. \u57F7\u884C\u6E2C\u8A66\uFF08\u53EF\u9078\uFF0C\u5982\u679C\u6E2C\u8A66\u6642\u9593\u8F03\u9577\u53EF\u4EE5\u8A3B\u89E3\u6389\uFF09
969
+ echo -e "\${YELLOW}[2/2] \u57F7\u884C\u6E2C\u8A66...\${NC}"
970
+ if bun test; then
971
+ echo -e "\${GREEN}\u2713 \u6E2C\u8A66\u901A\u904E\${NC}\\n"
972
+ else
973
+ echo -e "\${RED}\u2717 \u6E2C\u8A66\u5931\u6557\${NC}"
974
+ echo -e "\${YELLOW}\u63D0\u793A: \u8ACB\u4FEE\u6B63\u6E2C\u8A66\u932F\u8AA4\u5F8C\u518D\u63D0\u4EA4\${NC}"
975
+ exit 1
976
+ fi
977
+
978
+ echo -e "\${GREEN}=== Pre-commit \u6AA2\u67E5\u901A\u904E ===\${NC}\\n"
979
+ `;
980
+ }
981
+ /**
982
+ * Generate CHECK_SYSTEM.md documentation.
983
+ */
984
+ generateCheckSystemDoc(context) {
985
+ return `# \u5C08\u6848\u6AA2\u67E5\u7CFB\u7D71
986
+
987
+ \u672C\u5C08\u6848\u5DF2\u5EFA\u7ACB\u5B8C\u6574\u7684\u672C\u5730\u6AA2\u67E5\u6A5F\u5236\uFF0C\u7121\u9700\u4F9D\u8CF4 GitHub CI\u3002
988
+
989
+ ## \u5FEB\u901F\u958B\u59CB
990
+
991
+ ### \u57F7\u884C\u5B8C\u6574\u6AA2\u67E5
992
+ \`\`\`bash
993
+ bun run validate
994
+ \`\`\`
995
+
996
+ ### \u57F7\u884C\u55AE\u9805\u6AA2\u67E5
997
+ \`\`\`bash
998
+ # \u985E\u578B\u6AA2\u67E5
999
+ bun run typecheck
1000
+
1001
+ # \u6E2C\u8A66
1002
+ bun run test
1003
+
1004
+ # \u4F9D\u8CF4\u7248\u672C\u6AA2\u67E5
1005
+ bun run check:deps
1006
+ \`\`\`
1007
+
1008
+ ## \u53EF\u7528\u547D\u4EE4
1009
+
1010
+ ### Package.json \u8173\u672C
1011
+
1012
+ | \u547D\u4EE4 | \u8AAA\u660E |
1013
+ |------|------|
1014
+ | \`bun run typecheck\` | TypeScript \u985E\u578B\u6AA2\u67E5 |
1015
+ | \`bun run test\` | \u57F7\u884C\u6240\u6709\u6E2C\u8A66 |
1016
+ | \`bun run check\` | \u985E\u578B\u6AA2\u67E5 + \u6E2C\u8A66 |
1017
+ | \`bun run check:deps\` | \u6AA2\u67E5\u4F9D\u8CF4\u7248\u672C |
1018
+ | \`bun run validate\` | \u5B8C\u6574\u9A57\u8B49\uFF08\u985E\u578B + \u6E2C\u8A66 + \u4F9D\u8CF4\uFF09 |
1019
+ | \`bun run precommit\` | \u7B49\u540C\u65BC \`validate\` |
1020
+
1021
+ ### Shell \u8173\u672C
1022
+
1023
+ | \u8173\u672C | \u8AAA\u660E |
1024
+ |------|------|
1025
+ | \`./scripts/check.sh\` | \u5B8C\u6574\u5C08\u6848\u6AA2\u67E5\uFF08Shell \u7248\u672C\uFF09 |
1026
+ | \`./scripts/pre-commit.sh\` | Pre-commit hook \u8173\u672C |
1027
+
1028
+ ## Pre-commit Hook\uFF08\u63A8\u85A6\uFF09
1029
+
1030
+ \u5B89\u88DD pre-commit hook \u5F8C\uFF0C\u6BCF\u6B21 \`git commit\` \u524D\u6703\u81EA\u52D5\u57F7\u884C\u6AA2\u67E5\uFF1A
1031
+
1032
+ \`\`\`bash
1033
+ # \u5B89\u88DD pre-commit hook
1034
+ ln -s ../../scripts/pre-commit.sh .git/hooks/pre-commit
1035
+
1036
+ # \u6216\u4F7F\u7528\u8907\u88FD\u65B9\u5F0F
1037
+ cp scripts/pre-commit.sh .git/hooks/pre-commit
1038
+ chmod +x .git/hooks/pre-commit
1039
+ \`\`\`
1040
+
1041
+ **\u529F\u80FD\uFF1A**
1042
+ - \u2705 \u81EA\u52D5\u57F7\u884C\u985E\u578B\u6AA2\u67E5
1043
+ - \u2705 \u81EA\u52D5\u57F7\u884C\u6E2C\u8A66
1044
+ - \u274C \u6AA2\u67E5\u5931\u6557\u6642\u963B\u6B62\u63D0\u4EA4
1045
+
1046
+ **\u8DF3\u904E\u6AA2\u67E5\uFF08\u4E0D\u63A8\u85A6\uFF09\uFF1A**
1047
+ \`\`\`bash
1048
+ git commit --no-verify -m "\u7DCA\u6025\u4FEE\u5FA9"
1049
+ \`\`\`
1050
+
1051
+ ## \u6AA2\u67E5\u9805\u76EE
1052
+
1053
+ ### 1. \u985E\u578B\u6AA2\u67E5
1054
+ - \u4F7F\u7528 \`tsc --noEmit\` \u6AA2\u67E5 TypeScript \u985E\u578B
1055
+ - \u78BA\u4FDD\u6C92\u6709\u985E\u578B\u932F\u8AA4
1056
+
1057
+ ### 2. \u6E2C\u8A66
1058
+ - \u57F7\u884C\u6240\u6709\u55AE\u5143\u6E2C\u8A66\u548C\u6574\u5408\u6E2C\u8A66
1059
+ - \u78BA\u4FDD\u6E2C\u8A66\u901A\u904E
1060
+
1061
+ ### 3. \u4F9D\u8CF4\u6AA2\u67E5\uFF08\u53EF\u9078\uFF09
1062
+ - \u6AA2\u67E5\u5957\u4EF6\u7248\u672C\u662F\u5426\u70BA\u6700\u65B0
1063
+ - \u63D0\u4F9B\u66F4\u65B0\u5EFA\u8B70
1064
+ - \u9700\u8981\u7DB2\u8DEF\u9023\u7DDA
1065
+
1066
+ ## \u5DE5\u4F5C\u6D41\u7A0B\u5EFA\u8B70
1067
+
1068
+ ### \u958B\u767C\u6642
1069
+ 1. \u958B\u767C\u529F\u80FD
1070
+ 2. \u63D0\u4EA4\u524D\u57F7\u884C \`bun run validate\`
1071
+ 3. \u4FEE\u6B63\u554F\u984C
1072
+ 4. \u63D0\u4EA4\u7A0B\u5F0F\u78BC
1073
+
1074
+ ### \u4F7F\u7528 Pre-commit Hook\uFF08\u63A8\u85A6\uFF09
1075
+ 1. \u5B89\u88DD pre-commit hook\uFF08\u53EA\u9700\u4E00\u6B21\uFF09
1076
+ 2. \u6B63\u5E38\u958B\u767C\u548C\u63D0\u4EA4
1077
+ 3. \u6AA2\u67E5\u6703\u81EA\u52D5\u57F7\u884C
1078
+ 4. \u5982\u6709\u554F\u984C\uFF0C\u4FEE\u6B63\u5F8C\u91CD\u65B0\u63D0\u4EA4
1079
+
1080
+ ## \u6A94\u6848\u7D50\u69CB
1081
+
1082
+ \`\`\`
1083
+ ${context.nameKebabCase}/
1084
+ \u251C\u2500\u2500 package.json # \u6AA2\u67E5\u8173\u672C\u5B9A\u7FA9
1085
+ \u251C\u2500\u2500 scripts/
1086
+ \u2502 \u251C\u2500\u2500 check.sh # \u5B8C\u6574\u6AA2\u67E5\u8173\u672C\uFF08Shell\uFF09
1087
+ \u2502 \u251C\u2500\u2500 check-dependencies.ts # \u4F9D\u8CF4\u7248\u672C\u6AA2\u67E5
1088
+ \u2502 \u2514\u2500\u2500 pre-commit.sh # Pre-commit hook
1089
+ \u2514\u2500\u2500 CHECK_SYSTEM.md # \u672C\u6587\u4EF6
1090
+ \`\`\`
1091
+
1092
+ ## \u6CE8\u610F\u4E8B\u9805
1093
+
1094
+ 1. **\u4F9D\u8CF4\u6AA2\u67E5\u9700\u8981\u7DB2\u8DEF\u9023\u7DDA**\uFF1A\`check:deps\` \u9700\u8981\u9023\u63A5\u5230 npm registry
1095
+ 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
1096
+ 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
1097
+
1098
+ ## \u6545\u969C\u6392\u9664
1099
+
1100
+ ### \u6AA2\u67E5\u5931\u6557
1101
+ 1. \u67E5\u770B\u932F\u8AA4\u8A0A\u606F
1102
+ 2. \u4FEE\u6B63\u554F\u984C
1103
+ 3. \u91CD\u65B0\u57F7\u884C\u6AA2\u67E5
1104
+
1105
+ ### \u8DF3\u904E\u6AA2\u67E5
1106
+ \u53EA\u6709\u5728\u7DCA\u6025\u60C5\u6CC1\u4E0B\u624D\u4F7F\u7528\uFF1A
1107
+ \`\`\`bash
1108
+ git commit --no-verify
1109
+ \`\`\`
1110
+
1111
+ ### \u79FB\u9664 Pre-commit Hook
1112
+ \`\`\`bash
1113
+ rm .git/hooks/pre-commit
1114
+ \`\`\`
1115
+ `;
1116
+ }
688
1117
  /**
689
1118
  * Log a message if verbose mode is enabled.
690
1119
  */
@@ -865,6 +1294,11 @@ var CleanArchitectureGenerator = class extends BaseGenerator {
865
1294
  type: "directory",
866
1295
  name: "Providers",
867
1296
  children: [
1297
+ {
1298
+ type: "file",
1299
+ name: "index.ts",
1300
+ content: this.generateProvidersIndex()
1301
+ },
868
1302
  {
869
1303
  type: "file",
870
1304
  name: "AppServiceProvider.ts",
@@ -874,6 +1308,16 @@ var CleanArchitectureGenerator = class extends BaseGenerator {
874
1308
  type: "file",
875
1309
  name: "RepositoryServiceProvider.ts",
876
1310
  content: this.generateRepositoryServiceProvider()
1311
+ },
1312
+ {
1313
+ type: "file",
1314
+ name: "MiddlewareProvider.ts",
1315
+ content: this.generateMiddlewareProvider()
1316
+ },
1317
+ {
1318
+ type: "file",
1319
+ name: "RouteProvider.ts",
1320
+ content: this.generateRouteProvider()
877
1321
  }
878
1322
  ]
879
1323
  }
@@ -1367,6 +1811,65 @@ export class RepositoryServiceProvider extends ServiceProvider {
1367
1811
  container.singleton('mailService', () => new MailService())
1368
1812
  }
1369
1813
  }
1814
+ `;
1815
+ }
1816
+ generateProvidersIndex() {
1817
+ return `/**
1818
+ * Application Service Providers
1819
+ */
1820
+
1821
+ export { AppServiceProvider } from './AppServiceProvider'
1822
+ export { RepositoryServiceProvider } from './RepositoryServiceProvider'
1823
+ export { MiddlewareProvider } from './MiddlewareProvider'
1824
+ export { RouteProvider } from './RouteProvider'
1825
+ `;
1826
+ }
1827
+ generateMiddlewareProvider() {
1828
+ return `/**
1829
+ * Middleware Service Provider
1830
+ */
1831
+
1832
+ import {
1833
+ ServiceProvider,
1834
+ type Container,
1835
+ type PlanetCore,
1836
+ bodySizeLimit,
1837
+ securityHeaders,
1838
+ } from '@gravito/core'
1839
+
1840
+ export class MiddlewareProvider extends ServiceProvider {
1841
+ register(_container: Container): void {}
1842
+
1843
+ boot(core: PlanetCore): void {
1844
+ const isDev = process.env.NODE_ENV !== 'production'
1845
+
1846
+ core.adapter.use('*', securityHeaders({
1847
+ contentSecurityPolicy: isDev ? false : undefined,
1848
+ }))
1849
+
1850
+ core.adapter.use('*', bodySizeLimit(10 * 1024 * 1024))
1851
+
1852
+ core.logger.info('\u{1F6E1}\uFE0F Middleware registered')
1853
+ }
1854
+ }
1855
+ `;
1856
+ }
1857
+ generateRouteProvider() {
1858
+ return `/**
1859
+ * Route Service Provider
1860
+ */
1861
+
1862
+ import { ServiceProvider, type Container, type PlanetCore } from '@gravito/core'
1863
+ import { registerApiRoutes } from '../../Interface/Http/Routes/api'
1864
+
1865
+ export class RouteProvider extends ServiceProvider {
1866
+ register(_container: Container): void {}
1867
+
1868
+ boot(core: PlanetCore): void {
1869
+ registerApiRoutes(core.router)
1870
+ core.logger.info('\u{1F6E4}\uFE0F Routes registered')
1871
+ }
1872
+ }
1370
1873
  `;
1371
1874
  }
1372
1875
  // ─────────────────────────────────────────────────────────────
@@ -1451,64 +1954,55 @@ export class UserPresenter {
1451
1954
  }
1452
1955
  `;
1453
1956
  }
1454
- generateBootstrap(context) {
1957
+ generateBootstrap(_context) {
1455
1958
  return `/**
1456
1959
  * Application Bootstrap
1960
+ *
1961
+ * The entry point for your Clean Architecture application.
1962
+ * Uses the ServiceProvider pattern for modular initialization.
1963
+ *
1964
+ * Lifecycle:
1965
+ * 1. Configure: Load app config and orbits
1966
+ * 2. Boot: Initialize PlanetCore
1967
+ * 3. Register Providers: Bind services to container
1968
+ * 4. Bootstrap: Boot all providers
1457
1969
  */
1458
1970
 
1459
- import { bodySizeLimit, PlanetCore, securityHeaders } from '@gravito/core'
1971
+ import { defineConfig, PlanetCore } from '@gravito/core'
1460
1972
  import { OrbitAtlas } from '@gravito/atlas'
1461
- import databaseConfig from '../config/database'
1462
- import { AppServiceProvider } from './Infrastructure/Providers/AppServiceProvider'
1463
- import { RepositoryServiceProvider } from './Infrastructure/Providers/RepositoryServiceProvider'
1464
- import { registerApiRoutes } from './Interface/Http/Routes/api'
1465
-
1466
- const core = new PlanetCore({
1467
- config: {
1468
- APP_NAME: '${context.name}',
1469
- database: databaseConfig,
1470
- },
1471
- })
1472
-
1473
- const defaultCsp = [
1474
- "default-src 'self'",
1475
- "script-src 'self' 'unsafe-inline'",
1476
- "style-src 'self' 'unsafe-inline'",
1477
- "img-src 'self' data:",
1478
- "object-src 'none'",
1479
- "base-uri 'self'",
1480
- "frame-ancestors 'none'",
1481
- ].join('; ')
1482
- const cspValue = process.env.APP_CSP
1483
- const csp = cspValue === 'false' ? false : (cspValue ?? defaultCsp)
1484
- const hstsMaxAge = Number.parseInt(process.env.APP_HSTS_MAX_AGE ?? '15552000', 10)
1485
- const bodyLimit = Number.parseInt(process.env.APP_BODY_LIMIT ?? '1048576', 10)
1486
- const requireLength = process.env.APP_BODY_REQUIRE_LENGTH === 'true'
1487
-
1488
- core.adapter.use(
1489
- '*',
1490
- securityHeaders({
1491
- contentSecurityPolicy: csp,
1492
- hsts:
1493
- process.env.NODE_ENV === 'production'
1494
- ? { maxAge: Number.isNaN(hstsMaxAge) ? 15552000 : hstsMaxAge, includeSubDomains: true }
1495
- : false,
1973
+ import appConfig from '../config/app'
1974
+ import {
1975
+ AppServiceProvider,
1976
+ RepositoryServiceProvider,
1977
+ MiddlewareProvider,
1978
+ RouteProvider,
1979
+ } from './Infrastructure/Providers'
1980
+
1981
+ export async function bootstrap() {
1982
+ // 1. Configure
1983
+ const config = defineConfig({
1984
+ config: appConfig,
1985
+ orbits: [new OrbitAtlas()],
1496
1986
  })
1497
- )
1498
- if (!Number.isNaN(bodyLimit) && bodyLimit > 0) {
1499
- core.adapter.use('*', bodySizeLimit(bodyLimit, { requireContentLength: requireLength }))
1500
- }
1501
1987
 
1502
- await core.orbit(new OrbitAtlas())
1988
+ // 2. Boot Core
1989
+ const core = await PlanetCore.boot(config)
1990
+ core.registerGlobalErrorHandlers()
1503
1991
 
1504
- core.register(new RepositoryServiceProvider())
1505
- core.register(new AppServiceProvider())
1992
+ // 3. Register Providers
1993
+ core.register(new RepositoryServiceProvider())
1994
+ core.register(new AppServiceProvider())
1995
+ core.register(new MiddlewareProvider())
1996
+ core.register(new RouteProvider())
1506
1997
 
1507
- await core.bootstrap()
1998
+ // 4. Bootstrap All Providers
1999
+ await core.bootstrap()
1508
2000
 
1509
- // Register routes
1510
- registerApiRoutes(core.router)
2001
+ return core
2002
+ }
1511
2003
 
2004
+ // Application Entry Point
2005
+ const core = await bootstrap()
1512
2006
  export default core.liftoff()
1513
2007
  `;
1514
2008
  }
@@ -1520,6 +2014,15 @@ export default core.liftoff()
1520
2014
  This project follows **Clean Architecture** (by Robert C. Martin).
1521
2015
  The key principle is the **Dependency Rule**: dependencies point inward.
1522
2016
 
2017
+ ## Service Providers
2018
+
2019
+ Service providers are the central place to configure your application. They follow the ServiceProvider pattern with \`register()\` and \`boot()\` lifecycle methods.
2020
+
2021
+ ### Provider Lifecycle
2022
+
2023
+ 1. **register()**: Bind services to the container (sync or async).
2024
+ 2. **boot()**: Called after ALL providers have registered. Safe to use other services.
2025
+
1523
2026
  ## Layer Structure
1524
2027
 
1525
2028
  \`\`\`
@@ -1591,6 +2094,10 @@ Created with \u2764\uFE0F using Gravito Framework
1591
2094
  start: "bun run dist/bootstrap.js",
1592
2095
  test: "bun test",
1593
2096
  typecheck: "tsc --noEmit",
2097
+ check: "bun run typecheck && bun run test",
2098
+ "check:deps": "bun run scripts/check-dependencies.ts",
2099
+ validate: "bun run check && bun run check:deps",
2100
+ precommit: "bun run validate",
1594
2101
  "docker:build": `docker build -t ${context.nameKebabCase} .`,
1595
2102
  "docker:run": `docker run -it -p 3000:3000 ${context.nameKebabCase}`
1596
2103
  },
@@ -1914,63 +2421,46 @@ var DddGenerator = class extends BaseGenerator {
1914
2421
  // ─────────────────────────────────────────────────────────────
1915
2422
  // Bootstrap File Generators
1916
2423
  // ─────────────────────────────────────────────────────────────
1917
- generateBootstrapApp(context) {
2424
+ generateBootstrapApp(_context) {
1918
2425
  return `/**
1919
2426
  * Application Bootstrap
1920
2427
  *
1921
- * Central configuration and initialization of the application.
2428
+ * Central configuration and initialization using the ServiceProvider pattern.
2429
+ *
2430
+ * Lifecycle:
2431
+ * 1. Configure: Load app config and orbits
2432
+ * 2. Boot: Initialize PlanetCore
2433
+ * 3. Register Providers: Bind services to container
2434
+ * 4. Bootstrap: Boot all providers
1922
2435
  */
1923
2436
 
1924
- import { bodySizeLimit, PlanetCore, securityHeaders } from '@gravito/core'
2437
+ import { defineConfig, PlanetCore } from '@gravito/core'
2438
+ import { OrbitAtlas } from '@gravito/atlas'
2439
+ import appConfig from '../../config/app'
1925
2440
  import { registerProviders } from './providers'
1926
2441
  import { registerRoutes } from './routes'
1927
2442
 
1928
2443
  export async function createApp(): Promise<PlanetCore> {
1929
- const core = new PlanetCore({
1930
- config: {
1931
- APP_NAME: '${context.name}',
1932
- },
1933
- })
2444
+ // 1. Configure
2445
+ const config = defineConfig({
2446
+ config: appConfig,
2447
+ orbits: [new OrbitAtlas()],
2448
+ })
1934
2449
 
1935
- const defaultCsp = [
1936
- "default-src 'self'",
1937
- "script-src 'self' 'unsafe-inline'",
1938
- "style-src 'self' 'unsafe-inline'",
1939
- "img-src 'self' data:",
1940
- "object-src 'none'",
1941
- "base-uri 'self'",
1942
- "frame-ancestors 'none'",
1943
- ].join('; ')
1944
- const cspValue = process.env.APP_CSP
1945
- const csp = cspValue === 'false' ? false : (cspValue ?? defaultCsp)
1946
- const hstsMaxAge = Number.parseInt(process.env.APP_HSTS_MAX_AGE ?? '15552000', 10)
1947
- const bodyLimit = Number.parseInt(process.env.APP_BODY_LIMIT ?? '1048576', 10)
1948
- const requireLength = process.env.APP_BODY_REQUIRE_LENGTH === 'true'
1949
-
1950
- core.adapter.use(
1951
- '*',
1952
- securityHeaders({
1953
- contentSecurityPolicy: csp,
1954
- hsts:
1955
- process.env.NODE_ENV === 'production'
1956
- ? { maxAge: Number.isNaN(hstsMaxAge) ? 15552000 : hstsMaxAge, includeSubDomains: true }
1957
- : false,
1958
- })
1959
- )
1960
- if (!Number.isNaN(bodyLimit) && bodyLimit > 0) {
1961
- core.adapter.use('*', bodySizeLimit(bodyLimit, { requireContentLength: requireLength }))
1962
- }
2450
+ // 2. Boot Core
2451
+ const core = await PlanetCore.boot(config)
2452
+ core.registerGlobalErrorHandlers()
1963
2453
 
1964
- // Register all service providers
1965
- await registerProviders(core)
2454
+ // 3. Register Providers
2455
+ await registerProviders(core)
1966
2456
 
1967
- // Bootstrap the application
1968
- await core.bootstrap()
2457
+ // 4. Bootstrap All Providers
2458
+ await core.bootstrap()
1969
2459
 
1970
- // Register routes
1971
- registerRoutes(core.router)
2460
+ // Register routes after bootstrap
2461
+ registerRoutes(core.router)
1972
2462
 
1973
- return core
2463
+ return core
1974
2464
  }
1975
2465
  `;
1976
2466
  }
@@ -1978,19 +2468,48 @@ export async function createApp(): Promise<PlanetCore> {
1978
2468
  return `/**
1979
2469
  * Service Providers Registry
1980
2470
  *
1981
- * Register all module service providers here.
2471
+ * Register all service providers here.
2472
+ * Include both global and module-specific providers.
1982
2473
  */
1983
2474
 
1984
- import type { PlanetCore } from '@gravito/core'
2475
+ import {
2476
+ ServiceProvider,
2477
+ type Container,
2478
+ type PlanetCore,
2479
+ bodySizeLimit,
2480
+ securityHeaders,
2481
+ } from '@gravito/core'
1985
2482
  import { OrderingServiceProvider } from '../Modules/Ordering/Infrastructure/Providers/OrderingServiceProvider'
1986
2483
  import { CatalogServiceProvider } from '../Modules/Catalog/Infrastructure/Providers/CatalogServiceProvider'
1987
2484
 
2485
+ /**
2486
+ * Middleware Provider - Global middleware registration
2487
+ */
2488
+ export class MiddlewareProvider extends ServiceProvider {
2489
+ register(_container: Container): void {}
2490
+
2491
+ boot(core: PlanetCore): void {
2492
+ const isDev = process.env.NODE_ENV !== 'production'
2493
+
2494
+ core.adapter.use('*', securityHeaders({
2495
+ contentSecurityPolicy: isDev ? false : undefined,
2496
+ }))
2497
+
2498
+ core.adapter.use('*', bodySizeLimit(10 * 1024 * 1024))
2499
+
2500
+ core.logger.info('\u{1F6E1}\uFE0F Global middleware registered')
2501
+ }
2502
+ }
2503
+
1988
2504
  export async function registerProviders(core: PlanetCore): Promise<void> {
1989
- // Register module providers
1990
- core.register(new OrderingServiceProvider())
1991
- core.register(new CatalogServiceProvider())
2505
+ // Global Providers
2506
+ core.register(new MiddlewareProvider())
2507
+
2508
+ // Module Providers
2509
+ core.register(new OrderingServiceProvider())
2510
+ core.register(new CatalogServiceProvider())
1992
2511
 
1993
- // Add more providers as needed
2512
+ // Add more providers as needed
1994
2513
  }
1995
2514
  `;
1996
2515
  }
@@ -2104,6 +2623,10 @@ export class ${name}ServiceProvider extends ServiceProvider {
2104
2623
  start: "bun run dist/main.js",
2105
2624
  test: "bun test",
2106
2625
  typecheck: "tsc --noEmit",
2626
+ check: "bun run typecheck && bun run test",
2627
+ "check:deps": "bun run scripts/check-dependencies.ts",
2628
+ validate: "bun run check && bun run check:deps",
2629
+ precommit: "bun run validate",
2107
2630
  "docker:build": `docker build -t ${context.nameKebabCase} .`,
2108
2631
  "docker:run": `docker run -it -p 3000:3000 ${context.nameKebabCase}`
2109
2632
  },
@@ -2544,6 +3067,16 @@ export function report(error: unknown): void {
2544
3067
 
2545
3068
  This project follows **Domain-Driven Design (DDD)** with strategic and tactical patterns.
2546
3069
 
3070
+ ## Service Providers
3071
+
3072
+ Service providers are the central place to configure your application and modules. They follow the ServiceProvider pattern with \`register()\` and \`boot()\` lifecycle methods.
3073
+
3074
+ ### Internal Bootstrapping
3075
+
3076
+ 1. **Bootstrap/app.ts**: Orchestrates the 4-step lifecycle (Configure, Boot, Register, Bootstrap).
3077
+ 2. **Bootstrap/providers.ts**: Central registry for all global and module-specific providers.
3078
+ 3. **Infrastructure/Providers/[Module]ServiceProvider.ts**: Module-specific service registration.
3079
+
2547
3080
  ## Bounded Contexts
2548
3081
 
2549
3082
  \`\`\`
@@ -2681,6 +3214,11 @@ var EnterpriseMvcGenerator = class extends BaseGenerator {
2681
3214
  type: "directory",
2682
3215
  name: "Providers",
2683
3216
  children: [
3217
+ {
3218
+ type: "file",
3219
+ name: "index.ts",
3220
+ content: this.generateProvidersIndex()
3221
+ },
2684
3222
  {
2685
3223
  type: "file",
2686
3224
  name: "AppServiceProvider.ts",
@@ -2688,8 +3226,18 @@ var EnterpriseMvcGenerator = class extends BaseGenerator {
2688
3226
  },
2689
3227
  {
2690
3228
  type: "file",
2691
- name: "RouteServiceProvider.ts",
2692
- content: this.generateRouteServiceProvider(context)
3229
+ name: "DatabaseProvider.ts",
3230
+ content: this.generateDatabaseProvider()
3231
+ },
3232
+ {
3233
+ type: "file",
3234
+ name: "MiddlewareProvider.ts",
3235
+ content: this.generateMiddlewareProvider()
3236
+ },
3237
+ {
3238
+ type: "file",
3239
+ name: "RouteProvider.ts",
3240
+ content: this.generateRouteProvider()
2693
3241
  }
2694
3242
  ]
2695
3243
  },
@@ -3156,6 +3704,135 @@ export class RouteServiceProvider extends ServiceProvider {
3156
3704
  registerRoutes(core.router)
3157
3705
  }
3158
3706
  }
3707
+ `;
3708
+ }
3709
+ // ─────────────────────────────────────────────────────────────
3710
+ // Modern Provider Generators (ServiceProvider Pattern)
3711
+ // ─────────────────────────────────────────────────────────────
3712
+ generateProvidersIndex() {
3713
+ return `/**
3714
+ * Application Service Providers
3715
+ *
3716
+ * Export all providers for easy importing in bootstrap.
3717
+ * Providers are registered in the order they are listed.
3718
+ */
3719
+
3720
+ export { AppServiceProvider } from './AppServiceProvider'
3721
+ export { DatabaseProvider } from './DatabaseProvider'
3722
+ export { MiddlewareProvider } from './MiddlewareProvider'
3723
+ export { RouteProvider } from './RouteProvider'
3724
+ `;
3725
+ }
3726
+ generateDatabaseProvider() {
3727
+ return `/**
3728
+ * Database Service Provider
3729
+ *
3730
+ * Handles database initialization and migrations.
3731
+ *
3732
+ * Lifecycle:
3733
+ * - register(): Bind database config to container
3734
+ * - boot(): Run migrations and seeders
3735
+ */
3736
+
3737
+ import { ServiceProvider, type Container, type PlanetCore } from '@gravito/core'
3738
+ import databaseConfig from '../../config/database'
3739
+
3740
+ export class DatabaseProvider extends ServiceProvider {
3741
+ /**
3742
+ * Register database configuration.
3743
+ */
3744
+ register(_container: Container): void {
3745
+ this.mergeConfig(this.core!.config, 'database', databaseConfig)
3746
+ }
3747
+
3748
+ /**
3749
+ * Initialize database connections.
3750
+ */
3751
+ async boot(core: PlanetCore): Promise<void> {
3752
+ // Database initialization will be handled by Atlas orbit
3753
+ core.logger.info('\u{1F4E6} Database provider booted')
3754
+ }
3755
+ }
3756
+ `;
3757
+ }
3758
+ generateMiddlewareProvider() {
3759
+ return `/**
3760
+ * Middleware Service Provider
3761
+ *
3762
+ * Registers global middleware stack.
3763
+ * Provides a centralized location for middleware configuration.
3764
+ *
3765
+ * Lifecycle:
3766
+ * - register(): N/A (no container bindings)
3767
+ * - boot(): Register global middleware
3768
+ */
3769
+
3770
+ import {
3771
+ ServiceProvider,
3772
+ type Container,
3773
+ type PlanetCore,
3774
+ bodySizeLimit,
3775
+ securityHeaders,
3776
+ } from '@gravito/core'
3777
+
3778
+ export class MiddlewareProvider extends ServiceProvider {
3779
+ /**
3780
+ * No container bindings needed.
3781
+ */
3782
+ register(_container: Container): void {
3783
+ // Middleware doesn't require container bindings
3784
+ }
3785
+
3786
+ /**
3787
+ * Register global middleware stack.
3788
+ */
3789
+ boot(core: PlanetCore): void {
3790
+ const isDev = process.env.NODE_ENV !== 'production'
3791
+
3792
+ // Security Headers
3793
+ core.adapter.use('*', securityHeaders({
3794
+ contentSecurityPolicy: isDev ? false : undefined,
3795
+ }))
3796
+
3797
+ // Body Parser Limits
3798
+ core.adapter.use('*', bodySizeLimit(10 * 1024 * 1024)) // 10MB limit
3799
+
3800
+ core.logger.info('\u{1F6E1}\uFE0F Middleware registered')
3801
+ }
3802
+ }
3803
+ `;
3804
+ }
3805
+ generateRouteProvider() {
3806
+ return `/**
3807
+ * Route Service Provider
3808
+ *
3809
+ * Registers application routes.
3810
+ * Routes are registered in the boot phase after all services are available.
3811
+ *
3812
+ * Lifecycle:
3813
+ * - register(): N/A
3814
+ * - boot(): Register routes
3815
+ */
3816
+
3817
+ import { ServiceProvider, type Container, type PlanetCore } from '@gravito/core'
3818
+ import { registerRoutes } from '../routes'
3819
+
3820
+ export class RouteProvider extends ServiceProvider {
3821
+ /**
3822
+ * No container bindings needed.
3823
+ */
3824
+ register(_container: Container): void {
3825
+ // Routes don't require container bindings
3826
+ }
3827
+
3828
+ /**
3829
+ * Register application routes.
3830
+ */
3831
+ boot(core: PlanetCore): void {
3832
+ registerRoutes(core.router)
3833
+ core.logger.info('\u{1F6E4}\uFE0F Routes registered')
3834
+ }
3835
+ }
3159
3836
  `;
3160
3837
  }
3161
3838
  generateExceptionHandler() {
@@ -3204,74 +3881,75 @@ export const dontReport: string[] = [
3204
3881
  }
3205
3882
  generateBootstrap(context) {
3206
3883
  const spectrumImport = context.withSpectrum ? "import { SpectrumOrbit } from '@gravito/spectrum'\n" : "";
3207
- const spectrumOrbit = context.withSpectrum ? `
3208
- // Enable Debug Dashboard
3209
- if (process.env.APP_DEBUG === 'true') {
3210
- await core.orbit(new SpectrumOrbit())
3211
- }
3212
- ` : "";
3884
+ const spectrumOrbit = context.withSpectrum ? " new SpectrumOrbit()," : "";
3213
3885
  return `/**
3214
3886
  * Application Bootstrap
3215
3887
  *
3216
- * This is the entry point for your application.
3217
- * It initializes the core and registers all providers.
3888
+ * The entry point for your Gravito application.
3889
+ * Uses the ServiceProvider pattern for modular, maintainable initialization.
3890
+ *
3891
+ * Lifecycle:
3892
+ * 1. Configure: Load app config and orbits
3893
+ * 2. Boot: Initialize PlanetCore
3894
+ * 3. Register Providers: Bind services to container
3895
+ * 4. Bootstrap: Boot all providers
3896
+ *
3897
+ * @module bootstrap
3218
3898
  */
3219
3899
 
3220
- import { bodySizeLimit, PlanetCore, securityHeaders } from '@gravito/core'
3900
+ import { defineConfig, PlanetCore } from '@gravito/core'
3221
3901
  import { OrbitAtlas } from '@gravito/atlas'
3222
- import databaseConfig from '../config/database'
3223
- ${spectrumImport}import { AppServiceProvider } from './Providers/AppServiceProvider'
3224
- import { RouteServiceProvider } from './Providers/RouteServiceProvider'
3225
-
3226
- // Load environment variables
3227
- // Bun automatically loads .env
3228
-
3229
- // Create application core
3230
- const core = new PlanetCore({
3231
- config: {
3232
- APP_NAME: process.env.APP_NAME ?? '${context.name}',
3233
- database: databaseConfig,
3234
- },
3235
- })
3236
- const defaultCsp = [
3237
- "default-src 'self'",
3238
- "script-src 'self' 'unsafe-inline'",
3239
- "style-src 'self' 'unsafe-inline'",
3240
- "img-src 'self' data:",
3241
- "object-src 'none'",
3242
- "base-uri 'self'",
3243
- "frame-ancestors 'none'",
3244
- ].join('; ')
3245
- const cspValue = process.env.APP_CSP
3246
- const csp = cspValue === 'false' ? false : (cspValue ?? defaultCsp)
3247
- const hstsMaxAge = Number.parseInt(process.env.APP_HSTS_MAX_AGE ?? '15552000', 10)
3248
- const bodyLimit = Number.parseInt(process.env.APP_BODY_LIMIT ?? '1048576', 10)
3249
- const requireLength = process.env.APP_BODY_REQUIRE_LENGTH === 'true'
3250
-
3251
- core.adapter.use(
3252
- '*',
3253
- securityHeaders({
3254
- contentSecurityPolicy: csp,
3255
- hsts:
3256
- process.env.NODE_ENV === 'production'
3257
- ? { maxAge: Number.isNaN(hstsMaxAge) ? 15552000 : hstsMaxAge, includeSubDomains: true }
3258
- : false,
3259
- })
3260
- )
3261
- if (!Number.isNaN(bodyLimit) && bodyLimit > 0) {
3262
- core.adapter.use('*', bodySizeLimit(bodyLimit, { requireContentLength: requireLength }))
3263
- }
3902
+ import appConfig from '../config/app'
3903
+ ${spectrumImport}import {
3904
+ AppServiceProvider,
3905
+ DatabaseProvider,
3906
+ MiddlewareProvider,
3907
+ RouteProvider,
3908
+ } from './Providers'
3264
3909
 
3265
- await core.orbit(new OrbitAtlas())
3910
+ /**
3911
+ * Bootstrap the application with service providers.
3912
+ *
3913
+ * @returns The booted PlanetCore instance
3914
+ */
3915
+ export async function bootstrap() {
3916
+ // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
3917
+ // 1. Configure
3918
+ // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
3919
+ const config = defineConfig({
3920
+ config: appConfig,
3921
+ orbits: [
3922
+ new OrbitAtlas(),
3266
3923
  ${spectrumOrbit}
3267
- // Register service providers
3268
- core.register(new AppServiceProvider())
3269
- core.register(new RouteServiceProvider())
3924
+ ],
3925
+ })
3270
3926
 
3271
- // Bootstrap the application
3272
- await core.bootstrap()
3927
+ // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
3928
+ // 2. Boot Core
3929
+ // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
3930
+ const core = await PlanetCore.boot(config)
3931
+ core.registerGlobalErrorHandlers()
3932
+
3933
+ // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
3934
+ // 3. Register Providers
3935
+ // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
3936
+ core.register(new AppServiceProvider())
3937
+ core.register(new DatabaseProvider())
3938
+ core.register(new MiddlewareProvider())
3939
+ core.register(new RouteProvider())
3940
+
3941
+ // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
3942
+ // 4. Bootstrap All Providers
3943
+ // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
3944
+ await core.bootstrap()
3945
+
3946
+ return core
3947
+ }
3273
3948
 
3274
- // Export for Bun.serve()
3949
+ // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
3950
+ // Application Entry Point
3951
+ // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
3952
+ const core = await bootstrap()
3275
3953
  export default core.liftoff()
3276
3954
  `;
3277
3955
  }
@@ -3698,7 +4376,9 @@ export class ${name}ServiceProvider extends ServiceProvider {
3698
4376
  scripts: {
3699
4377
  build: "tsup src/index.ts --format cjs,esm --dts",
3700
4378
  test: "bun test",
3701
- typecheck: "tsc --noEmit"
4379
+ typecheck: "tsc --noEmit",
4380
+ check: "bun run typecheck && bun run test",
4381
+ validate: "bun run check"
3702
4382
  },
3703
4383
  dependencies: {
3704
4384
  "@gravito/core": depVersion,
@@ -3996,10 +4676,25 @@ var ActionDomainGenerator = class extends BaseGenerator {
3996
4676
  type: "directory",
3997
4677
  name: "providers",
3998
4678
  children: [
4679
+ {
4680
+ type: "file",
4681
+ name: "index.ts",
4682
+ content: this.generateProvidersIndex()
4683
+ },
3999
4684
  {
4000
4685
  type: "file",
4001
4686
  name: "AppServiceProvider.ts",
4002
4687
  content: this.generateAppServiceProvider(context)
4688
+ },
4689
+ {
4690
+ type: "file",
4691
+ name: "MiddlewareProvider.ts",
4692
+ content: this.generateMiddlewareProvider()
4693
+ },
4694
+ {
4695
+ type: "file",
4696
+ name: "RouteProvider.ts",
4697
+ content: this.generateRouteProvider()
4003
4698
  }
4004
4699
  ]
4005
4700
  },
@@ -4177,7 +4872,7 @@ export function registerApiRoutes(router: Router) {
4177
4872
  import { ServiceProvider, type Container, type PlanetCore } from '@gravito/core'
4178
4873
 
4179
4874
  export class AppServiceProvider extends ServiceProvider {
4180
- register(container: Container): void {
4875
+ register(_container: Container): void {
4181
4876
  // Register global services here
4182
4877
  }
4183
4878
 
@@ -4187,47 +4882,140 @@ export class AppServiceProvider extends ServiceProvider {
4187
4882
  }
4188
4883
  `;
4189
4884
  }
4190
- generateBootstrap(context) {
4885
+ generateProvidersIndex() {
4191
4886
  return `/**
4192
- * Application Entry Point
4887
+ * Application Service Providers
4193
4888
  */
4194
4889
 
4195
- import { PlanetCore, securityHeaders, bodySizeLimit } from '@gravito/core'
4196
- import { OrbitAtlas } from '@gravito/atlas'
4197
- import databaseConfig from '../config/database'
4198
- import { AppServiceProvider } from './providers/AppServiceProvider'
4199
- import { registerApiRoutes } from './routes/api'
4200
-
4201
- // Initialize Core
4202
- const core = new PlanetCore({
4203
- config: {
4204
- APP_NAME: '${context.name}',
4205
- database: databaseConfig
4206
- },
4207
- })
4890
+ export { AppServiceProvider } from './AppServiceProvider'
4891
+ export { MiddlewareProvider } from './MiddlewareProvider'
4892
+ export { RouteProvider } from './RouteProvider'
4893
+ `;
4894
+ }
4895
+ generateMiddlewareProvider() {
4896
+ return `/**
4897
+ * Middleware Service Provider
4898
+ */
4208
4899
 
4209
- // Middleware
4210
- core.adapter.use('*', securityHeaders())
4211
- core.adapter.use('*', bodySizeLimit(1024 * 1024)) // 1MB
4900
+ import {
4901
+ ServiceProvider,
4902
+ type Container,
4903
+ type PlanetCore,
4904
+ bodySizeLimit,
4905
+ securityHeaders,
4906
+ } from '@gravito/core'
4212
4907
 
4213
- // Install Orbits
4214
- await core.orbit(new OrbitAtlas())
4908
+ export class MiddlewareProvider extends ServiceProvider {
4909
+ register(_container: Container): void {}
4215
4910
 
4216
- // Service Providers
4217
- core.register(new AppServiceProvider())
4911
+ boot(core: PlanetCore): void {
4912
+ core.adapter.use('*', securityHeaders())
4913
+ core.adapter.use('*', bodySizeLimit(1024 * 1024))
4914
+ core.logger.info('\u{1F6E1}\uFE0F Middleware registered')
4915
+ }
4916
+ }
4917
+ `;
4918
+ }
4919
+ generateRouteProvider() {
4920
+ return `/**
4921
+ * Route Service Provider
4922
+ */
4923
+
4924
+ import { ServiceProvider, type Container, type PlanetCore } from '@gravito/core'
4925
+ import { registerApiRoutes } from '../routes/api'
4926
+
4927
+ export class RouteProvider extends ServiceProvider {
4928
+ register(_container: Container): void {}
4929
+
4930
+ boot(core: PlanetCore): void {
4931
+ registerApiRoutes(core.router)
4932
+ core.logger.info('\u{1F6E4}\uFE0F Routes registered')
4933
+ }
4934
+ }
4935
+ `;
4936
+ }
4937
+ generateBootstrap(_context) {
4938
+ return `/**
4939
+ * Application Bootstrap
4940
+ *
4941
+ * Uses the ServiceProvider pattern for modular initialization.
4942
+ */
4943
+
4944
+ import { defineConfig, PlanetCore } from '@gravito/core'
4945
+ import { OrbitAtlas } from '@gravito/atlas'
4946
+ import appConfig from '../config/app'
4947
+ import {
4948
+ AppServiceProvider,
4949
+ MiddlewareProvider,
4950
+ RouteProvider,
4951
+ } from './providers'
4952
+
4953
+ export async function bootstrap() {
4954
+ const config = defineConfig({
4955
+ config: appConfig,
4956
+ orbits: [new OrbitAtlas()],
4957
+ })
4958
+
4959
+ const core = await PlanetCore.boot(config)
4960
+ core.registerGlobalErrorHandlers()
4218
4961
 
4219
- // Bootstrap
4220
- await core.bootstrap()
4962
+ core.register(new AppServiceProvider())
4963
+ core.register(new MiddlewareProvider())
4964
+ core.register(new RouteProvider())
4221
4965
 
4222
- // Routes
4223
- registerApiRoutes(core.router)
4966
+ await core.bootstrap()
4224
4967
 
4225
- // Liftoff
4968
+ return core
4969
+ }
4970
+
4971
+ const core = await bootstrap()
4226
4972
  export default core.liftoff()
4227
4973
  `;
4228
4974
  }
4229
4975
  generateArchitectureDoc(context) {
4230
- 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';
4976
+ return `# ${context.name} - Action Domain Architecture
4977
+
4978
+ ## Overview
4979
+
4980
+ This project uses the **Action Domain** pattern, designed for high-clarity API implementations.
4981
+
4982
+ ## Service Providers
4983
+
4984
+ Service providers are the central place to configure your application. They follow the ServiceProvider pattern with \`register()\` and \`boot()\` lifecycle methods.
4985
+
4986
+ ## Directory Structure
4987
+
4988
+ \`\`\`
4989
+ src/
4990
+ \u251C\u2500\u2500 actions/ # Single Responsibility Business Logic
4991
+ \u2502 \u251C\u2500\u2500 Action.ts # Base Action class
4992
+ \u2502 \u2514\u2500\u2500 [Domain]/ # Domain-specific actions
4993
+ \u251C\u2500\u2500 controllers/ # HTTP Request Handlers
4994
+ \u2502 \u2514\u2500\u2500 api/v1/ # API Controllers
4995
+ \u251C\u2500\u2500 types/ # TypeScript Definitions
4996
+ \u251C\u2500\u2500 repositories/ # Data Access Layer
4997
+ \u251C\u2500\u2500 routes/ # Route Definitions
4998
+ \u251C\u2500\u2500 providers/ # Service Providers
4999
+ \u2514\u2500\u2500 config/ # Configuration
5000
+ \`\`\`
5001
+
5002
+ ## Core Concepts
5003
+
5004
+ ### Actions
5005
+ Every business operation is an "Action". An action:
5006
+ - Does ONE thing.
5007
+ - Takes specific input.
5008
+ - Returns specific output.
5009
+ - Is framework-agnostic (ideally).
5010
+
5011
+ ### Controllers
5012
+ Controllers are thin. They:
5013
+ 1. Parse the request.
5014
+ 2. Instantiate/Call the Action.
5015
+ 3. Return the response.
5016
+
5017
+ Created with \u2764\uFE0F using Gravito Framework
5018
+ `;
4231
5019
  }
4232
5020
  generatePackageJson(context) {
4233
5021
  const pkg = {
@@ -4239,7 +5027,11 @@ export default core.liftoff()
4239
5027
  build: "bun build ./src/bootstrap.ts --outdir ./dist --target bun",
4240
5028
  start: "bun run dist/bootstrap.js",
4241
5029
  test: "bun test",
4242
- typecheck: "tsc --noEmit"
5030
+ typecheck: "tsc --noEmit",
5031
+ check: "bun run typecheck && bun run test",
5032
+ "check:deps": "bun run scripts/check-dependencies.ts",
5033
+ validate: "bun run check && bun run check:deps",
5034
+ precommit: "bun run validate"
4243
5035
  },
4244
5036
  dependencies: {
4245
5037
  "@gravito/core": "workspace:*",