@gravito/pulse 1.0.0 → 2.0.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.
Files changed (2) hide show
  1. package/dist/index.js +1694 -271
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -81110,6 +81110,27 @@ class BaseGenerator {
81110
81110
  await this.writeFile(context.targetDir, "Dockerfile", this.generateDockerfile(context));
81111
81111
  await this.writeFile(context.targetDir, ".dockerignore", this.generateDockerIgnore());
81112
81112
  await this.writeFile(context.targetDir, "ARCHITECTURE.md", this.generateArchitectureDoc(context));
81113
+ await this.generateCheckScripts(context);
81114
+ await this.generateSkills(context);
81115
+ }
81116
+ async generateSkills(context) {
81117
+ const skillsDir = path2.resolve(this.config.templatesDir, "skills");
81118
+ const targetSkillsDir = path2.join(".skills");
81119
+ try {
81120
+ await fs2.access(skillsDir);
81121
+ } catch {
81122
+ return;
81123
+ }
81124
+ const files = await walk(skillsDir);
81125
+ for (const filePath of files) {
81126
+ const relativePath = path2.relative(skillsDir, filePath);
81127
+ const targetPath = path2.join(targetSkillsDir, relativePath);
81128
+ let content = await fs2.readFile(filePath, "utf-8");
81129
+ try {
81130
+ content = this.stubGenerator.render(content, context);
81131
+ } catch {}
81132
+ await this.writeFile(context.targetDir, targetPath, content);
81133
+ }
81113
81134
  }
81114
81135
  async applyOverlays(context) {
81115
81136
  const profile = context.profile;
@@ -81181,6 +81202,10 @@ class BaseGenerator {
81181
81202
  start: "bun run dist/bootstrap.js",
81182
81203
  test: "bun test",
81183
81204
  typecheck: "tsc --noEmit",
81205
+ check: "bun run typecheck && bun run test",
81206
+ "check:deps": "bun run scripts/check-dependencies.ts",
81207
+ validate: "bun run check && bun run check:deps",
81208
+ precommit: "bun run validate",
81184
81209
  "docker:build": `docker build -t ${context.nameKebabCase} .`,
81185
81210
  "docker:run": `docker run -it -p 3000:3000 ${context.nameKebabCase}`
81186
81211
  },
@@ -81418,6 +81443,411 @@ coverage/
81418
81443
  };
81419
81444
  return JSON.stringify(config, null, 2);
81420
81445
  }
81446
+ async generateCheckScripts(context) {
81447
+ const scriptsDir = path2.resolve(context.targetDir, "scripts");
81448
+ await fs2.mkdir(scriptsDir, { recursive: true });
81449
+ await this.writeFile(scriptsDir, "check-dependencies.ts", this.generateCheckDependenciesScript());
81450
+ await this.writeFile(scriptsDir, "check.sh", this.generateCheckShellScript());
81451
+ await this.writeFile(scriptsDir, "pre-commit.sh", this.generatePreCommitScript());
81452
+ await this.writeFile(context.targetDir, "CHECK_SYSTEM.md", this.generateCheckSystemDoc(context));
81453
+ }
81454
+ generateCheckDependenciesScript() {
81455
+ return `/**
81456
+ * \u76F8\u4F9D\u5957\u4EF6\u7248\u672C\u6AA2\u67E5\u8173\u672C
81457
+ *
81458
+ * \u6AA2\u67E5 package.json \u4E2D\u7684\u5957\u4EF6\u662F\u5426\u70BA\u6700\u65B0\u7A69\u5B9A\u7248\u672C
81459
+ * \u4E26\u63D0\u4F9B\u66F4\u65B0\u5EFA\u8B70
81460
+ */
81461
+
81462
+ import { readFileSync } from 'fs'
81463
+ import { join } from 'path'
81464
+
81465
+ interface PackageJson {
81466
+ dependencies?: Record<string, string>
81467
+ devDependencies?: Record<string, string>
81468
+ }
81469
+
81470
+ interface PackageInfo {
81471
+ name: string
81472
+ current: string
81473
+ latest: string
81474
+ outdated: boolean
81475
+ }
81476
+
81477
+ const colors = {
81478
+ reset: '\\x1b[0m',
81479
+ green: '\\x1b[32m',
81480
+ yellow: '\\x1b[33m',
81481
+ red: '\\x1b[31m',
81482
+ blue: '\\x1b[36m',
81483
+ }
81484
+
81485
+ function log(message: string, color: keyof typeof colors = 'reset') {
81486
+ console.log(\`\${colors[color]}\${message}\${colors.reset}\`)
81487
+ }
81488
+
81489
+ async function getLatestVersion(packageName: string): Promise<string | null> {
81490
+ try {
81491
+ const response = await fetch(\`https://registry.npmjs.org/\${packageName}/latest\`)
81492
+ if (!response.ok) return null
81493
+ const data = await response.json()
81494
+ return data.version
81495
+ } catch {
81496
+ return null
81497
+ }
81498
+ }
81499
+
81500
+ function parseVersion(version: string): string {
81501
+ // \u79FB\u9664 ^, ~, >= \u7B49\u524D\u7DB4
81502
+ return version.replace(/^[\\^~>=<]/, '')
81503
+ }
81504
+
81505
+ async function checkPackage(
81506
+ name: string,
81507
+ currentVersion: string
81508
+ ): Promise<PackageInfo | null> {
81509
+ // \u8DF3\u904E\u672C\u5730\u9023\u7D50\u7684\u5957\u4EF6
81510
+ if (currentVersion.startsWith('link:') || currentVersion.startsWith('workspace:')) {
81511
+ return null
81512
+ }
81513
+
81514
+ const current = parseVersion(currentVersion)
81515
+ const latest = await getLatestVersion(name)
81516
+
81517
+ if (!latest) {
81518
+ return null
81519
+ }
81520
+
81521
+ return {
81522
+ name,
81523
+ current,
81524
+ latest,
81525
+ outdated: current !== latest,
81526
+ }
81527
+ }
81528
+
81529
+ async function main() {
81530
+ log('\\n=== \u76F8\u4F9D\u5957\u4EF6\u7248\u672C\u6AA2\u67E5 ===\\n', 'blue')
81531
+
81532
+ const packageJsonPath = join(process.cwd(), 'package.json')
81533
+ const packageJson: PackageJson = JSON.parse(
81534
+ readFileSync(packageJsonPath, 'utf-8')
81535
+ )
81536
+
81537
+ const allDependencies = {
81538
+ ...packageJson.dependencies,
81539
+ ...packageJson.devDependencies,
81540
+ }
81541
+
81542
+ log(\`\u6AA2\u67E5 \${Object.keys(allDependencies).length} \u500B\u5957\u4EF6...\\n\`, 'yellow')
81543
+
81544
+ const results: PackageInfo[] = []
81545
+ const outdated: PackageInfo[] = []
81546
+ const upToDate: PackageInfo[] = []
81547
+
81548
+ // \u6AA2\u67E5\u6240\u6709\u5957\u4EF6
81549
+ for (const [name, version] of Object.entries(allDependencies)) {
81550
+ const info = await checkPackage(name, version)
81551
+ if (info) {
81552
+ results.push(info)
81553
+ if (info.outdated) {
81554
+ outdated.push(info)
81555
+ } else {
81556
+ upToDate.push(info)
81557
+ }
81558
+ }
81559
+ }
81560
+
81561
+ // \u986F\u793A\u7D50\u679C
81562
+ if (upToDate.length > 0) {
81563
+ log(\`\\n\u2713 \u5DF2\u662F\u6700\u65B0\u7248\u672C (\${upToDate.length}):\`, 'green')
81564
+ upToDate.forEach((pkg) => {
81565
+ log(\` \${pkg.name}: \${pkg.current}\`, 'green')
81566
+ })
81567
+ }
81568
+
81569
+ if (outdated.length > 0) {
81570
+ log(\`\\n\u26A0 \u9700\u8981\u66F4\u65B0 (\${outdated.length}):\`, 'yellow')
81571
+ outdated.forEach((pkg) => {
81572
+ log(\` \${pkg.name}: \${pkg.current} \u2192 \${pkg.latest}\`, 'yellow')
81573
+ })
81574
+ }
81575
+
81576
+ // \u7E3D\u7D50
81577
+ log('\\n=== \u6AA2\u67E5\u7D50\u679C ===', 'blue')
81578
+ log(\`\u7E3D\u8A08: \${results.length} \u500B\u5957\u4EF6\`, 'blue')
81579
+ log(\`\u6700\u65B0: \${upToDate.length} \u500B\`, 'green')
81580
+ log(\`\u9700\u66F4\u65B0: \${outdated.length} \u500B\`, outdated.length > 0 ? 'yellow' : 'green')
81581
+
81582
+ // \u5982\u679C\u6709\u9700\u8981\u66F4\u65B0\u7684\u5957\u4EF6\uFF0C\u8FD4\u56DE\u975E\u96F6\u9000\u51FA\u78BC
81583
+ if (outdated.length > 0) {
81584
+ log('\\n\u5EFA\u8B70\u57F7\u884C\u4EE5\u4E0B\u547D\u4EE4\u66F4\u65B0\u5957\u4EF6\uFF1A', 'yellow')
81585
+ log(' bun update', 'yellow')
81586
+ process.exit(1)
81587
+ } else {
81588
+ log('\\n\u2713 \u6240\u6709\u5957\u4EF6\u90FD\u662F\u6700\u65B0\u7248\u672C\uFF01', 'green')
81589
+ process.exit(0)
81590
+ }
81591
+ }
81592
+
81593
+ main().catch((error) => {
81594
+ log(\`\\n\u932F\u8AA4: \${error.message}\`, 'red')
81595
+ process.exit(1)
81596
+ })
81597
+ `;
81598
+ }
81599
+ generateCheckShellScript() {
81600
+ return `#!/bin/bash
81601
+
81602
+ # \u5C08\u6848\u6AA2\u67E5\u8173\u672C
81603
+ # \u57F7\u884C\u6240\u6709\u5FC5\u8981\u7684\u6AA2\u67E5\uFF1A\u985E\u578B\u6AA2\u67E5\u3001\u6E2C\u8A66\u3001\u4F9D\u8CF4\u6AA2\u67E5\u7B49
81604
+
81605
+ set -e
81606
+
81607
+ # \u984F\u8272\u5B9A\u7FA9
81608
+ GREEN='\\033[0;32m'
81609
+ YELLOW='\\033[1;33m'
81610
+ RED='\\033[0;31m'
81611
+ BLUE='\\033[0;34m'
81612
+ NC='\\033[0m' # No Color
81613
+
81614
+ echo -e "\${BLUE}=== \u5C08\u6848\u6AA2\u67E5 ===\${NC}\\n"
81615
+
81616
+ # \u6AA2\u67E5\u662F\u5426\u5728\u6B63\u78BA\u7684\u76EE\u9304
81617
+ if [ ! -f "package.json" ]; then
81618
+ echo -e "\${RED}\u932F\u8AA4: \u8ACB\u5728\u5C08\u6848\u6839\u76EE\u9304\u57F7\u884C\u6B64\u8173\u672C\${NC}"
81619
+ exit 1
81620
+ fi
81621
+
81622
+ # \u6AA2\u67E5 Bun \u662F\u5426\u5B89\u88DD
81623
+ if ! command -v bun &> /dev/null; then
81624
+ echo -e "\${RED}\u932F\u8AA4: \u672A\u627E\u5230 bun\uFF0C\u8ACB\u5148\u5B89\u88DD Bun\${NC}"
81625
+ exit 1
81626
+ fi
81627
+
81628
+ # 1. \u985E\u578B\u6AA2\u67E5
81629
+ echo -e "\${YELLOW}[1/3] \u57F7\u884C\u985E\u578B\u6AA2\u67E5...\${NC}"
81630
+ if bun run typecheck; then
81631
+ echo -e "\${GREEN}\u2713 \u985E\u578B\u6AA2\u67E5\u901A\u904E\${NC}\\n"
81632
+ else
81633
+ echo -e "\${RED}\u2717 \u985E\u578B\u6AA2\u67E5\u5931\u6557\${NC}"
81634
+ exit 1
81635
+ fi
81636
+
81637
+ # 2. \u57F7\u884C\u6E2C\u8A66
81638
+ echo -e "\${YELLOW}[2/3] \u57F7\u884C\u6E2C\u8A66...\${NC}"
81639
+ if bun test; then
81640
+ echo -e "\${GREEN}\u2713 \u6E2C\u8A66\u901A\u904E\${NC}\\n"
81641
+ else
81642
+ echo -e "\${RED}\u2717 \u6E2C\u8A66\u5931\u6557\${NC}"
81643
+ exit 1
81644
+ fi
81645
+
81646
+ # 3. \u6AA2\u67E5\u4F9D\u8CF4\u7248\u672C\uFF08\u53EF\u9078\uFF0C\u56E0\u70BA\u9700\u8981\u7DB2\u8DEF\u9023\u7DDA\uFF09
81647
+ echo -e "\${YELLOW}[3/3] \u6AA2\u67E5\u4F9D\u8CF4\u7248\u672C...\${NC}"
81648
+ if bun run check:deps; then
81649
+ echo -e "\${GREEN}\u2713 \u4F9D\u8CF4\u6AA2\u67E5\u5B8C\u6210\${NC}\\n"
81650
+ else
81651
+ 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"
81652
+ fi
81653
+
81654
+ echo -e "\${GREEN}=== \u6240\u6709\u6AA2\u67E5\u5B8C\u6210 ===\${NC}"
81655
+ `;
81656
+ }
81657
+ generatePreCommitScript() {
81658
+ return `#!/bin/bash
81659
+
81660
+ # Pre-commit Hook
81661
+ # \u5728 git commit \u524D\u81EA\u52D5\u57F7\u884C\u6AA2\u67E5
81662
+ #
81663
+ # \u5B89\u88DD\u65B9\u5F0F\uFF1A
81664
+ # ln -s ../../scripts/pre-commit.sh .git/hooks/pre-commit
81665
+ # \u6216
81666
+ # cp scripts/pre-commit.sh .git/hooks/pre-commit
81667
+ # chmod +x .git/hooks/pre-commit
81668
+
81669
+ set -e
81670
+
81671
+ # \u984F\u8272\u5B9A\u7FA9
81672
+ GREEN='\\033[0;32m'
81673
+ YELLOW='\\033[1;33m'
81674
+ RED='\\033[0;31m'
81675
+ BLUE='\\033[0;34m'
81676
+ NC='\\033[0m' # No Color
81677
+
81678
+ echo -e "\${BLUE}=== Pre-commit \u6AA2\u67E5 ===\${NC}\\n"
81679
+
81680
+ # \u5207\u63DB\u5230\u5C08\u6848\u6839\u76EE\u9304
81681
+ cd "$(git rev-parse --show-toplevel)"
81682
+
81683
+ # \u6AA2\u67E5\u662F\u5426\u5728\u6B63\u78BA\u7684\u76EE\u9304
81684
+ if [ ! -f "package.json" ]; then
81685
+ echo -e "\${RED}\u932F\u8AA4: \u627E\u4E0D\u5230 package.json\${NC}"
81686
+ exit 1
81687
+ fi
81688
+
81689
+ # \u6AA2\u67E5 Bun \u662F\u5426\u5B89\u88DD
81690
+ if ! command -v bun &> /dev/null; then
81691
+ echo -e "\${RED}\u932F\u8AA4: \u672A\u627E\u5230 bun\uFF0C\u8ACB\u5148\u5B89\u88DD Bun\${NC}"
81692
+ exit 1
81693
+ fi
81694
+
81695
+ # 1. \u985E\u578B\u6AA2\u67E5\uFF08\u5FEB\u901F\u6AA2\u67E5\uFF09
81696
+ echo -e "\${YELLOW}[1/2] \u57F7\u884C\u985E\u578B\u6AA2\u67E5...\${NC}"
81697
+ if bun run typecheck; then
81698
+ echo -e "\${GREEN}\u2713 \u985E\u578B\u6AA2\u67E5\u901A\u904E\${NC}\\n"
81699
+ else
81700
+ echo -e "\${RED}\u2717 \u985E\u578B\u6AA2\u67E5\u5931\u6557\${NC}"
81701
+ echo -e "\${YELLOW}\u63D0\u793A: \u8ACB\u4FEE\u6B63\u985E\u578B\u932F\u8AA4\u5F8C\u518D\u63D0\u4EA4\${NC}"
81702
+ exit 1
81703
+ fi
81704
+
81705
+ # 2. \u57F7\u884C\u6E2C\u8A66\uFF08\u53EF\u9078\uFF0C\u5982\u679C\u6E2C\u8A66\u6642\u9593\u8F03\u9577\u53EF\u4EE5\u8A3B\u89E3\u6389\uFF09
81706
+ echo -e "\${YELLOW}[2/2] \u57F7\u884C\u6E2C\u8A66...\${NC}"
81707
+ if bun test; then
81708
+ echo -e "\${GREEN}\u2713 \u6E2C\u8A66\u901A\u904E\${NC}\\n"
81709
+ else
81710
+ echo -e "\${RED}\u2717 \u6E2C\u8A66\u5931\u6557\${NC}"
81711
+ echo -e "\${YELLOW}\u63D0\u793A: \u8ACB\u4FEE\u6B63\u6E2C\u8A66\u932F\u8AA4\u5F8C\u518D\u63D0\u4EA4\${NC}"
81712
+ exit 1
81713
+ fi
81714
+
81715
+ echo -e "\${GREEN}=== Pre-commit \u6AA2\u67E5\u901A\u904E ===\${NC}\\n"
81716
+ `;
81717
+ }
81718
+ generateCheckSystemDoc(context) {
81719
+ return `# \u5C08\u6848\u6AA2\u67E5\u7CFB\u7D71
81720
+
81721
+ \u672C\u5C08\u6848\u5DF2\u5EFA\u7ACB\u5B8C\u6574\u7684\u672C\u5730\u6AA2\u67E5\u6A5F\u5236\uFF0C\u7121\u9700\u4F9D\u8CF4 GitHub CI\u3002
81722
+
81723
+ ## \u5FEB\u901F\u958B\u59CB
81724
+
81725
+ ### \u57F7\u884C\u5B8C\u6574\u6AA2\u67E5
81726
+ \`\`\`bash
81727
+ bun run validate
81728
+ \`\`\`
81729
+
81730
+ ### \u57F7\u884C\u55AE\u9805\u6AA2\u67E5
81731
+ \`\`\`bash
81732
+ # \u985E\u578B\u6AA2\u67E5
81733
+ bun run typecheck
81734
+
81735
+ # \u6E2C\u8A66
81736
+ bun run test
81737
+
81738
+ # \u4F9D\u8CF4\u7248\u672C\u6AA2\u67E5
81739
+ bun run check:deps
81740
+ \`\`\`
81741
+
81742
+ ## \u53EF\u7528\u547D\u4EE4
81743
+
81744
+ ### Package.json \u8173\u672C
81745
+
81746
+ | \u547D\u4EE4 | \u8AAA\u660E |
81747
+ |------|------|
81748
+ | \`bun run typecheck\` | TypeScript \u985E\u578B\u6AA2\u67E5 |
81749
+ | \`bun run test\` | \u57F7\u884C\u6240\u6709\u6E2C\u8A66 |
81750
+ | \`bun run check\` | \u985E\u578B\u6AA2\u67E5 + \u6E2C\u8A66 |
81751
+ | \`bun run check:deps\` | \u6AA2\u67E5\u4F9D\u8CF4\u7248\u672C |
81752
+ | \`bun run validate\` | \u5B8C\u6574\u9A57\u8B49\uFF08\u985E\u578B + \u6E2C\u8A66 + \u4F9D\u8CF4\uFF09 |
81753
+ | \`bun run precommit\` | \u7B49\u540C\u65BC \`validate\` |
81754
+
81755
+ ### Shell \u8173\u672C
81756
+
81757
+ | \u8173\u672C | \u8AAA\u660E |
81758
+ |------|------|
81759
+ | \`./scripts/check.sh\` | \u5B8C\u6574\u5C08\u6848\u6AA2\u67E5\uFF08Shell \u7248\u672C\uFF09 |
81760
+ | \`./scripts/pre-commit.sh\` | Pre-commit hook \u8173\u672C |
81761
+
81762
+ ## Pre-commit Hook\uFF08\u63A8\u85A6\uFF09
81763
+
81764
+ \u5B89\u88DD pre-commit hook \u5F8C\uFF0C\u6BCF\u6B21 \`git commit\` \u524D\u6703\u81EA\u52D5\u57F7\u884C\u6AA2\u67E5\uFF1A
81765
+
81766
+ \`\`\`bash
81767
+ # \u5B89\u88DD pre-commit hook
81768
+ ln -s ../../scripts/pre-commit.sh .git/hooks/pre-commit
81769
+
81770
+ # \u6216\u4F7F\u7528\u8907\u88FD\u65B9\u5F0F
81771
+ cp scripts/pre-commit.sh .git/hooks/pre-commit
81772
+ chmod +x .git/hooks/pre-commit
81773
+ \`\`\`
81774
+
81775
+ **\u529F\u80FD\uFF1A**
81776
+ - \u2705 \u81EA\u52D5\u57F7\u884C\u985E\u578B\u6AA2\u67E5
81777
+ - \u2705 \u81EA\u52D5\u57F7\u884C\u6E2C\u8A66
81778
+ - \u274C \u6AA2\u67E5\u5931\u6557\u6642\u963B\u6B62\u63D0\u4EA4
81779
+
81780
+ **\u8DF3\u904E\u6AA2\u67E5\uFF08\u4E0D\u63A8\u85A6\uFF09\uFF1A**
81781
+ \`\`\`bash
81782
+ git commit --no-verify -m "\u7DCA\u6025\u4FEE\u5FA9"
81783
+ \`\`\`
81784
+
81785
+ ## \u6AA2\u67E5\u9805\u76EE
81786
+
81787
+ ### 1. \u985E\u578B\u6AA2\u67E5
81788
+ - \u4F7F\u7528 \`tsc --noEmit\` \u6AA2\u67E5 TypeScript \u985E\u578B
81789
+ - \u78BA\u4FDD\u6C92\u6709\u985E\u578B\u932F\u8AA4
81790
+
81791
+ ### 2. \u6E2C\u8A66
81792
+ - \u57F7\u884C\u6240\u6709\u55AE\u5143\u6E2C\u8A66\u548C\u6574\u5408\u6E2C\u8A66
81793
+ - \u78BA\u4FDD\u6E2C\u8A66\u901A\u904E
81794
+
81795
+ ### 3. \u4F9D\u8CF4\u6AA2\u67E5\uFF08\u53EF\u9078\uFF09
81796
+ - \u6AA2\u67E5\u5957\u4EF6\u7248\u672C\u662F\u5426\u70BA\u6700\u65B0
81797
+ - \u63D0\u4F9B\u66F4\u65B0\u5EFA\u8B70
81798
+ - \u9700\u8981\u7DB2\u8DEF\u9023\u7DDA
81799
+
81800
+ ## \u5DE5\u4F5C\u6D41\u7A0B\u5EFA\u8B70
81801
+
81802
+ ### \u958B\u767C\u6642
81803
+ 1. \u958B\u767C\u529F\u80FD
81804
+ 2. \u63D0\u4EA4\u524D\u57F7\u884C \`bun run validate\`
81805
+ 3. \u4FEE\u6B63\u554F\u984C
81806
+ 4. \u63D0\u4EA4\u7A0B\u5F0F\u78BC
81807
+
81808
+ ### \u4F7F\u7528 Pre-commit Hook\uFF08\u63A8\u85A6\uFF09
81809
+ 1. \u5B89\u88DD pre-commit hook\uFF08\u53EA\u9700\u4E00\u6B21\uFF09
81810
+ 2. \u6B63\u5E38\u958B\u767C\u548C\u63D0\u4EA4
81811
+ 3. \u6AA2\u67E5\u6703\u81EA\u52D5\u57F7\u884C
81812
+ 4. \u5982\u6709\u554F\u984C\uFF0C\u4FEE\u6B63\u5F8C\u91CD\u65B0\u63D0\u4EA4
81813
+
81814
+ ## \u6A94\u6848\u7D50\u69CB
81815
+
81816
+ \`\`\`
81817
+ ${context.nameKebabCase}/
81818
+ \u251C\u2500\u2500 package.json # \u6AA2\u67E5\u8173\u672C\u5B9A\u7FA9
81819
+ \u251C\u2500\u2500 scripts/
81820
+ \u2502 \u251C\u2500\u2500 check.sh # \u5B8C\u6574\u6AA2\u67E5\u8173\u672C\uFF08Shell\uFF09
81821
+ \u2502 \u251C\u2500\u2500 check-dependencies.ts # \u4F9D\u8CF4\u7248\u672C\u6AA2\u67E5
81822
+ \u2502 \u2514\u2500\u2500 pre-commit.sh # Pre-commit hook
81823
+ \u2514\u2500\u2500 CHECK_SYSTEM.md # \u672C\u6587\u4EF6
81824
+ \`\`\`
81825
+
81826
+ ## \u6CE8\u610F\u4E8B\u9805
81827
+
81828
+ 1. **\u4F9D\u8CF4\u6AA2\u67E5\u9700\u8981\u7DB2\u8DEF\u9023\u7DDA**\uFF1A\`check:deps\` \u9700\u8981\u9023\u63A5\u5230 npm registry
81829
+ 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
81830
+ 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
81831
+
81832
+ ## \u6545\u969C\u6392\u9664
81833
+
81834
+ ### \u6AA2\u67E5\u5931\u6557
81835
+ 1. \u67E5\u770B\u932F\u8AA4\u8A0A\u606F
81836
+ 2. \u4FEE\u6B63\u554F\u984C
81837
+ 3. \u91CD\u65B0\u57F7\u884C\u6AA2\u67E5
81838
+
81839
+ ### \u8DF3\u904E\u6AA2\u67E5
81840
+ \u53EA\u6709\u5728\u7DCA\u6025\u60C5\u6CC1\u4E0B\u624D\u4F7F\u7528\uFF1A
81841
+ \`\`\`bash
81842
+ git commit --no-verify
81843
+ \`\`\`
81844
+
81845
+ ### \u79FB\u9664 Pre-commit Hook
81846
+ \`\`\`bash
81847
+ rm .git/hooks/pre-commit
81848
+ \`\`\`
81849
+ `;
81850
+ }
81421
81851
  log(message) {
81422
81852
  if (this.config.verbose) {
81423
81853
  console.log(message);
@@ -81588,6 +82018,11 @@ class CleanArchitectureGenerator extends BaseGenerator {
81588
82018
  type: "directory",
81589
82019
  name: "Providers",
81590
82020
  children: [
82021
+ {
82022
+ type: "file",
82023
+ name: "index.ts",
82024
+ content: this.generateProvidersIndex()
82025
+ },
81591
82026
  {
81592
82027
  type: "file",
81593
82028
  name: "AppServiceProvider.ts",
@@ -81597,6 +82032,16 @@ class CleanArchitectureGenerator extends BaseGenerator {
81597
82032
  type: "file",
81598
82033
  name: "RepositoryServiceProvider.ts",
81599
82034
  content: this.generateRepositoryServiceProvider()
82035
+ },
82036
+ {
82037
+ type: "file",
82038
+ name: "MiddlewareProvider.ts",
82039
+ content: this.generateMiddlewareProvider()
82040
+ },
82041
+ {
82042
+ type: "file",
82043
+ name: "RouteProvider.ts",
82044
+ content: this.generateRouteProvider()
81600
82045
  }
81601
82046
  ]
81602
82047
  }
@@ -82080,6 +82525,65 @@ export class RepositoryServiceProvider extends ServiceProvider {
82080
82525
  container.singleton('mailService', () => new MailService())
82081
82526
  }
82082
82527
  }
82528
+ `;
82529
+ }
82530
+ generateProvidersIndex() {
82531
+ return `/**
82532
+ * Application Service Providers
82533
+ */
82534
+
82535
+ export { AppServiceProvider } from './AppServiceProvider'
82536
+ export { RepositoryServiceProvider } from './RepositoryServiceProvider'
82537
+ export { MiddlewareProvider } from './MiddlewareProvider'
82538
+ export { RouteProvider } from './RouteProvider'
82539
+ `;
82540
+ }
82541
+ generateMiddlewareProvider() {
82542
+ return `/**
82543
+ * Middleware Service Provider
82544
+ */
82545
+
82546
+ import {
82547
+ ServiceProvider,
82548
+ type Container,
82549
+ type PlanetCore,
82550
+ bodySizeLimit,
82551
+ securityHeaders,
82552
+ } from '@gravito/core'
82553
+
82554
+ export class MiddlewareProvider extends ServiceProvider {
82555
+ register(_container: Container): void {}
82556
+
82557
+ boot(core: PlanetCore): void {
82558
+ const isDev = process.env.NODE_ENV !== 'production'
82559
+
82560
+ core.adapter.use('*', securityHeaders({
82561
+ contentSecurityPolicy: isDev ? false : undefined,
82562
+ }))
82563
+
82564
+ core.adapter.use('*', bodySizeLimit(10 * 1024 * 1024))
82565
+
82566
+ core.logger.info('\uD83D\uDEE1\uFE0F Middleware registered')
82567
+ }
82568
+ }
82569
+ `;
82570
+ }
82571
+ generateRouteProvider() {
82572
+ return `/**
82573
+ * Route Service Provider
82574
+ */
82575
+
82576
+ import { ServiceProvider, type Container, type PlanetCore } from '@gravito/core'
82577
+ import { registerApiRoutes } from '../../Interface/Http/Routes/api'
82578
+
82579
+ export class RouteProvider extends ServiceProvider {
82580
+ register(_container: Container): void {}
82581
+
82582
+ boot(core: PlanetCore): void {
82583
+ registerApiRoutes(core.router)
82584
+ core.logger.info('\uD83D\uDEE4\uFE0F Routes registered')
82585
+ }
82586
+ }
82083
82587
  `;
82084
82588
  }
82085
82589
  generateUserController() {
@@ -82161,64 +82665,55 @@ export class UserPresenter {
82161
82665
  }
82162
82666
  `;
82163
82667
  }
82164
- generateBootstrap(context) {
82668
+ generateBootstrap(_context) {
82165
82669
  return `/**
82166
82670
  * Application Bootstrap
82671
+ *
82672
+ * The entry point for your Clean Architecture application.
82673
+ * Uses the ServiceProvider pattern for modular initialization.
82674
+ *
82675
+ * Lifecycle:
82676
+ * 1. Configure: Load app config and orbits
82677
+ * 2. Boot: Initialize PlanetCore
82678
+ * 3. Register Providers: Bind services to container
82679
+ * 4. Bootstrap: Boot all providers
82167
82680
  */
82168
82681
 
82169
- import { bodySizeLimit, PlanetCore, securityHeaders } from '@gravito/core'
82682
+ import { defineConfig, PlanetCore } from '@gravito/core'
82170
82683
  import { OrbitAtlas } from '@gravito/atlas'
82171
- import databaseConfig from '../config/database'
82172
- import { AppServiceProvider } from './Infrastructure/Providers/AppServiceProvider'
82173
- import { RepositoryServiceProvider } from './Infrastructure/Providers/RepositoryServiceProvider'
82174
- import { registerApiRoutes } from './Interface/Http/Routes/api'
82175
-
82176
- const core = new PlanetCore({
82177
- config: {
82178
- APP_NAME: '${context.name}',
82179
- database: databaseConfig,
82180
- },
82181
- })
82182
-
82183
- const defaultCsp = [
82184
- "default-src 'self'",
82185
- "script-src 'self' 'unsafe-inline'",
82186
- "style-src 'self' 'unsafe-inline'",
82187
- "img-src 'self' data:",
82188
- "object-src 'none'",
82189
- "base-uri 'self'",
82190
- "frame-ancestors 'none'",
82191
- ].join('; ')
82192
- const cspValue = process.env.APP_CSP
82193
- const csp = cspValue === 'false' ? false : (cspValue ?? defaultCsp)
82194
- const hstsMaxAge = Number.parseInt(process.env.APP_HSTS_MAX_AGE ?? '15552000', 10)
82195
- const bodyLimit = Number.parseInt(process.env.APP_BODY_LIMIT ?? '1048576', 10)
82196
- const requireLength = process.env.APP_BODY_REQUIRE_LENGTH === 'true'
82197
-
82198
- core.adapter.use(
82199
- '*',
82200
- securityHeaders({
82201
- contentSecurityPolicy: csp,
82202
- hsts:
82203
- process.env.NODE_ENV === 'production'
82204
- ? { maxAge: Number.isNaN(hstsMaxAge) ? 15552000 : hstsMaxAge, includeSubDomains: true }
82205
- : false,
82684
+ import appConfig from '../config/app'
82685
+ import {
82686
+ AppServiceProvider,
82687
+ RepositoryServiceProvider,
82688
+ MiddlewareProvider,
82689
+ RouteProvider,
82690
+ } from './Infrastructure/Providers'
82691
+
82692
+ export async function bootstrap() {
82693
+ // 1. Configure
82694
+ const config = defineConfig({
82695
+ config: appConfig,
82696
+ orbits: [new OrbitAtlas()],
82206
82697
  })
82207
- )
82208
- if (!Number.isNaN(bodyLimit) && bodyLimit > 0) {
82209
- core.adapter.use('*', bodySizeLimit(bodyLimit, { requireContentLength: requireLength }))
82210
- }
82211
82698
 
82212
- await core.orbit(new OrbitAtlas())
82699
+ // 2. Boot Core
82700
+ const core = await PlanetCore.boot(config)
82701
+ core.registerGlobalErrorHandlers()
82213
82702
 
82214
- core.register(new RepositoryServiceProvider())
82215
- core.register(new AppServiceProvider())
82703
+ // 3. Register Providers
82704
+ core.register(new RepositoryServiceProvider())
82705
+ core.register(new AppServiceProvider())
82706
+ core.register(new MiddlewareProvider())
82707
+ core.register(new RouteProvider())
82216
82708
 
82217
- await core.bootstrap()
82709
+ // 4. Bootstrap All Providers
82710
+ await core.bootstrap()
82218
82711
 
82219
- // Register routes
82220
- registerApiRoutes(core.router)
82712
+ return core
82713
+ }
82221
82714
 
82715
+ // Application Entry Point
82716
+ const core = await bootstrap()
82222
82717
  export default core.liftoff()
82223
82718
  `;
82224
82719
  }
@@ -82230,6 +82725,15 @@ export default core.liftoff()
82230
82725
  This project follows **Clean Architecture** (by Robert C. Martin).
82231
82726
  The key principle is the **Dependency Rule**: dependencies point inward.
82232
82727
 
82728
+ ## Service Providers
82729
+
82730
+ Service providers are the central place to configure your application. They follow the ServiceProvider pattern with \`register()\` and \`boot()\` lifecycle methods.
82731
+
82732
+ ### Provider Lifecycle
82733
+
82734
+ 1. **register()**: Bind services to the container (sync or async).
82735
+ 2. **boot()**: Called after ALL providers have registered. Safe to use other services.
82736
+
82233
82737
  ## Layer Structure
82234
82738
 
82235
82739
  \`\`\`
@@ -82301,6 +82805,10 @@ Created with \u2764\uFE0F using Gravito Framework
82301
82805
  start: "bun run dist/bootstrap.js",
82302
82806
  test: "bun test",
82303
82807
  typecheck: "tsc --noEmit",
82808
+ check: "bun run typecheck && bun run test",
82809
+ "check:deps": "bun run scripts/check-dependencies.ts",
82810
+ validate: "bun run check && bun run check:deps",
82811
+ precommit: "bun run validate",
82304
82812
  "docker:build": `docker build -t ${context.nameKebabCase} .`,
82305
82813
  "docker:run": `docker run -it -p 3000:3000 ${context.nameKebabCase}`
82306
82814
  },
@@ -82605,63 +83113,46 @@ class DddGenerator extends BaseGenerator {
82605
83113
  ]
82606
83114
  };
82607
83115
  }
82608
- generateBootstrapApp(context) {
83116
+ generateBootstrapApp(_context) {
82609
83117
  return `/**
82610
83118
  * Application Bootstrap
82611
83119
  *
82612
- * Central configuration and initialization of the application.
83120
+ * Central configuration and initialization using the ServiceProvider pattern.
83121
+ *
83122
+ * Lifecycle:
83123
+ * 1. Configure: Load app config and orbits
83124
+ * 2. Boot: Initialize PlanetCore
83125
+ * 3. Register Providers: Bind services to container
83126
+ * 4. Bootstrap: Boot all providers
82613
83127
  */
82614
83128
 
82615
- import { bodySizeLimit, PlanetCore, securityHeaders } from '@gravito/core'
83129
+ import { defineConfig, PlanetCore } from '@gravito/core'
83130
+ import { OrbitAtlas } from '@gravito/atlas'
83131
+ import appConfig from '../../config/app'
82616
83132
  import { registerProviders } from './providers'
82617
83133
  import { registerRoutes } from './routes'
82618
83134
 
82619
83135
  export async function createApp(): Promise<PlanetCore> {
82620
- const core = new PlanetCore({
82621
- config: {
82622
- APP_NAME: '${context.name}',
82623
- },
82624
- })
83136
+ // 1. Configure
83137
+ const config = defineConfig({
83138
+ config: appConfig,
83139
+ orbits: [new OrbitAtlas()],
83140
+ })
82625
83141
 
82626
- const defaultCsp = [
82627
- "default-src 'self'",
82628
- "script-src 'self' 'unsafe-inline'",
82629
- "style-src 'self' 'unsafe-inline'",
82630
- "img-src 'self' data:",
82631
- "object-src 'none'",
82632
- "base-uri 'self'",
82633
- "frame-ancestors 'none'",
82634
- ].join('; ')
82635
- const cspValue = process.env.APP_CSP
82636
- const csp = cspValue === 'false' ? false : (cspValue ?? defaultCsp)
82637
- const hstsMaxAge = Number.parseInt(process.env.APP_HSTS_MAX_AGE ?? '15552000', 10)
82638
- const bodyLimit = Number.parseInt(process.env.APP_BODY_LIMIT ?? '1048576', 10)
82639
- const requireLength = process.env.APP_BODY_REQUIRE_LENGTH === 'true'
82640
-
82641
- core.adapter.use(
82642
- '*',
82643
- securityHeaders({
82644
- contentSecurityPolicy: csp,
82645
- hsts:
82646
- process.env.NODE_ENV === 'production'
82647
- ? { maxAge: Number.isNaN(hstsMaxAge) ? 15552000 : hstsMaxAge, includeSubDomains: true }
82648
- : false,
82649
- })
82650
- )
82651
- if (!Number.isNaN(bodyLimit) && bodyLimit > 0) {
82652
- core.adapter.use('*', bodySizeLimit(bodyLimit, { requireContentLength: requireLength }))
82653
- }
83142
+ // 2. Boot Core
83143
+ const core = await PlanetCore.boot(config)
83144
+ core.registerGlobalErrorHandlers()
82654
83145
 
82655
- // Register all service providers
82656
- await registerProviders(core)
83146
+ // 3. Register Providers
83147
+ await registerProviders(core)
82657
83148
 
82658
- // Bootstrap the application
82659
- await core.bootstrap()
83149
+ // 4. Bootstrap All Providers
83150
+ await core.bootstrap()
82660
83151
 
82661
- // Register routes
82662
- registerRoutes(core.router)
83152
+ // Register routes after bootstrap
83153
+ registerRoutes(core.router)
82663
83154
 
82664
- return core
83155
+ return core
82665
83156
  }
82666
83157
  `;
82667
83158
  }
@@ -82669,19 +83160,48 @@ export async function createApp(): Promise<PlanetCore> {
82669
83160
  return `/**
82670
83161
  * Service Providers Registry
82671
83162
  *
82672
- * Register all module service providers here.
83163
+ * Register all service providers here.
83164
+ * Include both global and module-specific providers.
82673
83165
  */
82674
83166
 
82675
- import type { PlanetCore } from '@gravito/core'
83167
+ import {
83168
+ ServiceProvider,
83169
+ type Container,
83170
+ type PlanetCore,
83171
+ bodySizeLimit,
83172
+ securityHeaders,
83173
+ } from '@gravito/core'
82676
83174
  import { OrderingServiceProvider } from '../Modules/Ordering/Infrastructure/Providers/OrderingServiceProvider'
82677
83175
  import { CatalogServiceProvider } from '../Modules/Catalog/Infrastructure/Providers/CatalogServiceProvider'
82678
83176
 
83177
+ /**
83178
+ * Middleware Provider - Global middleware registration
83179
+ */
83180
+ export class MiddlewareProvider extends ServiceProvider {
83181
+ register(_container: Container): void {}
83182
+
83183
+ boot(core: PlanetCore): void {
83184
+ const isDev = process.env.NODE_ENV !== 'production'
83185
+
83186
+ core.adapter.use('*', securityHeaders({
83187
+ contentSecurityPolicy: isDev ? false : undefined,
83188
+ }))
83189
+
83190
+ core.adapter.use('*', bodySizeLimit(10 * 1024 * 1024))
83191
+
83192
+ core.logger.info('\uD83D\uDEE1\uFE0F Global middleware registered')
83193
+ }
83194
+ }
83195
+
82679
83196
  export async function registerProviders(core: PlanetCore): Promise<void> {
82680
- // Register module providers
82681
- core.register(new OrderingServiceProvider())
82682
- core.register(new CatalogServiceProvider())
83197
+ // Global Providers
83198
+ core.register(new MiddlewareProvider())
82683
83199
 
82684
- // Add more providers as needed
83200
+ // Module Providers
83201
+ core.register(new OrderingServiceProvider())
83202
+ core.register(new CatalogServiceProvider())
83203
+
83204
+ // Add more providers as needed
82685
83205
  }
82686
83206
  `;
82687
83207
  }
@@ -82792,6 +83312,10 @@ export class ${name}ServiceProvider extends ServiceProvider {
82792
83312
  start: "bun run dist/main.js",
82793
83313
  test: "bun test",
82794
83314
  typecheck: "tsc --noEmit",
83315
+ check: "bun run typecheck && bun run test",
83316
+ "check:deps": "bun run scripts/check-dependencies.ts",
83317
+ validate: "bun run check && bun run check:deps",
83318
+ precommit: "bun run validate",
82795
83319
  "docker:build": `docker build -t ${context.nameKebabCase} .`,
82796
83320
  "docker:run": `docker run -it -p 3000:3000 ${context.nameKebabCase}`
82797
83321
  },
@@ -83223,6 +83747,16 @@ export function report(error: unknown): void {
83223
83747
 
83224
83748
  This project follows **Domain-Driven Design (DDD)** with strategic and tactical patterns.
83225
83749
 
83750
+ ## Service Providers
83751
+
83752
+ Service providers are the central place to configure your application and modules. They follow the ServiceProvider pattern with \`register()\` and \`boot()\` lifecycle methods.
83753
+
83754
+ ### Internal Bootstrapping
83755
+
83756
+ 1. **Bootstrap/app.ts**: Orchestrates the 4-step lifecycle (Configure, Boot, Register, Bootstrap).
83757
+ 2. **Bootstrap/providers.ts**: Central registry for all global and module-specific providers.
83758
+ 3. **Infrastructure/Providers/[Module]ServiceProvider.ts**: Module-specific service registration.
83759
+
83226
83760
  ## Bounded Contexts
83227
83761
 
83228
83762
  \`\`\`
@@ -83359,6 +83893,11 @@ class EnterpriseMvcGenerator extends BaseGenerator {
83359
83893
  type: "directory",
83360
83894
  name: "Providers",
83361
83895
  children: [
83896
+ {
83897
+ type: "file",
83898
+ name: "index.ts",
83899
+ content: this.generateProvidersIndex()
83900
+ },
83362
83901
  {
83363
83902
  type: "file",
83364
83903
  name: "AppServiceProvider.ts",
@@ -83366,8 +83905,18 @@ class EnterpriseMvcGenerator extends BaseGenerator {
83366
83905
  },
83367
83906
  {
83368
83907
  type: "file",
83369
- name: "RouteServiceProvider.ts",
83370
- content: this.generateRouteServiceProvider(context)
83908
+ name: "DatabaseProvider.ts",
83909
+ content: this.generateDatabaseProvider()
83910
+ },
83911
+ {
83912
+ type: "file",
83913
+ name: "MiddlewareProvider.ts",
83914
+ content: this.generateMiddlewareProvider()
83915
+ },
83916
+ {
83917
+ type: "file",
83918
+ name: "RouteProvider.ts",
83919
+ content: this.generateRouteProvider()
83371
83920
  }
83372
83921
  ]
83373
83922
  },
@@ -83830,42 +84379,168 @@ export class RouteServiceProvider extends ServiceProvider {
83830
84379
  }
83831
84380
  `;
83832
84381
  }
83833
- generateExceptionHandler() {
84382
+ generateProvidersIndex() {
83834
84383
  return `/**
83835
- * Exception Handler
84384
+ * Application Service Providers
83836
84385
  *
83837
- * Handles all exceptions thrown by the application.
83838
- * Customize error responses and logging here.
84386
+ * Export all providers for easy importing in bootstrap.
84387
+ * Providers are registered in the order they are listed.
83839
84388
  */
83840
84389
 
83841
- import type { ErrorHandlerContext } from '@gravito/core'
83842
-
83843
- /**
83844
- * Report an exception (logging, monitoring, etc.)
83845
- */
83846
- export function report(error: unknown, context: ErrorHandlerContext): void {
83847
- // Log to external service (Sentry, etc.)
83848
- if (!context.isProduction) {
83849
- console.error('[Exception Handler]', error)
84390
+ export { AppServiceProvider } from './AppServiceProvider'
84391
+ export { DatabaseProvider } from './DatabaseProvider'
84392
+ export { MiddlewareProvider } from './MiddlewareProvider'
84393
+ export { RouteProvider } from './RouteProvider'
84394
+ `;
83850
84395
  }
83851
- }
83852
-
83853
- /**
83854
- * Determine if the exception should be reported.
84396
+ generateDatabaseProvider() {
84397
+ return `/**
84398
+ * Database Service Provider
84399
+ *
84400
+ * Handles database initialization and migrations.
84401
+ *
84402
+ * Lifecycle:
84403
+ * - register(): Bind database config to container
84404
+ * - boot(): Run migrations and seeders
83855
84405
  */
83856
- export function shouldReport(error: unknown): boolean {
83857
- // Don't report 4xx errors
83858
- if (error instanceof Error && 'status' in error) {
83859
- const status = (error as any).status
83860
- if (status >= 400 && status < 500) {
83861
- return false
83862
- }
84406
+
84407
+ import { ServiceProvider, type Container, type PlanetCore } from '@gravito/core'
84408
+ import databaseConfig from '../../config/database'
84409
+
84410
+ export class DatabaseProvider extends ServiceProvider {
84411
+ /**
84412
+ * Register database configuration.
84413
+ */
84414
+ register(_container: Container): void {
84415
+ this.mergeConfig(this.core!.config, 'database', databaseConfig)
83863
84416
  }
83864
84417
 
83865
- return true
84418
+ /**
84419
+ * Initialize database connections.
84420
+ */
84421
+ async boot(core: PlanetCore): Promise<void> {
84422
+ // Database initialization will be handled by Atlas orbit
84423
+ core.logger.info('\uD83D\uDCE6 Database provider booted')
84424
+ }
83866
84425
  }
84426
+ `;
84427
+ }
84428
+ generateMiddlewareProvider() {
84429
+ return `/**
84430
+ * Middleware Service Provider
84431
+ *
84432
+ * Registers global middleware stack.
84433
+ * Provides a centralized location for middleware configuration.
84434
+ *
84435
+ * Lifecycle:
84436
+ * - register(): N/A (no container bindings)
84437
+ * - boot(): Register global middleware
84438
+ */
83867
84439
 
83868
- /**
84440
+ import {
84441
+ ServiceProvider,
84442
+ type Container,
84443
+ type PlanetCore,
84444
+ bodySizeLimit,
84445
+ securityHeaders,
84446
+ } from '@gravito/core'
84447
+
84448
+ export class MiddlewareProvider extends ServiceProvider {
84449
+ /**
84450
+ * No container bindings needed.
84451
+ */
84452
+ register(_container: Container): void {
84453
+ // Middleware doesn't require container bindings
84454
+ }
84455
+
84456
+ /**
84457
+ * Register global middleware stack.
84458
+ */
84459
+ boot(core: PlanetCore): void {
84460
+ const isDev = process.env.NODE_ENV !== 'production'
84461
+
84462
+ // Security Headers
84463
+ core.adapter.use('*', securityHeaders({
84464
+ contentSecurityPolicy: isDev ? false : undefined,
84465
+ }))
84466
+
84467
+ // Body Parser Limits
84468
+ core.adapter.use('*', bodySizeLimit(10 * 1024 * 1024)) // 10MB limit
84469
+
84470
+ core.logger.info('\uD83D\uDEE1\uFE0F Middleware registered')
84471
+ }
84472
+ }
84473
+ `;
84474
+ }
84475
+ generateRouteProvider() {
84476
+ return `/**
84477
+ * Route Service Provider
84478
+ *
84479
+ * Registers application routes.
84480
+ * Routes are registered in the boot phase after all services are available.
84481
+ *
84482
+ * Lifecycle:
84483
+ * - register(): N/A
84484
+ * - boot(): Register routes
84485
+ */
84486
+
84487
+ import { ServiceProvider, type Container, type PlanetCore } from '@gravito/core'
84488
+ import { registerRoutes } from '../routes'
84489
+
84490
+ export class RouteProvider extends ServiceProvider {
84491
+ /**
84492
+ * No container bindings needed.
84493
+ */
84494
+ register(_container: Container): void {
84495
+ // Routes don't require container bindings
84496
+ }
84497
+
84498
+ /**
84499
+ * Register application routes.
84500
+ */
84501
+ boot(core: PlanetCore): void {
84502
+ registerRoutes(core.router)
84503
+ core.logger.info('\uD83D\uDEE4\uFE0F Routes registered')
84504
+ }
84505
+ }
84506
+ `;
84507
+ }
84508
+ generateExceptionHandler() {
84509
+ return `/**
84510
+ * Exception Handler
84511
+ *
84512
+ * Handles all exceptions thrown by the application.
84513
+ * Customize error responses and logging here.
84514
+ */
84515
+
84516
+ import type { ErrorHandlerContext } from '@gravito/core'
84517
+
84518
+ /**
84519
+ * Report an exception (logging, monitoring, etc.)
84520
+ */
84521
+ export function report(error: unknown, context: ErrorHandlerContext): void {
84522
+ // Log to external service (Sentry, etc.)
84523
+ if (!context.isProduction) {
84524
+ console.error('[Exception Handler]', error)
84525
+ }
84526
+ }
84527
+
84528
+ /**
84529
+ * Determine if the exception should be reported.
84530
+ */
84531
+ export function shouldReport(error: unknown): boolean {
84532
+ // Don't report 4xx errors
84533
+ if (error instanceof Error && 'status' in error) {
84534
+ const status = (error as any).status
84535
+ if (status >= 400 && status < 500) {
84536
+ return false
84537
+ }
84538
+ }
84539
+
84540
+ return true
84541
+ }
84542
+
84543
+ /**
83869
84544
  * A list of exception types that should not be reported.
83870
84545
  */
83871
84546
  export const dontReport: string[] = [
@@ -83877,74 +84552,75 @@ export const dontReport: string[] = [
83877
84552
  generateBootstrap(context) {
83878
84553
  const spectrumImport = context.withSpectrum ? `import { SpectrumOrbit } from '@gravito/spectrum'
83879
84554
  ` : "";
83880
- const spectrumOrbit = context.withSpectrum ? `
83881
- // Enable Debug Dashboard
83882
- if (process.env.APP_DEBUG === 'true') {
83883
- await core.orbit(new SpectrumOrbit())
83884
- }
83885
- ` : "";
84555
+ const spectrumOrbit = context.withSpectrum ? " new SpectrumOrbit()," : "";
83886
84556
  return `/**
83887
84557
  * Application Bootstrap
83888
84558
  *
83889
- * This is the entry point for your application.
83890
- * It initializes the core and registers all providers.
84559
+ * The entry point for your Gravito application.
84560
+ * Uses the ServiceProvider pattern for modular, maintainable initialization.
84561
+ *
84562
+ * Lifecycle:
84563
+ * 1. Configure: Load app config and orbits
84564
+ * 2. Boot: Initialize PlanetCore
84565
+ * 3. Register Providers: Bind services to container
84566
+ * 4. Bootstrap: Boot all providers
84567
+ *
84568
+ * @module bootstrap
83891
84569
  */
83892
84570
 
83893
- import { bodySizeLimit, PlanetCore, securityHeaders } from '@gravito/core'
84571
+ import { defineConfig, PlanetCore } from '@gravito/core'
83894
84572
  import { OrbitAtlas } from '@gravito/atlas'
83895
- import databaseConfig from '../config/database'
83896
- ${spectrumImport}import { AppServiceProvider } from './Providers/AppServiceProvider'
83897
- import { RouteServiceProvider } from './Providers/RouteServiceProvider'
83898
-
83899
- // Load environment variables
83900
- // Bun automatically loads .env
83901
-
83902
- // Create application core
83903
- const core = new PlanetCore({
83904
- config: {
83905
- APP_NAME: process.env.APP_NAME ?? '${context.name}',
83906
- database: databaseConfig,
83907
- },
83908
- })
83909
- const defaultCsp = [
83910
- "default-src 'self'",
83911
- "script-src 'self' 'unsafe-inline'",
83912
- "style-src 'self' 'unsafe-inline'",
83913
- "img-src 'self' data:",
83914
- "object-src 'none'",
83915
- "base-uri 'self'",
83916
- "frame-ancestors 'none'",
83917
- ].join('; ')
83918
- const cspValue = process.env.APP_CSP
83919
- const csp = cspValue === 'false' ? false : (cspValue ?? defaultCsp)
83920
- const hstsMaxAge = Number.parseInt(process.env.APP_HSTS_MAX_AGE ?? '15552000', 10)
83921
- const bodyLimit = Number.parseInt(process.env.APP_BODY_LIMIT ?? '1048576', 10)
83922
- const requireLength = process.env.APP_BODY_REQUIRE_LENGTH === 'true'
83923
-
83924
- core.adapter.use(
83925
- '*',
83926
- securityHeaders({
83927
- contentSecurityPolicy: csp,
83928
- hsts:
83929
- process.env.NODE_ENV === 'production'
83930
- ? { maxAge: Number.isNaN(hstsMaxAge) ? 15552000 : hstsMaxAge, includeSubDomains: true }
83931
- : false,
83932
- })
83933
- )
83934
- if (!Number.isNaN(bodyLimit) && bodyLimit > 0) {
83935
- core.adapter.use('*', bodySizeLimit(bodyLimit, { requireContentLength: requireLength }))
83936
- }
84573
+ import appConfig from '../config/app'
84574
+ ${spectrumImport}import {
84575
+ AppServiceProvider,
84576
+ DatabaseProvider,
84577
+ MiddlewareProvider,
84578
+ RouteProvider,
84579
+ } from './Providers'
83937
84580
 
83938
- await core.orbit(new OrbitAtlas())
84581
+ /**
84582
+ * Bootstrap the application with service providers.
84583
+ *
84584
+ * @returns The booted PlanetCore instance
84585
+ */
84586
+ export async function bootstrap() {
84587
+ // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
84588
+ // 1. Configure
84589
+ // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
84590
+ const config = defineConfig({
84591
+ config: appConfig,
84592
+ orbits: [
84593
+ new OrbitAtlas(),
83939
84594
  ${spectrumOrbit}
83940
- // Register service providers
83941
- core.register(new AppServiceProvider())
83942
- core.register(new RouteServiceProvider())
84595
+ ],
84596
+ })
83943
84597
 
83944
- // Bootstrap the application
83945
- await core.bootstrap()
84598
+ // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
84599
+ // 2. Boot Core
84600
+ // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
84601
+ const core = await PlanetCore.boot(config)
84602
+ core.registerGlobalErrorHandlers()
84603
+
84604
+ // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
84605
+ // 3. Register Providers
84606
+ // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
84607
+ core.register(new AppServiceProvider())
84608
+ core.register(new DatabaseProvider())
84609
+ core.register(new MiddlewareProvider())
84610
+ core.register(new RouteProvider())
84611
+
84612
+ // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
84613
+ // 4. Bootstrap All Providers
84614
+ // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
84615
+ await core.bootstrap()
84616
+
84617
+ return core
84618
+ }
83946
84619
 
83947
- // Export for Bun.serve()
84620
+ // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
84621
+ // Application Entry Point
84622
+ // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
84623
+ const core = await bootstrap()
83948
84624
  export default core.liftoff()
83949
84625
  `;
83950
84626
  }
@@ -84350,7 +85026,9 @@ export class ${name}ServiceProvider extends ServiceProvider {
84350
85026
  scripts: {
84351
85027
  build: "tsup src/index.ts --format cjs,esm --dts",
84352
85028
  test: "bun test",
84353
- typecheck: "tsc --noEmit"
85029
+ typecheck: "tsc --noEmit",
85030
+ check: "bun run typecheck && bun run test",
85031
+ validate: "bun run check"
84354
85032
  },
84355
85033
  dependencies: {
84356
85034
  "@gravito/core": depVersion,
@@ -84638,10 +85316,25 @@ class ActionDomainGenerator extends BaseGenerator {
84638
85316
  type: "directory",
84639
85317
  name: "providers",
84640
85318
  children: [
85319
+ {
85320
+ type: "file",
85321
+ name: "index.ts",
85322
+ content: this.generateProvidersIndex()
85323
+ },
84641
85324
  {
84642
85325
  type: "file",
84643
85326
  name: "AppServiceProvider.ts",
84644
85327
  content: this.generateAppServiceProvider(context)
85328
+ },
85329
+ {
85330
+ type: "file",
85331
+ name: "MiddlewareProvider.ts",
85332
+ content: this.generateMiddlewareProvider()
85333
+ },
85334
+ {
85335
+ type: "file",
85336
+ name: "RouteProvider.ts",
85337
+ content: this.generateRouteProvider()
84645
85338
  }
84646
85339
  ]
84647
85340
  },
@@ -84801,7 +85494,7 @@ export function registerApiRoutes(router: Router) {
84801
85494
  import { ServiceProvider, type Container, type PlanetCore } from '@gravito/core'
84802
85495
 
84803
85496
  export class AppServiceProvider extends ServiceProvider {
84804
- register(container: Container): void {
85497
+ register(_container: Container): void {
84805
85498
  // Register global services here
84806
85499
  }
84807
85500
 
@@ -84811,82 +85504,139 @@ export class AppServiceProvider extends ServiceProvider {
84811
85504
  }
84812
85505
  `;
84813
85506
  }
84814
- generateBootstrap(context) {
85507
+ generateProvidersIndex() {
84815
85508
  return `/**
84816
- * Application Entry Point
85509
+ * Application Service Providers
84817
85510
  */
84818
85511
 
84819
- import { PlanetCore, securityHeaders, bodySizeLimit } from '@gravito/core'
84820
- import { OrbitAtlas } from '@gravito/atlas'
84821
- import databaseConfig from '../config/database'
84822
- import { AppServiceProvider } from './providers/AppServiceProvider'
84823
- import { registerApiRoutes } from './routes/api'
84824
-
84825
- // Initialize Core
84826
- const core = new PlanetCore({
84827
- config: {
84828
- APP_NAME: '${context.name}',
84829
- database: databaseConfig
84830
- },
84831
- })
85512
+ export { AppServiceProvider } from './AppServiceProvider'
85513
+ export { MiddlewareProvider } from './MiddlewareProvider'
85514
+ export { RouteProvider } from './RouteProvider'
85515
+ `;
85516
+ }
85517
+ generateMiddlewareProvider() {
85518
+ return `/**
85519
+ * Middleware Service Provider
85520
+ */
84832
85521
 
84833
- // Middleware
84834
- core.adapter.use('*', securityHeaders())
84835
- core.adapter.use('*', bodySizeLimit(1024 * 1024)) // 1MB
85522
+ import {
85523
+ ServiceProvider,
85524
+ type Container,
85525
+ type PlanetCore,
85526
+ bodySizeLimit,
85527
+ securityHeaders,
85528
+ } from '@gravito/core'
84836
85529
 
84837
- // Install Orbits
84838
- await core.orbit(new OrbitAtlas())
85530
+ export class MiddlewareProvider extends ServiceProvider {
85531
+ register(_container: Container): void {}
84839
85532
 
84840
- // Service Providers
84841
- core.register(new AppServiceProvider())
85533
+ boot(core: PlanetCore): void {
85534
+ core.adapter.use('*', securityHeaders())
85535
+ core.adapter.use('*', bodySizeLimit(1024 * 1024))
85536
+ core.logger.info('\uD83D\uDEE1\uFE0F Middleware registered')
85537
+ }
85538
+ }
85539
+ `;
85540
+ }
85541
+ generateRouteProvider() {
85542
+ return `/**
85543
+ * Route Service Provider
85544
+ */
84842
85545
 
84843
- // Bootstrap
84844
- await core.bootstrap()
85546
+ import { ServiceProvider, type Container, type PlanetCore } from '@gravito/core'
85547
+ import { registerApiRoutes } from '../routes/api'
84845
85548
 
84846
- // Routes
84847
- registerApiRoutes(core.router)
85549
+ export class RouteProvider extends ServiceProvider {
85550
+ register(_container: Container): void {}
84848
85551
 
84849
- // Liftoff
85552
+ boot(core: PlanetCore): void {
85553
+ registerApiRoutes(core.router)
85554
+ core.logger.info('\uD83D\uDEE4\uFE0F Routes registered')
85555
+ }
85556
+ }
85557
+ `;
85558
+ }
85559
+ generateBootstrap(_context) {
85560
+ return `/**
85561
+ * Application Bootstrap
85562
+ *
85563
+ * Uses the ServiceProvider pattern for modular initialization.
85564
+ */
85565
+
85566
+ import { defineConfig, PlanetCore } from '@gravito/core'
85567
+ import { OrbitAtlas } from '@gravito/atlas'
85568
+ import appConfig from '../config/app'
85569
+ import {
85570
+ AppServiceProvider,
85571
+ MiddlewareProvider,
85572
+ RouteProvider,
85573
+ } from './providers'
85574
+
85575
+ export async function bootstrap() {
85576
+ const config = defineConfig({
85577
+ config: appConfig,
85578
+ orbits: [new OrbitAtlas()],
85579
+ })
85580
+
85581
+ const core = await PlanetCore.boot(config)
85582
+ core.registerGlobalErrorHandlers()
85583
+
85584
+ core.register(new AppServiceProvider())
85585
+ core.register(new MiddlewareProvider())
85586
+ core.register(new RouteProvider())
85587
+
85588
+ await core.bootstrap()
85589
+
85590
+ return core
85591
+ }
85592
+
85593
+ const core = await bootstrap()
84850
85594
  export default core.liftoff()
84851
85595
  `;
84852
85596
  }
84853
85597
  generateArchitectureDoc(context) {
84854
- return "# " + context.name + ` - Action Domain Architecture
84855
-
84856
- ` + `## Overview
84857
-
84858
- ` + `This project uses the **Action Domain** pattern, designed for high-clarity API implementations.
84859
-
84860
- ` + `## Directory Structure
84861
-
84862
- ` + "```\n" + `src/
84863
- ` + `\u251C\u2500\u2500 actions/ # Single Responsibility Business Logic
84864
- ` + `\u2502 \u251C\u2500\u2500 Action.ts # Base Action class
84865
- ` + `\u2502 \u2514\u2500\u2500 [Domain]/ # Domain-specific actions
84866
- ` + `\u251C\u2500\u2500 controllers/ # HTTP Request Handlers
84867
- ` + `\u2502 \u2514\u2500\u2500 api/v1/ # API Controllers
84868
- ` + `\u251C\u2500\u2500 types/ # TypeScript Definitions
84869
- ` + `\u2502 \u251C\u2500\u2500 requests/ # Request Payloads
84870
- ` + `\u2502 \u2514\u2500\u2500 responses/ # Response Structures
84871
- ` + `\u251C\u2500\u2500 repositories/ # Data Access Layer
84872
- ` + `\u251C\u2500\u2500 routes/ # Route Definitions
84873
- ` + `\u2514\u2500\u2500 config/ # Configuration
84874
- ` + "```\n\n" + `## Core Concepts
84875
-
84876
- ` + `### Actions
84877
- ` + `Every business operation is an "Action". An action:
84878
- ` + `- Does ONE thing.
84879
- ` + `- Takes specific input.
84880
- ` + `- Returns specific output.
84881
- ` + `- Is framework-agnostic (ideally).
84882
-
84883
- ` + `### Controllers
84884
- ` + `Controllers are thin. They:
84885
- ` + `1. Parse the request.
84886
- ` + `2. Instantiate/Call the Action.
84887
- ` + `3. Return the response.
84888
-
84889
- ` + `Created with \u2764\uFE0F using Gravito Framework
85598
+ return `# ${context.name} - Action Domain Architecture
85599
+
85600
+ ## Overview
85601
+
85602
+ This project uses the **Action Domain** pattern, designed for high-clarity API implementations.
85603
+
85604
+ ## Service Providers
85605
+
85606
+ Service providers are the central place to configure your application. They follow the ServiceProvider pattern with \`register()\` and \`boot()\` lifecycle methods.
85607
+
85608
+ ## Directory Structure
85609
+
85610
+ \`\`\`
85611
+ src/
85612
+ \u251C\u2500\u2500 actions/ # Single Responsibility Business Logic
85613
+ \u2502 \u251C\u2500\u2500 Action.ts # Base Action class
85614
+ \u2502 \u2514\u2500\u2500 [Domain]/ # Domain-specific actions
85615
+ \u251C\u2500\u2500 controllers/ # HTTP Request Handlers
85616
+ \u2502 \u2514\u2500\u2500 api/v1/ # API Controllers
85617
+ \u251C\u2500\u2500 types/ # TypeScript Definitions
85618
+ \u251C\u2500\u2500 repositories/ # Data Access Layer
85619
+ \u251C\u2500\u2500 routes/ # Route Definitions
85620
+ \u251C\u2500\u2500 providers/ # Service Providers
85621
+ \u2514\u2500\u2500 config/ # Configuration
85622
+ \`\`\`
85623
+
85624
+ ## Core Concepts
85625
+
85626
+ ### Actions
85627
+ Every business operation is an "Action". An action:
85628
+ - Does ONE thing.
85629
+ - Takes specific input.
85630
+ - Returns specific output.
85631
+ - Is framework-agnostic (ideally).
85632
+
85633
+ ### Controllers
85634
+ Controllers are thin. They:
85635
+ 1. Parse the request.
85636
+ 2. Instantiate/Call the Action.
85637
+ 3. Return the response.
85638
+
85639
+ Created with \u2764\uFE0F using Gravito Framework
84890
85640
  `;
84891
85641
  }
84892
85642
  generatePackageJson(context) {
@@ -84899,7 +85649,11 @@ export default core.liftoff()
84899
85649
  build: "bun build ./src/bootstrap.ts --outdir ./dist --target bun",
84900
85650
  start: "bun run dist/bootstrap.js",
84901
85651
  test: "bun test",
84902
- typecheck: "tsc --noEmit"
85652
+ typecheck: "tsc --noEmit",
85653
+ check: "bun run typecheck && bun run test",
85654
+ "check:deps": "bun run scripts/check-dependencies.ts",
85655
+ validate: "bun run check && bun run check:deps",
85656
+ precommit: "bun run validate"
84903
85657
  },
84904
85658
  dependencies: {
84905
85659
  "@gravito/core": "workspace:*",
@@ -85027,7 +85781,7 @@ import { confirm, note, spinner } from "@clack/prompts";
85027
85781
  // ../core/package.json
85028
85782
  var package_default = {
85029
85783
  name: "@gravito/core",
85030
- version: "1.0.0",
85784
+ version: "1.1.0",
85031
85785
  description: "",
85032
85786
  module: "./dist/index.js",
85033
85787
  main: "./dist/index.cjs",
@@ -85043,6 +85797,11 @@ var package_default = {
85043
85797
  types: "./dist/compat.d.ts",
85044
85798
  import: "./dist/compat.js",
85045
85799
  require: "./dist/compat.cjs"
85800
+ },
85801
+ "./engine": {
85802
+ types: "./dist/engine/index.d.ts",
85803
+ import: "./dist/engine/index.js",
85804
+ require: "./dist/engine/index.cjs"
85046
85805
  }
85047
85806
  },
85048
85807
  files: [
@@ -85096,7 +85855,6 @@ var package_default = {
85096
85855
  // ../core/src/adapters/PhotonAdapter.ts
85097
85856
  class PhotonRequestWrapper {
85098
85857
  photonCtx;
85099
- _cachedJson = null;
85100
85858
  constructor(photonCtx) {
85101
85859
  this.photonCtx = photonCtx;
85102
85860
  }
@@ -87711,6 +88469,661 @@ class ServiceProvider {
87711
88469
  return Array.from(ServiceProvider.publishables.keys());
87712
88470
  }
87713
88471
  }
88472
+ // ../core/src/engine/AOTRouter.ts
88473
+ class AOTRouter {
88474
+ staticRoutes = new Map;
88475
+ dynamicRouter = new RadixRouter;
88476
+ globalMiddleware = [];
88477
+ pathMiddleware = new Map;
88478
+ add(method, path4, handler, middleware = []) {
88479
+ const normalizedMethod = method.toLowerCase();
88480
+ if (this.isStaticPath(path4)) {
88481
+ const key = `${normalizedMethod}:${path4}`;
88482
+ this.staticRoutes.set(key, { handler, middleware });
88483
+ } else {
88484
+ const wrappedHandler = handler;
88485
+ this.dynamicRouter.add(normalizedMethod, path4, [wrappedHandler]);
88486
+ if (middleware.length > 0) {
88487
+ this.pathMiddleware.set(`${normalizedMethod}:${path4}`, middleware);
88488
+ }
88489
+ }
88490
+ }
88491
+ use(...middleware) {
88492
+ this.globalMiddleware.push(...middleware);
88493
+ }
88494
+ usePattern(pattern, ...middleware) {
88495
+ const existing = this.pathMiddleware.get(pattern) ?? [];
88496
+ this.pathMiddleware.set(pattern, [...existing, ...middleware]);
88497
+ }
88498
+ match(method, path4) {
88499
+ const normalizedMethod = method.toLowerCase();
88500
+ const staticKey = `${normalizedMethod}:${path4}`;
88501
+ const staticRoute = this.staticRoutes.get(staticKey);
88502
+ if (staticRoute) {
88503
+ return {
88504
+ handler: staticRoute.handler,
88505
+ params: {},
88506
+ middleware: this.collectMiddleware(path4, staticRoute.middleware)
88507
+ };
88508
+ }
88509
+ const match2 = this.dynamicRouter.match(normalizedMethod, path4);
88510
+ if (match2 && match2.handlers.length > 0) {
88511
+ const handler = match2.handlers[0];
88512
+ const routeKey = this.findDynamicRouteKey(normalizedMethod, path4);
88513
+ const routeMiddleware = routeKey ? this.pathMiddleware.get(routeKey) ?? [] : [];
88514
+ return {
88515
+ handler,
88516
+ params: match2.params,
88517
+ middleware: this.collectMiddleware(path4, routeMiddleware)
88518
+ };
88519
+ }
88520
+ return {
88521
+ handler: null,
88522
+ params: {},
88523
+ middleware: []
88524
+ };
88525
+ }
88526
+ collectMiddlewarePublic(path4, routeMiddleware) {
88527
+ return this.collectMiddleware(path4, routeMiddleware);
88528
+ }
88529
+ collectMiddleware(path4, routeMiddleware) {
88530
+ if (this.globalMiddleware.length === 0 && this.pathMiddleware.size === 0 && routeMiddleware.length === 0) {
88531
+ return [];
88532
+ }
88533
+ const middleware = [];
88534
+ if (this.globalMiddleware.length > 0) {
88535
+ middleware.push(...this.globalMiddleware);
88536
+ }
88537
+ if (this.pathMiddleware.size > 0) {
88538
+ for (const [pattern, mw] of this.pathMiddleware) {
88539
+ if (pattern.includes(":"))
88540
+ continue;
88541
+ if (this.matchPattern(pattern, path4)) {
88542
+ middleware.push(...mw);
88543
+ }
88544
+ }
88545
+ }
88546
+ if (routeMiddleware.length > 0) {
88547
+ middleware.push(...routeMiddleware);
88548
+ }
88549
+ return middleware;
88550
+ }
88551
+ isStaticPath(path4) {
88552
+ return !path4.includes(":") && !path4.includes("*");
88553
+ }
88554
+ matchPattern(pattern, path4) {
88555
+ if (pattern === "*")
88556
+ return true;
88557
+ if (pattern === path4)
88558
+ return true;
88559
+ if (pattern.endsWith("/*")) {
88560
+ const prefix = pattern.slice(0, -2);
88561
+ return path4.startsWith(prefix);
88562
+ }
88563
+ return false;
88564
+ }
88565
+ findDynamicRouteKey(method, path4) {
88566
+ for (const key of this.pathMiddleware.keys()) {
88567
+ if (key.startsWith(`${method}:`)) {
88568
+ return key;
88569
+ }
88570
+ }
88571
+ return null;
88572
+ }
88573
+ getRoutes() {
88574
+ const routes = [];
88575
+ for (const key of this.staticRoutes.keys()) {
88576
+ const [method, path4] = key.split(":");
88577
+ routes.push({ method, path: path4, type: "static" });
88578
+ }
88579
+ return routes;
88580
+ }
88581
+ }
88582
+
88583
+ // ../core/src/engine/analyzer.ts
88584
+ function analyzeHandler(handler) {
88585
+ const source = handler.toString();
88586
+ return {
88587
+ usesHeaders: source.includes(".header(") || source.includes(".header)") || source.includes(".headers(") || source.includes(".headers)"),
88588
+ usesQuery: source.includes(".query(") || source.includes(".query)") || source.includes(".queries(") || source.includes(".queries)"),
88589
+ usesBody: source.includes(".json()") || source.includes(".text()") || source.includes(".formData()") || source.includes(".body"),
88590
+ usesParams: source.includes(".param(") || source.includes(".param)") || source.includes(".params(") || source.includes(".params)"),
88591
+ isAsync: source.includes("async") || source.includes("await")
88592
+ };
88593
+ }
88594
+ function getOptimalContextType(analysis) {
88595
+ if (analysis.usesHeaders) {
88596
+ return "fast";
88597
+ }
88598
+ if (!analysis.usesQuery && !analysis.usesBody && !analysis.usesParams) {
88599
+ return "minimal";
88600
+ }
88601
+ if (!analysis.usesQuery && !analysis.usesBody && analysis.usesParams) {
88602
+ return "minimal";
88603
+ }
88604
+ if (analysis.usesBody) {
88605
+ return "full";
88606
+ }
88607
+ return "fast";
88608
+ }
88609
+
88610
+ // ../core/src/engine/FastContext.ts
88611
+ class FastRequestImpl {
88612
+ _request;
88613
+ _params;
88614
+ _url = new URL("http://localhost");
88615
+ _query = null;
88616
+ _headers = null;
88617
+ reset(request, params = {}) {
88618
+ this._request = request;
88619
+ this._params = params;
88620
+ this._url.href = request.url;
88621
+ this._query = null;
88622
+ this._headers = null;
88623
+ }
88624
+ get url() {
88625
+ return this._request.url;
88626
+ }
88627
+ get method() {
88628
+ return this._request.method;
88629
+ }
88630
+ get path() {
88631
+ return this._url.pathname;
88632
+ }
88633
+ param(name) {
88634
+ return this._params[name];
88635
+ }
88636
+ params() {
88637
+ return { ...this._params };
88638
+ }
88639
+ query(name) {
88640
+ if (!this._query) {
88641
+ this._query = this._url.searchParams;
88642
+ }
88643
+ return this._query.get(name) ?? undefined;
88644
+ }
88645
+ queries() {
88646
+ if (!this._query) {
88647
+ this._query = this._url.searchParams;
88648
+ }
88649
+ const result = {};
88650
+ for (const [key, value] of this._query.entries()) {
88651
+ const existing = result[key];
88652
+ if (existing === undefined) {
88653
+ result[key] = value;
88654
+ } else if (Array.isArray(existing)) {
88655
+ existing.push(value);
88656
+ } else {
88657
+ result[key] = [existing, value];
88658
+ }
88659
+ }
88660
+ return result;
88661
+ }
88662
+ header(name) {
88663
+ return this._request.headers.get(name) ?? undefined;
88664
+ }
88665
+ headers() {
88666
+ if (!this._headers) {
88667
+ this._headers = {};
88668
+ for (const [key, value] of this._request.headers.entries()) {
88669
+ this._headers[key] = value;
88670
+ }
88671
+ }
88672
+ return { ...this._headers };
88673
+ }
88674
+ async json() {
88675
+ return this._request.json();
88676
+ }
88677
+ async text() {
88678
+ return this._request.text();
88679
+ }
88680
+ async formData() {
88681
+ return this._request.formData();
88682
+ }
88683
+ get raw() {
88684
+ return this._request;
88685
+ }
88686
+ }
88687
+
88688
+ class FastContext {
88689
+ _req = new FastRequestImpl;
88690
+ _statusCode = 200;
88691
+ _headers = new Headers;
88692
+ reset(request, params = {}) {
88693
+ this._req.reset(request, params);
88694
+ this._statusCode = 200;
88695
+ this._headers = new Headers;
88696
+ return this;
88697
+ }
88698
+ get req() {
88699
+ return this._req;
88700
+ }
88701
+ json(data2, status = 200) {
88702
+ this._headers.set("Content-Type", "application/json; charset=utf-8");
88703
+ return new Response(JSON.stringify(data2), {
88704
+ status,
88705
+ headers: this._headers
88706
+ });
88707
+ }
88708
+ text(text, status = 200) {
88709
+ this._headers.set("Content-Type", "text/plain; charset=utf-8");
88710
+ return new Response(text, {
88711
+ status,
88712
+ headers: this._headers
88713
+ });
88714
+ }
88715
+ html(html, status = 200) {
88716
+ this._headers.set("Content-Type", "text/html; charset=utf-8");
88717
+ return new Response(html, {
88718
+ status,
88719
+ headers: this._headers
88720
+ });
88721
+ }
88722
+ redirect(url, status = 302) {
88723
+ this._headers.set("Location", url);
88724
+ return new Response(null, {
88725
+ status,
88726
+ headers: this._headers
88727
+ });
88728
+ }
88729
+ body(data2, status = 200) {
88730
+ return new Response(data2, {
88731
+ status,
88732
+ headers: this._headers
88733
+ });
88734
+ }
88735
+ header(name, value) {
88736
+ this._headers.set(name, value);
88737
+ }
88738
+ status(code) {
88739
+ this._statusCode = code;
88740
+ }
88741
+ }
88742
+
88743
+ // ../core/src/engine/MinimalContext.ts
88744
+ class MinimalRequest {
88745
+ _request;
88746
+ _params;
88747
+ _path;
88748
+ constructor(_request, _params, _path) {
88749
+ this._request = _request;
88750
+ this._params = _params;
88751
+ this._path = _path;
88752
+ }
88753
+ get url() {
88754
+ return this._request.url;
88755
+ }
88756
+ get method() {
88757
+ return this._request.method;
88758
+ }
88759
+ get path() {
88760
+ return this._path;
88761
+ }
88762
+ param(name) {
88763
+ return this._params[name];
88764
+ }
88765
+ params() {
88766
+ return { ...this._params };
88767
+ }
88768
+ query(name) {
88769
+ const url = new URL(this._request.url);
88770
+ return url.searchParams.get(name) ?? undefined;
88771
+ }
88772
+ queries() {
88773
+ const url = new URL(this._request.url);
88774
+ const result = {};
88775
+ for (const [key, value] of url.searchParams.entries()) {
88776
+ const existing = result[key];
88777
+ if (existing === undefined) {
88778
+ result[key] = value;
88779
+ } else if (Array.isArray(existing)) {
88780
+ existing.push(value);
88781
+ } else {
88782
+ result[key] = [existing, value];
88783
+ }
88784
+ }
88785
+ return result;
88786
+ }
88787
+ header(name) {
88788
+ return this._request.headers.get(name) ?? undefined;
88789
+ }
88790
+ headers() {
88791
+ const result = {};
88792
+ for (const [key, value] of this._request.headers.entries()) {
88793
+ result[key] = value;
88794
+ }
88795
+ return result;
88796
+ }
88797
+ async json() {
88798
+ return this._request.json();
88799
+ }
88800
+ async text() {
88801
+ return this._request.text();
88802
+ }
88803
+ async formData() {
88804
+ return this._request.formData();
88805
+ }
88806
+ get raw() {
88807
+ return this._request;
88808
+ }
88809
+ }
88810
+
88811
+ class MinimalContext {
88812
+ _req;
88813
+ constructor(request, params, path4) {
88814
+ this._req = new MinimalRequest(request, params, path4);
88815
+ }
88816
+ get req() {
88817
+ return this._req;
88818
+ }
88819
+ json(data2, status = 200) {
88820
+ return new Response(JSON.stringify(data2), {
88821
+ status,
88822
+ headers: { "Content-Type": "application/json; charset=utf-8" }
88823
+ });
88824
+ }
88825
+ text(text, status = 200) {
88826
+ return new Response(text, {
88827
+ status,
88828
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
88829
+ });
88830
+ }
88831
+ html(html, status = 200) {
88832
+ return new Response(html, {
88833
+ status,
88834
+ headers: { "Content-Type": "text/html; charset=utf-8" }
88835
+ });
88836
+ }
88837
+ redirect(url, status = 302) {
88838
+ return new Response(null, {
88839
+ status,
88840
+ headers: { Location: url }
88841
+ });
88842
+ }
88843
+ body(data2, status = 200) {
88844
+ return new Response(data2, { status });
88845
+ }
88846
+ header(_name, _value) {
88847
+ console.warn("MinimalContext.header() is a no-op. Use FastContext for custom headers.");
88848
+ }
88849
+ status(_code) {}
88850
+ reset(_request, _params) {
88851
+ throw new Error("MinimalContext does not support reset. Create a new instance instead.");
88852
+ }
88853
+ }
88854
+
88855
+ // ../core/src/engine/path.ts
88856
+ function extractPath(url) {
88857
+ const protocolEnd = url.indexOf("://");
88858
+ const searchStart = protocolEnd === -1 ? 0 : protocolEnd + 3;
88859
+ const pathStart = url.indexOf("/", searchStart);
88860
+ if (pathStart === -1) {
88861
+ return "/";
88862
+ }
88863
+ const queryStart = url.indexOf("?", pathStart);
88864
+ if (queryStart === -1) {
88865
+ return url.slice(pathStart);
88866
+ }
88867
+ return url.slice(pathStart, queryStart);
88868
+ }
88869
+
88870
+ // ../core/src/engine/pool.ts
88871
+ class ObjectPool {
88872
+ pool = [];
88873
+ factory;
88874
+ reset;
88875
+ maxSize;
88876
+ constructor(factory, reset, maxSize = 256) {
88877
+ this.factory = factory;
88878
+ this.reset = reset;
88879
+ this.maxSize = maxSize;
88880
+ }
88881
+ acquire() {
88882
+ const obj = this.pool.pop();
88883
+ if (obj !== undefined) {
88884
+ return obj;
88885
+ }
88886
+ return this.factory();
88887
+ }
88888
+ release(obj) {
88889
+ if (this.pool.length < this.maxSize) {
88890
+ this.reset(obj);
88891
+ this.pool.push(obj);
88892
+ }
88893
+ }
88894
+ clear() {
88895
+ this.pool = [];
88896
+ }
88897
+ get size() {
88898
+ return this.pool.length;
88899
+ }
88900
+ get capacity() {
88901
+ return this.maxSize;
88902
+ }
88903
+ prewarm(count) {
88904
+ const targetSize = Math.min(count, this.maxSize);
88905
+ while (this.pool.length < targetSize) {
88906
+ const obj = this.factory();
88907
+ this.reset(obj);
88908
+ this.pool.push(obj);
88909
+ }
88910
+ }
88911
+ }
88912
+
88913
+ // ../core/src/engine/Gravito.ts
88914
+ class Gravito {
88915
+ router = new AOTRouter;
88916
+ contextPool;
88917
+ errorHandler;
88918
+ notFoundHandler;
88919
+ staticRoutes;
88920
+ isPureStaticApp = true;
88921
+ constructor(options = {}) {
88922
+ const poolSize = options.poolSize ?? 256;
88923
+ this.contextPool = new ObjectPool(() => new FastContext, (ctx) => ctx.reset(new Request("http://localhost")), poolSize);
88924
+ this.contextPool.prewarm(Math.min(32, poolSize));
88925
+ if (options.onError) {
88926
+ this.errorHandler = options.onError;
88927
+ }
88928
+ if (options.onNotFound) {
88929
+ this.notFoundHandler = options.onNotFound;
88930
+ }
88931
+ this.compileRoutes();
88932
+ }
88933
+ get(path4, ...handlers) {
88934
+ return this.addRoute("get", path4, handlers);
88935
+ }
88936
+ post(path4, ...handlers) {
88937
+ return this.addRoute("post", path4, handlers);
88938
+ }
88939
+ put(path4, ...handlers) {
88940
+ return this.addRoute("put", path4, handlers);
88941
+ }
88942
+ delete(path4, ...handlers) {
88943
+ return this.addRoute("delete", path4, handlers);
88944
+ }
88945
+ patch(path4, ...handlers) {
88946
+ return this.addRoute("patch", path4, handlers);
88947
+ }
88948
+ options(path4, ...handlers) {
88949
+ return this.addRoute("options", path4, handlers);
88950
+ }
88951
+ head(path4, ...handlers) {
88952
+ return this.addRoute("head", path4, handlers);
88953
+ }
88954
+ all(path4, ...handlers) {
88955
+ const methods = ["get", "post", "put", "delete", "patch", "options", "head"];
88956
+ for (const method of methods) {
88957
+ this.addRoute(method, path4, handlers);
88958
+ }
88959
+ return this;
88960
+ }
88961
+ use(pathOrMiddleware, ...middleware) {
88962
+ this.isPureStaticApp = false;
88963
+ if (typeof pathOrMiddleware === "string") {
88964
+ this.router.usePattern(pathOrMiddleware, ...middleware);
88965
+ } else {
88966
+ this.router.use(pathOrMiddleware, ...middleware);
88967
+ }
88968
+ return this;
88969
+ }
88970
+ route(path4, app2) {
88971
+ console.warn("route() method is not yet fully implemented");
88972
+ return this;
88973
+ }
88974
+ onError(handler) {
88975
+ this.errorHandler = handler;
88976
+ return this;
88977
+ }
88978
+ notFound(handler) {
88979
+ this.notFoundHandler = handler;
88980
+ return this;
88981
+ }
88982
+ fetch = (request) => {
88983
+ const path4 = extractPath(request.url);
88984
+ const method = request.method.toLowerCase();
88985
+ const staticKey = `${method}:${path4}`;
88986
+ const staticRoute = this.staticRoutes.get(staticKey);
88987
+ if (staticRoute) {
88988
+ if (staticRoute.useMinimal) {
88989
+ const ctx = new MinimalContext(request, {}, path4);
88990
+ try {
88991
+ const result = staticRoute.handler(ctx);
88992
+ if (result instanceof Response) {
88993
+ return result;
88994
+ }
88995
+ return result;
88996
+ } catch (error) {
88997
+ return this.handleErrorSync(error, request, path4);
88998
+ }
88999
+ }
89000
+ return this.handleWithMiddleware(request, path4, staticRoute);
89001
+ }
89002
+ return this.handleDynamicRoute(request, method, path4);
89003
+ };
89004
+ async handleWithMiddleware(request, path4, route) {
89005
+ const ctx = this.contextPool.acquire();
89006
+ try {
89007
+ ctx.reset(request, {});
89008
+ const middleware = this.collectMiddlewareForPath(path4, route.middleware);
89009
+ if (middleware.length === 0) {
89010
+ return await route.handler(ctx);
89011
+ }
89012
+ return await this.executeMiddleware(ctx, middleware, route.handler);
89013
+ } catch (error) {
89014
+ return await this.handleError(error, ctx);
89015
+ } finally {
89016
+ this.contextPool.release(ctx);
89017
+ }
89018
+ }
89019
+ handleDynamicRoute(request, method, path4) {
89020
+ const match2 = this.router.match(method.toUpperCase(), path4);
89021
+ if (!match2.handler) {
89022
+ return this.handleNotFoundSync(request, path4);
89023
+ }
89024
+ const ctx = this.contextPool.acquire();
89025
+ const execute = async () => {
89026
+ try {
89027
+ ctx.reset(request, match2.params);
89028
+ if (match2.middleware.length === 0) {
89029
+ return await match2.handler(ctx);
89030
+ }
89031
+ return await this.executeMiddleware(ctx, match2.middleware, match2.handler);
89032
+ } catch (error) {
89033
+ return await this.handleError(error, ctx);
89034
+ } finally {
89035
+ this.contextPool.release(ctx);
89036
+ }
89037
+ };
89038
+ return execute();
89039
+ }
89040
+ handleErrorSync(error, request, path4) {
89041
+ if (this.errorHandler) {
89042
+ const ctx = new MinimalContext(request, {}, path4);
89043
+ const result = this.errorHandler(error, ctx);
89044
+ if (result instanceof Response) {
89045
+ return result;
89046
+ }
89047
+ return result;
89048
+ }
89049
+ console.error("Unhandled error:", error);
89050
+ return new Response(JSON.stringify({
89051
+ error: "Internal Server Error",
89052
+ message: error.message
89053
+ }), {
89054
+ status: 500,
89055
+ headers: { "Content-Type": "application/json" }
89056
+ });
89057
+ }
89058
+ handleNotFoundSync(request, path4) {
89059
+ if (this.notFoundHandler) {
89060
+ const ctx = new MinimalContext(request, {}, path4);
89061
+ const result = this.notFoundHandler(ctx);
89062
+ if (result instanceof Response) {
89063
+ return result;
89064
+ }
89065
+ return result;
89066
+ }
89067
+ return new Response(JSON.stringify({ error: "Not Found" }), {
89068
+ status: 404,
89069
+ headers: { "Content-Type": "application/json" }
89070
+ });
89071
+ }
89072
+ collectMiddlewareForPath(path4, routeMiddleware) {
89073
+ if (this.router.globalMiddleware.length === 0 && this.router.pathMiddleware.size === 0) {
89074
+ return routeMiddleware;
89075
+ }
89076
+ return this.router.collectMiddlewarePublic(path4, routeMiddleware);
89077
+ }
89078
+ compileRoutes() {
89079
+ this.staticRoutes = this.router.staticRoutes;
89080
+ const hasGlobalMiddleware = this.router.globalMiddleware.length > 0;
89081
+ const hasPathMiddleware = this.router.pathMiddleware.size > 0;
89082
+ this.isPureStaticApp = !hasGlobalMiddleware && !hasPathMiddleware;
89083
+ for (const [_key, route] of this.staticRoutes) {
89084
+ const analysis = analyzeHandler(route.handler);
89085
+ const optimalType = getOptimalContextType(analysis);
89086
+ route.useMinimal = this.isPureStaticApp && route.middleware.length === 0 && optimalType === "minimal";
89087
+ }
89088
+ }
89089
+ addRoute(method, path4, handlers) {
89090
+ if (handlers.length === 0) {
89091
+ throw new Error(`No handler provided for ${method.toUpperCase()} ${path4}`);
89092
+ }
89093
+ const handler = handlers[handlers.length - 1];
89094
+ const middleware = handlers.slice(0, -1);
89095
+ this.router.add(method, path4, handler, middleware);
89096
+ this.compileRoutes();
89097
+ return this;
89098
+ }
89099
+ async executeMiddleware(ctx, middleware, handler) {
89100
+ let index = 0;
89101
+ const next = async () => {
89102
+ if (index < middleware.length) {
89103
+ const mw = middleware[index++];
89104
+ await mw(ctx, next);
89105
+ }
89106
+ };
89107
+ await next();
89108
+ return await handler(ctx);
89109
+ }
89110
+ async handleNotFound(ctx) {
89111
+ if (this.notFoundHandler) {
89112
+ return await this.notFoundHandler(ctx);
89113
+ }
89114
+ return ctx.json({ error: "Not Found" }, 404);
89115
+ }
89116
+ async handleError(error, ctx) {
89117
+ if (this.errorHandler) {
89118
+ return await this.errorHandler(error, ctx);
89119
+ }
89120
+ console.error("Unhandled error:", error);
89121
+ return ctx.json({
89122
+ error: "Internal Server Error",
89123
+ message: error.message
89124
+ }, 500);
89125
+ }
89126
+ }
87714
89127
  // ../core/src/index.ts
87715
89128
  var VERSION = package_default.version;
87716
89129
 
@@ -88259,6 +89672,11 @@ async function initCommand(options = {}) {
88259
89672
  value: "ddd",
88260
89673
  label: "\uD83C\uDFDB\uFE0F Domain-Driven Design (DDD)",
88261
89674
  hint: "\u5B8C\u6574\u7684 DDD \u67B6\u69CB\uFF0C\u5305\u542B Modules\u3001Bounded Contexts"
89675
+ },
89676
+ {
89677
+ value: "action-domain",
89678
+ label: "\u26A1 Action-Domain-Responder (ADR)",
89679
+ hint: "Web \u7279\u5316\u7684\u8A2D\u8A08\u6A21\u5F0F\uFF0C\u95DC\u6CE8 Actions \u8207 Domain \u908F\u8F2F\u7684\u5206\u96E2"
88262
89680
  }
88263
89681
  ]
88264
89682
  });
@@ -88367,7 +89785,8 @@ async function initCommand(options = {}) {
88367
89785
  "enterprise-mvc": "\uD83D\uDCE6 Enterprise MVC",
88368
89786
  clean: "\uD83E\uDDC5 Clean Architecture",
88369
89787
  ddd: "\uD83C\uDFDB\uFE0F Domain-Driven Design",
88370
- satellite: "\uD83D\uDEF0\uFE0F Satellite Service"
89788
+ satellite: "\uD83D\uDEF0\uFE0F Satellite Service",
89789
+ "action-domain": "\u26A1 Action-Domain-Responder"
88371
89790
  };
88372
89791
  note2(`\u5C08\u6848\u540D\u7A31: ${pc3.cyan(projectName)}
88373
89792
  \u67B6\u69CB\u6A21\u5F0F: ${pc3.green(archLabels[architecture])}
@@ -89922,7 +91341,7 @@ cli.command("schedule:list", "List scheduled tasks").option("--entry <file>", "E
89922
91341
  });
89923
91342
  cli.command("create [name]", "Create a new Gravito project").option("--template <template>", "Template to use (basic, inertia-react)").option("--profile <profile>", "Profile preset (core, scale, enterprise)", { default: "core" }).option("--with <features>", "Feature add-ons (comma-separated, e.g. redis,queue)", {
89924
91343
  default: ""
89925
- }).option("--recommend", "Auto-detect profile based on environment").action(async (name, options) => {
91344
+ }).option("--recommend", "Auto-detect profile based on environment").option("--framework <framework>", "Frontend framework (react, vue) for static-site template").action(async (name, options) => {
89926
91345
  console.clear();
89927
91346
  intro2(pc11.bgBlack(pc11.white(" \uD83C\uDF0C Gravito CLI ")));
89928
91347
  if (options.recommend) {
@@ -90016,26 +91435,30 @@ Confidence: ${detection.confidence}`, "Environment Detection");
90016
91435
  }
90017
91436
  let framework = null;
90018
91437
  if (project.template === "static-site") {
90019
- const frameworkResult = await select2({
90020
- message: "Choose your frontend framework:",
90021
- options: [
90022
- {
90023
- value: "react",
90024
- label: "\u269B\uFE0F React",
90025
- hint: "Recommended for most projects"
90026
- },
90027
- {
90028
- value: "vue",
90029
- label: "\uD83D\uDFE2 Vue 3",
90030
- hint: "Composition API with TypeScript"
90031
- }
90032
- ]
90033
- });
90034
- if (isCancel2(frameworkResult)) {
90035
- cancel2("Operation cancelled.");
90036
- process.exit(0);
91438
+ if (options.framework) {
91439
+ framework = options.framework;
91440
+ } else {
91441
+ const frameworkResult = await select2({
91442
+ message: "Choose your frontend framework:",
91443
+ options: [
91444
+ {
91445
+ value: "react",
91446
+ label: "\u269B\uFE0F React",
91447
+ hint: "Recommended for most projects"
91448
+ },
91449
+ {
91450
+ value: "vue",
91451
+ label: "\uD83D\uDFE2 Vue 3",
91452
+ hint: "Composition API with TypeScript"
91453
+ }
91454
+ ]
91455
+ });
91456
+ if (isCancel2(frameworkResult)) {
91457
+ cancel2("Operation cancelled.");
91458
+ process.exit(0);
91459
+ }
91460
+ framework = frameworkResult;
90037
91461
  }
90038
- framework = frameworkResult;
90039
91462
  }
90040
91463
  const s = spinner5();
90041
91464
  s.start("Scaffolding your universe...");