@gravito/pulse 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +987 -244
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -81110,6 +81110,7 @@ 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);
81113
81114
  }
81114
81115
  async applyOverlays(context) {
81115
81116
  const profile = context.profile;
@@ -81181,6 +81182,10 @@ class BaseGenerator {
81181
81182
  start: "bun run dist/bootstrap.js",
81182
81183
  test: "bun test",
81183
81184
  typecheck: "tsc --noEmit",
81185
+ check: "bun run typecheck && bun run test",
81186
+ "check:deps": "bun run scripts/check-dependencies.ts",
81187
+ validate: "bun run check && bun run check:deps",
81188
+ precommit: "bun run validate",
81184
81189
  "docker:build": `docker build -t ${context.nameKebabCase} .`,
81185
81190
  "docker:run": `docker run -it -p 3000:3000 ${context.nameKebabCase}`
81186
81191
  },
@@ -81418,6 +81423,411 @@ coverage/
81418
81423
  };
81419
81424
  return JSON.stringify(config, null, 2);
81420
81425
  }
81426
+ async generateCheckScripts(context) {
81427
+ const scriptsDir = path2.resolve(context.targetDir, "scripts");
81428
+ await fs2.mkdir(scriptsDir, { recursive: true });
81429
+ await this.writeFile(scriptsDir, "check-dependencies.ts", this.generateCheckDependenciesScript());
81430
+ await this.writeFile(scriptsDir, "check.sh", this.generateCheckShellScript());
81431
+ await this.writeFile(scriptsDir, "pre-commit.sh", this.generatePreCommitScript());
81432
+ await this.writeFile(context.targetDir, "CHECK_SYSTEM.md", this.generateCheckSystemDoc(context));
81433
+ }
81434
+ generateCheckDependenciesScript() {
81435
+ return `/**
81436
+ * \u76F8\u4F9D\u5957\u4EF6\u7248\u672C\u6AA2\u67E5\u8173\u672C
81437
+ *
81438
+ * \u6AA2\u67E5 package.json \u4E2D\u7684\u5957\u4EF6\u662F\u5426\u70BA\u6700\u65B0\u7A69\u5B9A\u7248\u672C
81439
+ * \u4E26\u63D0\u4F9B\u66F4\u65B0\u5EFA\u8B70
81440
+ */
81441
+
81442
+ import { readFileSync } from 'fs'
81443
+ import { join } from 'path'
81444
+
81445
+ interface PackageJson {
81446
+ dependencies?: Record<string, string>
81447
+ devDependencies?: Record<string, string>
81448
+ }
81449
+
81450
+ interface PackageInfo {
81451
+ name: string
81452
+ current: string
81453
+ latest: string
81454
+ outdated: boolean
81455
+ }
81456
+
81457
+ const colors = {
81458
+ reset: '\\x1b[0m',
81459
+ green: '\\x1b[32m',
81460
+ yellow: '\\x1b[33m',
81461
+ red: '\\x1b[31m',
81462
+ blue: '\\x1b[36m',
81463
+ }
81464
+
81465
+ function log(message: string, color: keyof typeof colors = 'reset') {
81466
+ console.log(\`\${colors[color]}\${message}\${colors.reset}\`)
81467
+ }
81468
+
81469
+ async function getLatestVersion(packageName: string): Promise<string | null> {
81470
+ try {
81471
+ const response = await fetch(\`https://registry.npmjs.org/\${packageName}/latest\`)
81472
+ if (!response.ok) return null
81473
+ const data = await response.json()
81474
+ return data.version
81475
+ } catch {
81476
+ return null
81477
+ }
81478
+ }
81479
+
81480
+ function parseVersion(version: string): string {
81481
+ // \u79FB\u9664 ^, ~, >= \u7B49\u524D\u7DB4
81482
+ return version.replace(/^[\\^~>=<]/, '')
81483
+ }
81484
+
81485
+ async function checkPackage(
81486
+ name: string,
81487
+ currentVersion: string
81488
+ ): Promise<PackageInfo | null> {
81489
+ // \u8DF3\u904E\u672C\u5730\u9023\u7D50\u7684\u5957\u4EF6
81490
+ if (currentVersion.startsWith('link:') || currentVersion.startsWith('workspace:')) {
81491
+ return null
81492
+ }
81493
+
81494
+ const current = parseVersion(currentVersion)
81495
+ const latest = await getLatestVersion(name)
81496
+
81497
+ if (!latest) {
81498
+ return null
81499
+ }
81500
+
81501
+ return {
81502
+ name,
81503
+ current,
81504
+ latest,
81505
+ outdated: current !== latest,
81506
+ }
81507
+ }
81508
+
81509
+ async function main() {
81510
+ log('\\n=== \u76F8\u4F9D\u5957\u4EF6\u7248\u672C\u6AA2\u67E5 ===\\n', 'blue')
81511
+
81512
+ const packageJsonPath = join(process.cwd(), 'package.json')
81513
+ const packageJson: PackageJson = JSON.parse(
81514
+ readFileSync(packageJsonPath, 'utf-8')
81515
+ )
81516
+
81517
+ const allDependencies = {
81518
+ ...packageJson.dependencies,
81519
+ ...packageJson.devDependencies,
81520
+ }
81521
+
81522
+ log(\`\u6AA2\u67E5 \${Object.keys(allDependencies).length} \u500B\u5957\u4EF6...\\n\`, 'yellow')
81523
+
81524
+ const results: PackageInfo[] = []
81525
+ const outdated: PackageInfo[] = []
81526
+ const upToDate: PackageInfo[] = []
81527
+
81528
+ // \u6AA2\u67E5\u6240\u6709\u5957\u4EF6
81529
+ for (const [name, version] of Object.entries(allDependencies)) {
81530
+ const info = await checkPackage(name, version)
81531
+ if (info) {
81532
+ results.push(info)
81533
+ if (info.outdated) {
81534
+ outdated.push(info)
81535
+ } else {
81536
+ upToDate.push(info)
81537
+ }
81538
+ }
81539
+ }
81540
+
81541
+ // \u986F\u793A\u7D50\u679C
81542
+ if (upToDate.length > 0) {
81543
+ log(\`\\n\u2713 \u5DF2\u662F\u6700\u65B0\u7248\u672C (\${upToDate.length}):\`, 'green')
81544
+ upToDate.forEach((pkg) => {
81545
+ log(\` \${pkg.name}: \${pkg.current}\`, 'green')
81546
+ })
81547
+ }
81548
+
81549
+ if (outdated.length > 0) {
81550
+ log(\`\\n\u26A0 \u9700\u8981\u66F4\u65B0 (\${outdated.length}):\`, 'yellow')
81551
+ outdated.forEach((pkg) => {
81552
+ log(\` \${pkg.name}: \${pkg.current} \u2192 \${pkg.latest}\`, 'yellow')
81553
+ })
81554
+ }
81555
+
81556
+ // \u7E3D\u7D50
81557
+ log('\\n=== \u6AA2\u67E5\u7D50\u679C ===', 'blue')
81558
+ log(\`\u7E3D\u8A08: \${results.length} \u500B\u5957\u4EF6\`, 'blue')
81559
+ log(\`\u6700\u65B0: \${upToDate.length} \u500B\`, 'green')
81560
+ log(\`\u9700\u66F4\u65B0: \${outdated.length} \u500B\`, outdated.length > 0 ? 'yellow' : 'green')
81561
+
81562
+ // \u5982\u679C\u6709\u9700\u8981\u66F4\u65B0\u7684\u5957\u4EF6\uFF0C\u8FD4\u56DE\u975E\u96F6\u9000\u51FA\u78BC
81563
+ if (outdated.length > 0) {
81564
+ log('\\n\u5EFA\u8B70\u57F7\u884C\u4EE5\u4E0B\u547D\u4EE4\u66F4\u65B0\u5957\u4EF6\uFF1A', 'yellow')
81565
+ log(' bun update', 'yellow')
81566
+ process.exit(1)
81567
+ } else {
81568
+ log('\\n\u2713 \u6240\u6709\u5957\u4EF6\u90FD\u662F\u6700\u65B0\u7248\u672C\uFF01', 'green')
81569
+ process.exit(0)
81570
+ }
81571
+ }
81572
+
81573
+ main().catch((error) => {
81574
+ log(\`\\n\u932F\u8AA4: \${error.message}\`, 'red')
81575
+ process.exit(1)
81576
+ })
81577
+ `;
81578
+ }
81579
+ generateCheckShellScript() {
81580
+ return `#!/bin/bash
81581
+
81582
+ # \u5C08\u6848\u6AA2\u67E5\u8173\u672C
81583
+ # \u57F7\u884C\u6240\u6709\u5FC5\u8981\u7684\u6AA2\u67E5\uFF1A\u985E\u578B\u6AA2\u67E5\u3001\u6E2C\u8A66\u3001\u4F9D\u8CF4\u6AA2\u67E5\u7B49
81584
+
81585
+ set -e
81586
+
81587
+ # \u984F\u8272\u5B9A\u7FA9
81588
+ GREEN='\\033[0;32m'
81589
+ YELLOW='\\033[1;33m'
81590
+ RED='\\033[0;31m'
81591
+ BLUE='\\033[0;34m'
81592
+ NC='\\033[0m' # No Color
81593
+
81594
+ echo -e "\${BLUE}=== \u5C08\u6848\u6AA2\u67E5 ===\${NC}\\n"
81595
+
81596
+ # \u6AA2\u67E5\u662F\u5426\u5728\u6B63\u78BA\u7684\u76EE\u9304
81597
+ if [ ! -f "package.json" ]; then
81598
+ echo -e "\${RED}\u932F\u8AA4: \u8ACB\u5728\u5C08\u6848\u6839\u76EE\u9304\u57F7\u884C\u6B64\u8173\u672C\${NC}"
81599
+ exit 1
81600
+ fi
81601
+
81602
+ # \u6AA2\u67E5 Bun \u662F\u5426\u5B89\u88DD
81603
+ if ! command -v bun &> /dev/null; then
81604
+ echo -e "\${RED}\u932F\u8AA4: \u672A\u627E\u5230 bun\uFF0C\u8ACB\u5148\u5B89\u88DD Bun\${NC}"
81605
+ exit 1
81606
+ fi
81607
+
81608
+ # 1. \u985E\u578B\u6AA2\u67E5
81609
+ echo -e "\${YELLOW}[1/3] \u57F7\u884C\u985E\u578B\u6AA2\u67E5...\${NC}"
81610
+ if bun run typecheck; then
81611
+ echo -e "\${GREEN}\u2713 \u985E\u578B\u6AA2\u67E5\u901A\u904E\${NC}\\n"
81612
+ else
81613
+ echo -e "\${RED}\u2717 \u985E\u578B\u6AA2\u67E5\u5931\u6557\${NC}"
81614
+ exit 1
81615
+ fi
81616
+
81617
+ # 2. \u57F7\u884C\u6E2C\u8A66
81618
+ echo -e "\${YELLOW}[2/3] \u57F7\u884C\u6E2C\u8A66...\${NC}"
81619
+ if bun test; then
81620
+ echo -e "\${GREEN}\u2713 \u6E2C\u8A66\u901A\u904E\${NC}\\n"
81621
+ else
81622
+ echo -e "\${RED}\u2717 \u6E2C\u8A66\u5931\u6557\${NC}"
81623
+ exit 1
81624
+ fi
81625
+
81626
+ # 3. \u6AA2\u67E5\u4F9D\u8CF4\u7248\u672C\uFF08\u53EF\u9078\uFF0C\u56E0\u70BA\u9700\u8981\u7DB2\u8DEF\u9023\u7DDA\uFF09
81627
+ echo -e "\${YELLOW}[3/3] \u6AA2\u67E5\u4F9D\u8CF4\u7248\u672C...\${NC}"
81628
+ if bun run check:deps; then
81629
+ echo -e "\${GREEN}\u2713 \u4F9D\u8CF4\u6AA2\u67E5\u5B8C\u6210\${NC}\\n"
81630
+ else
81631
+ 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"
81632
+ fi
81633
+
81634
+ echo -e "\${GREEN}=== \u6240\u6709\u6AA2\u67E5\u5B8C\u6210 ===\${NC}"
81635
+ `;
81636
+ }
81637
+ generatePreCommitScript() {
81638
+ return `#!/bin/bash
81639
+
81640
+ # Pre-commit Hook
81641
+ # \u5728 git commit \u524D\u81EA\u52D5\u57F7\u884C\u6AA2\u67E5
81642
+ #
81643
+ # \u5B89\u88DD\u65B9\u5F0F\uFF1A
81644
+ # ln -s ../../scripts/pre-commit.sh .git/hooks/pre-commit
81645
+ # \u6216
81646
+ # cp scripts/pre-commit.sh .git/hooks/pre-commit
81647
+ # chmod +x .git/hooks/pre-commit
81648
+
81649
+ set -e
81650
+
81651
+ # \u984F\u8272\u5B9A\u7FA9
81652
+ GREEN='\\033[0;32m'
81653
+ YELLOW='\\033[1;33m'
81654
+ RED='\\033[0;31m'
81655
+ BLUE='\\033[0;34m'
81656
+ NC='\\033[0m' # No Color
81657
+
81658
+ echo -e "\${BLUE}=== Pre-commit \u6AA2\u67E5 ===\${NC}\\n"
81659
+
81660
+ # \u5207\u63DB\u5230\u5C08\u6848\u6839\u76EE\u9304
81661
+ cd "$(git rev-parse --show-toplevel)"
81662
+
81663
+ # \u6AA2\u67E5\u662F\u5426\u5728\u6B63\u78BA\u7684\u76EE\u9304
81664
+ if [ ! -f "package.json" ]; then
81665
+ echo -e "\${RED}\u932F\u8AA4: \u627E\u4E0D\u5230 package.json\${NC}"
81666
+ exit 1
81667
+ fi
81668
+
81669
+ # \u6AA2\u67E5 Bun \u662F\u5426\u5B89\u88DD
81670
+ if ! command -v bun &> /dev/null; then
81671
+ echo -e "\${RED}\u932F\u8AA4: \u672A\u627E\u5230 bun\uFF0C\u8ACB\u5148\u5B89\u88DD Bun\${NC}"
81672
+ exit 1
81673
+ fi
81674
+
81675
+ # 1. \u985E\u578B\u6AA2\u67E5\uFF08\u5FEB\u901F\u6AA2\u67E5\uFF09
81676
+ echo -e "\${YELLOW}[1/2] \u57F7\u884C\u985E\u578B\u6AA2\u67E5...\${NC}"
81677
+ if bun run typecheck; then
81678
+ echo -e "\${GREEN}\u2713 \u985E\u578B\u6AA2\u67E5\u901A\u904E\${NC}\\n"
81679
+ else
81680
+ echo -e "\${RED}\u2717 \u985E\u578B\u6AA2\u67E5\u5931\u6557\${NC}"
81681
+ echo -e "\${YELLOW}\u63D0\u793A: \u8ACB\u4FEE\u6B63\u985E\u578B\u932F\u8AA4\u5F8C\u518D\u63D0\u4EA4\${NC}"
81682
+ exit 1
81683
+ fi
81684
+
81685
+ # 2. \u57F7\u884C\u6E2C\u8A66\uFF08\u53EF\u9078\uFF0C\u5982\u679C\u6E2C\u8A66\u6642\u9593\u8F03\u9577\u53EF\u4EE5\u8A3B\u89E3\u6389\uFF09
81686
+ echo -e "\${YELLOW}[2/2] \u57F7\u884C\u6E2C\u8A66...\${NC}"
81687
+ if bun test; then
81688
+ echo -e "\${GREEN}\u2713 \u6E2C\u8A66\u901A\u904E\${NC}\\n"
81689
+ else
81690
+ echo -e "\${RED}\u2717 \u6E2C\u8A66\u5931\u6557\${NC}"
81691
+ echo -e "\${YELLOW}\u63D0\u793A: \u8ACB\u4FEE\u6B63\u6E2C\u8A66\u932F\u8AA4\u5F8C\u518D\u63D0\u4EA4\${NC}"
81692
+ exit 1
81693
+ fi
81694
+
81695
+ echo -e "\${GREEN}=== Pre-commit \u6AA2\u67E5\u901A\u904E ===\${NC}\\n"
81696
+ `;
81697
+ }
81698
+ generateCheckSystemDoc(context) {
81699
+ return `# \u5C08\u6848\u6AA2\u67E5\u7CFB\u7D71
81700
+
81701
+ \u672C\u5C08\u6848\u5DF2\u5EFA\u7ACB\u5B8C\u6574\u7684\u672C\u5730\u6AA2\u67E5\u6A5F\u5236\uFF0C\u7121\u9700\u4F9D\u8CF4 GitHub CI\u3002
81702
+
81703
+ ## \u5FEB\u901F\u958B\u59CB
81704
+
81705
+ ### \u57F7\u884C\u5B8C\u6574\u6AA2\u67E5
81706
+ \`\`\`bash
81707
+ bun run validate
81708
+ \`\`\`
81709
+
81710
+ ### \u57F7\u884C\u55AE\u9805\u6AA2\u67E5
81711
+ \`\`\`bash
81712
+ # \u985E\u578B\u6AA2\u67E5
81713
+ bun run typecheck
81714
+
81715
+ # \u6E2C\u8A66
81716
+ bun run test
81717
+
81718
+ # \u4F9D\u8CF4\u7248\u672C\u6AA2\u67E5
81719
+ bun run check:deps
81720
+ \`\`\`
81721
+
81722
+ ## \u53EF\u7528\u547D\u4EE4
81723
+
81724
+ ### Package.json \u8173\u672C
81725
+
81726
+ | \u547D\u4EE4 | \u8AAA\u660E |
81727
+ |------|------|
81728
+ | \`bun run typecheck\` | TypeScript \u985E\u578B\u6AA2\u67E5 |
81729
+ | \`bun run test\` | \u57F7\u884C\u6240\u6709\u6E2C\u8A66 |
81730
+ | \`bun run check\` | \u985E\u578B\u6AA2\u67E5 + \u6E2C\u8A66 |
81731
+ | \`bun run check:deps\` | \u6AA2\u67E5\u4F9D\u8CF4\u7248\u672C |
81732
+ | \`bun run validate\` | \u5B8C\u6574\u9A57\u8B49\uFF08\u985E\u578B + \u6E2C\u8A66 + \u4F9D\u8CF4\uFF09 |
81733
+ | \`bun run precommit\` | \u7B49\u540C\u65BC \`validate\` |
81734
+
81735
+ ### Shell \u8173\u672C
81736
+
81737
+ | \u8173\u672C | \u8AAA\u660E |
81738
+ |------|------|
81739
+ | \`./scripts/check.sh\` | \u5B8C\u6574\u5C08\u6848\u6AA2\u67E5\uFF08Shell \u7248\u672C\uFF09 |
81740
+ | \`./scripts/pre-commit.sh\` | Pre-commit hook \u8173\u672C |
81741
+
81742
+ ## Pre-commit Hook\uFF08\u63A8\u85A6\uFF09
81743
+
81744
+ \u5B89\u88DD pre-commit hook \u5F8C\uFF0C\u6BCF\u6B21 \`git commit\` \u524D\u6703\u81EA\u52D5\u57F7\u884C\u6AA2\u67E5\uFF1A
81745
+
81746
+ \`\`\`bash
81747
+ # \u5B89\u88DD pre-commit hook
81748
+ ln -s ../../scripts/pre-commit.sh .git/hooks/pre-commit
81749
+
81750
+ # \u6216\u4F7F\u7528\u8907\u88FD\u65B9\u5F0F
81751
+ cp scripts/pre-commit.sh .git/hooks/pre-commit
81752
+ chmod +x .git/hooks/pre-commit
81753
+ \`\`\`
81754
+
81755
+ **\u529F\u80FD\uFF1A**
81756
+ - \u2705 \u81EA\u52D5\u57F7\u884C\u985E\u578B\u6AA2\u67E5
81757
+ - \u2705 \u81EA\u52D5\u57F7\u884C\u6E2C\u8A66
81758
+ - \u274C \u6AA2\u67E5\u5931\u6557\u6642\u963B\u6B62\u63D0\u4EA4
81759
+
81760
+ **\u8DF3\u904E\u6AA2\u67E5\uFF08\u4E0D\u63A8\u85A6\uFF09\uFF1A**
81761
+ \`\`\`bash
81762
+ git commit --no-verify -m "\u7DCA\u6025\u4FEE\u5FA9"
81763
+ \`\`\`
81764
+
81765
+ ## \u6AA2\u67E5\u9805\u76EE
81766
+
81767
+ ### 1. \u985E\u578B\u6AA2\u67E5
81768
+ - \u4F7F\u7528 \`tsc --noEmit\` \u6AA2\u67E5 TypeScript \u985E\u578B
81769
+ - \u78BA\u4FDD\u6C92\u6709\u985E\u578B\u932F\u8AA4
81770
+
81771
+ ### 2. \u6E2C\u8A66
81772
+ - \u57F7\u884C\u6240\u6709\u55AE\u5143\u6E2C\u8A66\u548C\u6574\u5408\u6E2C\u8A66
81773
+ - \u78BA\u4FDD\u6E2C\u8A66\u901A\u904E
81774
+
81775
+ ### 3. \u4F9D\u8CF4\u6AA2\u67E5\uFF08\u53EF\u9078\uFF09
81776
+ - \u6AA2\u67E5\u5957\u4EF6\u7248\u672C\u662F\u5426\u70BA\u6700\u65B0
81777
+ - \u63D0\u4F9B\u66F4\u65B0\u5EFA\u8B70
81778
+ - \u9700\u8981\u7DB2\u8DEF\u9023\u7DDA
81779
+
81780
+ ## \u5DE5\u4F5C\u6D41\u7A0B\u5EFA\u8B70
81781
+
81782
+ ### \u958B\u767C\u6642
81783
+ 1. \u958B\u767C\u529F\u80FD
81784
+ 2. \u63D0\u4EA4\u524D\u57F7\u884C \`bun run validate\`
81785
+ 3. \u4FEE\u6B63\u554F\u984C
81786
+ 4. \u63D0\u4EA4\u7A0B\u5F0F\u78BC
81787
+
81788
+ ### \u4F7F\u7528 Pre-commit Hook\uFF08\u63A8\u85A6\uFF09
81789
+ 1. \u5B89\u88DD pre-commit hook\uFF08\u53EA\u9700\u4E00\u6B21\uFF09
81790
+ 2. \u6B63\u5E38\u958B\u767C\u548C\u63D0\u4EA4
81791
+ 3. \u6AA2\u67E5\u6703\u81EA\u52D5\u57F7\u884C
81792
+ 4. \u5982\u6709\u554F\u984C\uFF0C\u4FEE\u6B63\u5F8C\u91CD\u65B0\u63D0\u4EA4
81793
+
81794
+ ## \u6A94\u6848\u7D50\u69CB
81795
+
81796
+ \`\`\`
81797
+ ${context.nameKebabCase}/
81798
+ \u251C\u2500\u2500 package.json # \u6AA2\u67E5\u8173\u672C\u5B9A\u7FA9
81799
+ \u251C\u2500\u2500 scripts/
81800
+ \u2502 \u251C\u2500\u2500 check.sh # \u5B8C\u6574\u6AA2\u67E5\u8173\u672C\uFF08Shell\uFF09
81801
+ \u2502 \u251C\u2500\u2500 check-dependencies.ts # \u4F9D\u8CF4\u7248\u672C\u6AA2\u67E5
81802
+ \u2502 \u2514\u2500\u2500 pre-commit.sh # Pre-commit hook
81803
+ \u2514\u2500\u2500 CHECK_SYSTEM.md # \u672C\u6587\u4EF6
81804
+ \`\`\`
81805
+
81806
+ ## \u6CE8\u610F\u4E8B\u9805
81807
+
81808
+ 1. **\u4F9D\u8CF4\u6AA2\u67E5\u9700\u8981\u7DB2\u8DEF\u9023\u7DDA**\uFF1A\`check:deps\` \u9700\u8981\u9023\u63A5\u5230 npm registry
81809
+ 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
81810
+ 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
81811
+
81812
+ ## \u6545\u969C\u6392\u9664
81813
+
81814
+ ### \u6AA2\u67E5\u5931\u6557
81815
+ 1. \u67E5\u770B\u932F\u8AA4\u8A0A\u606F
81816
+ 2. \u4FEE\u6B63\u554F\u984C
81817
+ 3. \u91CD\u65B0\u57F7\u884C\u6AA2\u67E5
81818
+
81819
+ ### \u8DF3\u904E\u6AA2\u67E5
81820
+ \u53EA\u6709\u5728\u7DCA\u6025\u60C5\u6CC1\u4E0B\u624D\u4F7F\u7528\uFF1A
81821
+ \`\`\`bash
81822
+ git commit --no-verify
81823
+ \`\`\`
81824
+
81825
+ ### \u79FB\u9664 Pre-commit Hook
81826
+ \`\`\`bash
81827
+ rm .git/hooks/pre-commit
81828
+ \`\`\`
81829
+ `;
81830
+ }
81421
81831
  log(message) {
81422
81832
  if (this.config.verbose) {
81423
81833
  console.log(message);
@@ -81588,6 +81998,11 @@ class CleanArchitectureGenerator extends BaseGenerator {
81588
81998
  type: "directory",
81589
81999
  name: "Providers",
81590
82000
  children: [
82001
+ {
82002
+ type: "file",
82003
+ name: "index.ts",
82004
+ content: this.generateProvidersIndex()
82005
+ },
81591
82006
  {
81592
82007
  type: "file",
81593
82008
  name: "AppServiceProvider.ts",
@@ -81597,6 +82012,16 @@ class CleanArchitectureGenerator extends BaseGenerator {
81597
82012
  type: "file",
81598
82013
  name: "RepositoryServiceProvider.ts",
81599
82014
  content: this.generateRepositoryServiceProvider()
82015
+ },
82016
+ {
82017
+ type: "file",
82018
+ name: "MiddlewareProvider.ts",
82019
+ content: this.generateMiddlewareProvider()
82020
+ },
82021
+ {
82022
+ type: "file",
82023
+ name: "RouteProvider.ts",
82024
+ content: this.generateRouteProvider()
81600
82025
  }
81601
82026
  ]
81602
82027
  }
@@ -82080,6 +82505,65 @@ export class RepositoryServiceProvider extends ServiceProvider {
82080
82505
  container.singleton('mailService', () => new MailService())
82081
82506
  }
82082
82507
  }
82508
+ `;
82509
+ }
82510
+ generateProvidersIndex() {
82511
+ return `/**
82512
+ * Application Service Providers
82513
+ */
82514
+
82515
+ export { AppServiceProvider } from './AppServiceProvider'
82516
+ export { RepositoryServiceProvider } from './RepositoryServiceProvider'
82517
+ export { MiddlewareProvider } from './MiddlewareProvider'
82518
+ export { RouteProvider } from './RouteProvider'
82519
+ `;
82520
+ }
82521
+ generateMiddlewareProvider() {
82522
+ return `/**
82523
+ * Middleware Service Provider
82524
+ */
82525
+
82526
+ import {
82527
+ ServiceProvider,
82528
+ type Container,
82529
+ type PlanetCore,
82530
+ bodySizeLimit,
82531
+ securityHeaders,
82532
+ } from '@gravito/core'
82533
+
82534
+ export class MiddlewareProvider extends ServiceProvider {
82535
+ register(_container: Container): void {}
82536
+
82537
+ boot(core: PlanetCore): void {
82538
+ const isDev = process.env.NODE_ENV !== 'production'
82539
+
82540
+ core.adapter.use('*', securityHeaders({
82541
+ contentSecurityPolicy: isDev ? false : undefined,
82542
+ }))
82543
+
82544
+ core.adapter.use('*', bodySizeLimit(10 * 1024 * 1024))
82545
+
82546
+ core.logger.info('\uD83D\uDEE1\uFE0F Middleware registered')
82547
+ }
82548
+ }
82549
+ `;
82550
+ }
82551
+ generateRouteProvider() {
82552
+ return `/**
82553
+ * Route Service Provider
82554
+ */
82555
+
82556
+ import { ServiceProvider, type Container, type PlanetCore } from '@gravito/core'
82557
+ import { registerApiRoutes } from '../../Interface/Http/Routes/api'
82558
+
82559
+ export class RouteProvider extends ServiceProvider {
82560
+ register(_container: Container): void {}
82561
+
82562
+ boot(core: PlanetCore): void {
82563
+ registerApiRoutes(core.router)
82564
+ core.logger.info('\uD83D\uDEE4\uFE0F Routes registered')
82565
+ }
82566
+ }
82083
82567
  `;
82084
82568
  }
82085
82569
  generateUserController() {
@@ -82161,64 +82645,55 @@ export class UserPresenter {
82161
82645
  }
82162
82646
  `;
82163
82647
  }
82164
- generateBootstrap(context) {
82648
+ generateBootstrap(_context) {
82165
82649
  return `/**
82166
82650
  * Application Bootstrap
82651
+ *
82652
+ * The entry point for your Clean Architecture application.
82653
+ * Uses the ServiceProvider pattern for modular initialization.
82654
+ *
82655
+ * Lifecycle:
82656
+ * 1. Configure: Load app config and orbits
82657
+ * 2. Boot: Initialize PlanetCore
82658
+ * 3. Register Providers: Bind services to container
82659
+ * 4. Bootstrap: Boot all providers
82167
82660
  */
82168
82661
 
82169
- import { bodySizeLimit, PlanetCore, securityHeaders } from '@gravito/core'
82662
+ import { defineConfig, PlanetCore } from '@gravito/core'
82170
82663
  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,
82664
+ import appConfig from '../config/app'
82665
+ import {
82666
+ AppServiceProvider,
82667
+ RepositoryServiceProvider,
82668
+ MiddlewareProvider,
82669
+ RouteProvider,
82670
+ } from './Infrastructure/Providers'
82671
+
82672
+ export async function bootstrap() {
82673
+ // 1. Configure
82674
+ const config = defineConfig({
82675
+ config: appConfig,
82676
+ orbits: [new OrbitAtlas()],
82206
82677
  })
82207
- )
82208
- if (!Number.isNaN(bodyLimit) && bodyLimit > 0) {
82209
- core.adapter.use('*', bodySizeLimit(bodyLimit, { requireContentLength: requireLength }))
82210
- }
82211
82678
 
82212
- await core.orbit(new OrbitAtlas())
82679
+ // 2. Boot Core
82680
+ const core = await PlanetCore.boot(config)
82681
+ core.registerGlobalErrorHandlers()
82213
82682
 
82214
- core.register(new RepositoryServiceProvider())
82215
- core.register(new AppServiceProvider())
82683
+ // 3. Register Providers
82684
+ core.register(new RepositoryServiceProvider())
82685
+ core.register(new AppServiceProvider())
82686
+ core.register(new MiddlewareProvider())
82687
+ core.register(new RouteProvider())
82216
82688
 
82217
- await core.bootstrap()
82689
+ // 4. Bootstrap All Providers
82690
+ await core.bootstrap()
82218
82691
 
82219
- // Register routes
82220
- registerApiRoutes(core.router)
82692
+ return core
82693
+ }
82221
82694
 
82695
+ // Application Entry Point
82696
+ const core = await bootstrap()
82222
82697
  export default core.liftoff()
82223
82698
  `;
82224
82699
  }
@@ -82230,6 +82705,15 @@ export default core.liftoff()
82230
82705
  This project follows **Clean Architecture** (by Robert C. Martin).
82231
82706
  The key principle is the **Dependency Rule**: dependencies point inward.
82232
82707
 
82708
+ ## Service Providers
82709
+
82710
+ Service providers are the central place to configure your application. They follow the ServiceProvider pattern with \`register()\` and \`boot()\` lifecycle methods.
82711
+
82712
+ ### Provider Lifecycle
82713
+
82714
+ 1. **register()**: Bind services to the container (sync or async).
82715
+ 2. **boot()**: Called after ALL providers have registered. Safe to use other services.
82716
+
82233
82717
  ## Layer Structure
82234
82718
 
82235
82719
  \`\`\`
@@ -82301,6 +82785,10 @@ Created with \u2764\uFE0F using Gravito Framework
82301
82785
  start: "bun run dist/bootstrap.js",
82302
82786
  test: "bun test",
82303
82787
  typecheck: "tsc --noEmit",
82788
+ check: "bun run typecheck && bun run test",
82789
+ "check:deps": "bun run scripts/check-dependencies.ts",
82790
+ validate: "bun run check && bun run check:deps",
82791
+ precommit: "bun run validate",
82304
82792
  "docker:build": `docker build -t ${context.nameKebabCase} .`,
82305
82793
  "docker:run": `docker run -it -p 3000:3000 ${context.nameKebabCase}`
82306
82794
  },
@@ -82605,63 +83093,46 @@ class DddGenerator extends BaseGenerator {
82605
83093
  ]
82606
83094
  };
82607
83095
  }
82608
- generateBootstrapApp(context) {
83096
+ generateBootstrapApp(_context) {
82609
83097
  return `/**
82610
83098
  * Application Bootstrap
82611
83099
  *
82612
- * Central configuration and initialization of the application.
83100
+ * Central configuration and initialization using the ServiceProvider pattern.
83101
+ *
83102
+ * Lifecycle:
83103
+ * 1. Configure: Load app config and orbits
83104
+ * 2. Boot: Initialize PlanetCore
83105
+ * 3. Register Providers: Bind services to container
83106
+ * 4. Bootstrap: Boot all providers
82613
83107
  */
82614
83108
 
82615
- import { bodySizeLimit, PlanetCore, securityHeaders } from '@gravito/core'
83109
+ import { defineConfig, PlanetCore } from '@gravito/core'
83110
+ import { OrbitAtlas } from '@gravito/atlas'
83111
+ import appConfig from '../../config/app'
82616
83112
  import { registerProviders } from './providers'
82617
83113
  import { registerRoutes } from './routes'
82618
83114
 
82619
83115
  export async function createApp(): Promise<PlanetCore> {
82620
- const core = new PlanetCore({
82621
- config: {
82622
- APP_NAME: '${context.name}',
82623
- },
82624
- })
83116
+ // 1. Configure
83117
+ const config = defineConfig({
83118
+ config: appConfig,
83119
+ orbits: [new OrbitAtlas()],
83120
+ })
82625
83121
 
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
- }
83122
+ // 2. Boot Core
83123
+ const core = await PlanetCore.boot(config)
83124
+ core.registerGlobalErrorHandlers()
82654
83125
 
82655
- // Register all service providers
82656
- await registerProviders(core)
83126
+ // 3. Register Providers
83127
+ await registerProviders(core)
82657
83128
 
82658
- // Bootstrap the application
82659
- await core.bootstrap()
83129
+ // 4. Bootstrap All Providers
83130
+ await core.bootstrap()
82660
83131
 
82661
- // Register routes
82662
- registerRoutes(core.router)
83132
+ // Register routes after bootstrap
83133
+ registerRoutes(core.router)
82663
83134
 
82664
- return core
83135
+ return core
82665
83136
  }
82666
83137
  `;
82667
83138
  }
@@ -82669,19 +83140,48 @@ export async function createApp(): Promise<PlanetCore> {
82669
83140
  return `/**
82670
83141
  * Service Providers Registry
82671
83142
  *
82672
- * Register all module service providers here.
83143
+ * Register all service providers here.
83144
+ * Include both global and module-specific providers.
82673
83145
  */
82674
83146
 
82675
- import type { PlanetCore } from '@gravito/core'
83147
+ import {
83148
+ ServiceProvider,
83149
+ type Container,
83150
+ type PlanetCore,
83151
+ bodySizeLimit,
83152
+ securityHeaders,
83153
+ } from '@gravito/core'
82676
83154
  import { OrderingServiceProvider } from '../Modules/Ordering/Infrastructure/Providers/OrderingServiceProvider'
82677
83155
  import { CatalogServiceProvider } from '../Modules/Catalog/Infrastructure/Providers/CatalogServiceProvider'
82678
83156
 
83157
+ /**
83158
+ * Middleware Provider - Global middleware registration
83159
+ */
83160
+ export class MiddlewareProvider extends ServiceProvider {
83161
+ register(_container: Container): void {}
83162
+
83163
+ boot(core: PlanetCore): void {
83164
+ const isDev = process.env.NODE_ENV !== 'production'
83165
+
83166
+ core.adapter.use('*', securityHeaders({
83167
+ contentSecurityPolicy: isDev ? false : undefined,
83168
+ }))
83169
+
83170
+ core.adapter.use('*', bodySizeLimit(10 * 1024 * 1024))
83171
+
83172
+ core.logger.info('\uD83D\uDEE1\uFE0F Global middleware registered')
83173
+ }
83174
+ }
83175
+
82679
83176
  export async function registerProviders(core: PlanetCore): Promise<void> {
82680
- // Register module providers
82681
- core.register(new OrderingServiceProvider())
82682
- core.register(new CatalogServiceProvider())
83177
+ // Global Providers
83178
+ core.register(new MiddlewareProvider())
83179
+
83180
+ // Module Providers
83181
+ core.register(new OrderingServiceProvider())
83182
+ core.register(new CatalogServiceProvider())
82683
83183
 
82684
- // Add more providers as needed
83184
+ // Add more providers as needed
82685
83185
  }
82686
83186
  `;
82687
83187
  }
@@ -82792,6 +83292,10 @@ export class ${name}ServiceProvider extends ServiceProvider {
82792
83292
  start: "bun run dist/main.js",
82793
83293
  test: "bun test",
82794
83294
  typecheck: "tsc --noEmit",
83295
+ check: "bun run typecheck && bun run test",
83296
+ "check:deps": "bun run scripts/check-dependencies.ts",
83297
+ validate: "bun run check && bun run check:deps",
83298
+ precommit: "bun run validate",
82795
83299
  "docker:build": `docker build -t ${context.nameKebabCase} .`,
82796
83300
  "docker:run": `docker run -it -p 3000:3000 ${context.nameKebabCase}`
82797
83301
  },
@@ -83223,6 +83727,16 @@ export function report(error: unknown): void {
83223
83727
 
83224
83728
  This project follows **Domain-Driven Design (DDD)** with strategic and tactical patterns.
83225
83729
 
83730
+ ## Service Providers
83731
+
83732
+ Service providers are the central place to configure your application and modules. They follow the ServiceProvider pattern with \`register()\` and \`boot()\` lifecycle methods.
83733
+
83734
+ ### Internal Bootstrapping
83735
+
83736
+ 1. **Bootstrap/app.ts**: Orchestrates the 4-step lifecycle (Configure, Boot, Register, Bootstrap).
83737
+ 2. **Bootstrap/providers.ts**: Central registry for all global and module-specific providers.
83738
+ 3. **Infrastructure/Providers/[Module]ServiceProvider.ts**: Module-specific service registration.
83739
+
83226
83740
  ## Bounded Contexts
83227
83741
 
83228
83742
  \`\`\`
@@ -83359,6 +83873,11 @@ class EnterpriseMvcGenerator extends BaseGenerator {
83359
83873
  type: "directory",
83360
83874
  name: "Providers",
83361
83875
  children: [
83876
+ {
83877
+ type: "file",
83878
+ name: "index.ts",
83879
+ content: this.generateProvidersIndex()
83880
+ },
83362
83881
  {
83363
83882
  type: "file",
83364
83883
  name: "AppServiceProvider.ts",
@@ -83366,8 +83885,18 @@ class EnterpriseMvcGenerator extends BaseGenerator {
83366
83885
  },
83367
83886
  {
83368
83887
  type: "file",
83369
- name: "RouteServiceProvider.ts",
83370
- content: this.generateRouteServiceProvider(context)
83888
+ name: "DatabaseProvider.ts",
83889
+ content: this.generateDatabaseProvider()
83890
+ },
83891
+ {
83892
+ type: "file",
83893
+ name: "MiddlewareProvider.ts",
83894
+ content: this.generateMiddlewareProvider()
83895
+ },
83896
+ {
83897
+ type: "file",
83898
+ name: "RouteProvider.ts",
83899
+ content: this.generateRouteProvider()
83371
83900
  }
83372
83901
  ]
83373
83902
  },
@@ -83828,6 +84357,132 @@ export class RouteServiceProvider extends ServiceProvider {
83828
84357
  registerRoutes(core.router)
83829
84358
  }
83830
84359
  }
84360
+ `;
84361
+ }
84362
+ generateProvidersIndex() {
84363
+ return `/**
84364
+ * Application Service Providers
84365
+ *
84366
+ * Export all providers for easy importing in bootstrap.
84367
+ * Providers are registered in the order they are listed.
84368
+ */
84369
+
84370
+ export { AppServiceProvider } from './AppServiceProvider'
84371
+ export { DatabaseProvider } from './DatabaseProvider'
84372
+ export { MiddlewareProvider } from './MiddlewareProvider'
84373
+ export { RouteProvider } from './RouteProvider'
84374
+ `;
84375
+ }
84376
+ generateDatabaseProvider() {
84377
+ return `/**
84378
+ * Database Service Provider
84379
+ *
84380
+ * Handles database initialization and migrations.
84381
+ *
84382
+ * Lifecycle:
84383
+ * - register(): Bind database config to container
84384
+ * - boot(): Run migrations and seeders
84385
+ */
84386
+
84387
+ import { ServiceProvider, type Container, type PlanetCore } from '@gravito/core'
84388
+ import databaseConfig from '../../config/database'
84389
+
84390
+ export class DatabaseProvider extends ServiceProvider {
84391
+ /**
84392
+ * Register database configuration.
84393
+ */
84394
+ register(_container: Container): void {
84395
+ this.mergeConfig(this.core!.config, 'database', databaseConfig)
84396
+ }
84397
+
84398
+ /**
84399
+ * Initialize database connections.
84400
+ */
84401
+ async boot(core: PlanetCore): Promise<void> {
84402
+ // Database initialization will be handled by Atlas orbit
84403
+ core.logger.info('\uD83D\uDCE6 Database provider booted')
84404
+ }
84405
+ }
84406
+ `;
84407
+ }
84408
+ generateMiddlewareProvider() {
84409
+ return `/**
84410
+ * Middleware Service Provider
84411
+ *
84412
+ * Registers global middleware stack.
84413
+ * Provides a centralized location for middleware configuration.
84414
+ *
84415
+ * Lifecycle:
84416
+ * - register(): N/A (no container bindings)
84417
+ * - boot(): Register global middleware
84418
+ */
84419
+
84420
+ import {
84421
+ ServiceProvider,
84422
+ type Container,
84423
+ type PlanetCore,
84424
+ bodySizeLimit,
84425
+ securityHeaders,
84426
+ } from '@gravito/core'
84427
+
84428
+ export class MiddlewareProvider extends ServiceProvider {
84429
+ /**
84430
+ * No container bindings needed.
84431
+ */
84432
+ register(_container: Container): void {
84433
+ // Middleware doesn't require container bindings
84434
+ }
84435
+
84436
+ /**
84437
+ * Register global middleware stack.
84438
+ */
84439
+ boot(core: PlanetCore): void {
84440
+ const isDev = process.env.NODE_ENV !== 'production'
84441
+
84442
+ // Security Headers
84443
+ core.adapter.use('*', securityHeaders({
84444
+ contentSecurityPolicy: isDev ? false : undefined,
84445
+ }))
84446
+
84447
+ // Body Parser Limits
84448
+ core.adapter.use('*', bodySizeLimit(10 * 1024 * 1024)) // 10MB limit
84449
+
84450
+ core.logger.info('\uD83D\uDEE1\uFE0F Middleware registered')
84451
+ }
84452
+ }
84453
+ `;
84454
+ }
84455
+ generateRouteProvider() {
84456
+ return `/**
84457
+ * Route Service Provider
84458
+ *
84459
+ * Registers application routes.
84460
+ * Routes are registered in the boot phase after all services are available.
84461
+ *
84462
+ * Lifecycle:
84463
+ * - register(): N/A
84464
+ * - boot(): Register routes
84465
+ */
84466
+
84467
+ import { ServiceProvider, type Container, type PlanetCore } from '@gravito/core'
84468
+ import { registerRoutes } from '../routes'
84469
+
84470
+ export class RouteProvider extends ServiceProvider {
84471
+ /**
84472
+ * No container bindings needed.
84473
+ */
84474
+ register(_container: Container): void {
84475
+ // Routes don't require container bindings
84476
+ }
84477
+
84478
+ /**
84479
+ * Register application routes.
84480
+ */
84481
+ boot(core: PlanetCore): void {
84482
+ registerRoutes(core.router)
84483
+ core.logger.info('\uD83D\uDEE4\uFE0F Routes registered')
84484
+ }
84485
+ }
83831
84486
  `;
83832
84487
  }
83833
84488
  generateExceptionHandler() {
@@ -83877,74 +84532,75 @@ export const dontReport: string[] = [
83877
84532
  generateBootstrap(context) {
83878
84533
  const spectrumImport = context.withSpectrum ? `import { SpectrumOrbit } from '@gravito/spectrum'
83879
84534
  ` : "";
83880
- const spectrumOrbit = context.withSpectrum ? `
83881
- // Enable Debug Dashboard
83882
- if (process.env.APP_DEBUG === 'true') {
83883
- await core.orbit(new SpectrumOrbit())
83884
- }
83885
- ` : "";
84535
+ const spectrumOrbit = context.withSpectrum ? " new SpectrumOrbit()," : "";
83886
84536
  return `/**
83887
84537
  * Application Bootstrap
83888
84538
  *
83889
- * This is the entry point for your application.
83890
- * It initializes the core and registers all providers.
84539
+ * The entry point for your Gravito application.
84540
+ * Uses the ServiceProvider pattern for modular, maintainable initialization.
84541
+ *
84542
+ * Lifecycle:
84543
+ * 1. Configure: Load app config and orbits
84544
+ * 2. Boot: Initialize PlanetCore
84545
+ * 3. Register Providers: Bind services to container
84546
+ * 4. Bootstrap: Boot all providers
84547
+ *
84548
+ * @module bootstrap
83891
84549
  */
83892
84550
 
83893
- import { bodySizeLimit, PlanetCore, securityHeaders } from '@gravito/core'
84551
+ import { defineConfig, PlanetCore } from '@gravito/core'
83894
84552
  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
- }
84553
+ import appConfig from '../config/app'
84554
+ ${spectrumImport}import {
84555
+ AppServiceProvider,
84556
+ DatabaseProvider,
84557
+ MiddlewareProvider,
84558
+ RouteProvider,
84559
+ } from './Providers'
83937
84560
 
83938
- await core.orbit(new OrbitAtlas())
84561
+ /**
84562
+ * Bootstrap the application with service providers.
84563
+ *
84564
+ * @returns The booted PlanetCore instance
84565
+ */
84566
+ export async function bootstrap() {
84567
+ // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
84568
+ // 1. Configure
84569
+ // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
84570
+ const config = defineConfig({
84571
+ config: appConfig,
84572
+ orbits: [
84573
+ new OrbitAtlas(),
83939
84574
  ${spectrumOrbit}
83940
- // Register service providers
83941
- core.register(new AppServiceProvider())
83942
- core.register(new RouteServiceProvider())
84575
+ ],
84576
+ })
83943
84577
 
83944
- // Bootstrap the application
83945
- await core.bootstrap()
84578
+ // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
84579
+ // 2. Boot Core
84580
+ // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
84581
+ const core = await PlanetCore.boot(config)
84582
+ core.registerGlobalErrorHandlers()
84583
+
84584
+ // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
84585
+ // 3. Register Providers
84586
+ // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
84587
+ core.register(new AppServiceProvider())
84588
+ core.register(new DatabaseProvider())
84589
+ core.register(new MiddlewareProvider())
84590
+ core.register(new RouteProvider())
84591
+
84592
+ // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
84593
+ // 4. Bootstrap All Providers
84594
+ // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
84595
+ await core.bootstrap()
84596
+
84597
+ return core
84598
+ }
83946
84599
 
83947
- // Export for Bun.serve()
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
+ // Application Entry Point
84602
+ // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
84603
+ const core = await bootstrap()
83948
84604
  export default core.liftoff()
83949
84605
  `;
83950
84606
  }
@@ -84350,7 +85006,9 @@ export class ${name}ServiceProvider extends ServiceProvider {
84350
85006
  scripts: {
84351
85007
  build: "tsup src/index.ts --format cjs,esm --dts",
84352
85008
  test: "bun test",
84353
- typecheck: "tsc --noEmit"
85009
+ typecheck: "tsc --noEmit",
85010
+ check: "bun run typecheck && bun run test",
85011
+ validate: "bun run check"
84354
85012
  },
84355
85013
  dependencies: {
84356
85014
  "@gravito/core": depVersion,
@@ -84638,10 +85296,25 @@ class ActionDomainGenerator extends BaseGenerator {
84638
85296
  type: "directory",
84639
85297
  name: "providers",
84640
85298
  children: [
85299
+ {
85300
+ type: "file",
85301
+ name: "index.ts",
85302
+ content: this.generateProvidersIndex()
85303
+ },
84641
85304
  {
84642
85305
  type: "file",
84643
85306
  name: "AppServiceProvider.ts",
84644
85307
  content: this.generateAppServiceProvider(context)
85308
+ },
85309
+ {
85310
+ type: "file",
85311
+ name: "MiddlewareProvider.ts",
85312
+ content: this.generateMiddlewareProvider()
85313
+ },
85314
+ {
85315
+ type: "file",
85316
+ name: "RouteProvider.ts",
85317
+ content: this.generateRouteProvider()
84645
85318
  }
84646
85319
  ]
84647
85320
  },
@@ -84801,7 +85474,7 @@ export function registerApiRoutes(router: Router) {
84801
85474
  import { ServiceProvider, type Container, type PlanetCore } from '@gravito/core'
84802
85475
 
84803
85476
  export class AppServiceProvider extends ServiceProvider {
84804
- register(container: Container): void {
85477
+ register(_container: Container): void {
84805
85478
  // Register global services here
84806
85479
  }
84807
85480
 
@@ -84811,82 +85484,139 @@ export class AppServiceProvider extends ServiceProvider {
84811
85484
  }
84812
85485
  `;
84813
85486
  }
84814
- generateBootstrap(context) {
85487
+ generateProvidersIndex() {
84815
85488
  return `/**
84816
- * Application Entry Point
85489
+ * Application Service Providers
84817
85490
  */
84818
85491
 
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
- })
85492
+ export { AppServiceProvider } from './AppServiceProvider'
85493
+ export { MiddlewareProvider } from './MiddlewareProvider'
85494
+ export { RouteProvider } from './RouteProvider'
85495
+ `;
85496
+ }
85497
+ generateMiddlewareProvider() {
85498
+ return `/**
85499
+ * Middleware Service Provider
85500
+ */
84832
85501
 
84833
- // Middleware
84834
- core.adapter.use('*', securityHeaders())
84835
- core.adapter.use('*', bodySizeLimit(1024 * 1024)) // 1MB
85502
+ import {
85503
+ ServiceProvider,
85504
+ type Container,
85505
+ type PlanetCore,
85506
+ bodySizeLimit,
85507
+ securityHeaders,
85508
+ } from '@gravito/core'
84836
85509
 
84837
- // Install Orbits
84838
- await core.orbit(new OrbitAtlas())
85510
+ export class MiddlewareProvider extends ServiceProvider {
85511
+ register(_container: Container): void {}
84839
85512
 
84840
- // Service Providers
84841
- core.register(new AppServiceProvider())
85513
+ boot(core: PlanetCore): void {
85514
+ core.adapter.use('*', securityHeaders())
85515
+ core.adapter.use('*', bodySizeLimit(1024 * 1024))
85516
+ core.logger.info('\uD83D\uDEE1\uFE0F Middleware registered')
85517
+ }
85518
+ }
85519
+ `;
85520
+ }
85521
+ generateRouteProvider() {
85522
+ return `/**
85523
+ * Route Service Provider
85524
+ */
84842
85525
 
84843
- // Bootstrap
84844
- await core.bootstrap()
85526
+ import { ServiceProvider, type Container, type PlanetCore } from '@gravito/core'
85527
+ import { registerApiRoutes } from '../routes/api'
85528
+
85529
+ export class RouteProvider extends ServiceProvider {
85530
+ register(_container: Container): void {}
84845
85531
 
84846
- // Routes
84847
- registerApiRoutes(core.router)
85532
+ boot(core: PlanetCore): void {
85533
+ registerApiRoutes(core.router)
85534
+ core.logger.info('\uD83D\uDEE4\uFE0F Routes registered')
85535
+ }
85536
+ }
85537
+ `;
85538
+ }
85539
+ generateBootstrap(_context) {
85540
+ return `/**
85541
+ * Application Bootstrap
85542
+ *
85543
+ * Uses the ServiceProvider pattern for modular initialization.
85544
+ */
85545
+
85546
+ import { defineConfig, PlanetCore } from '@gravito/core'
85547
+ import { OrbitAtlas } from '@gravito/atlas'
85548
+ import appConfig from '../config/app'
85549
+ import {
85550
+ AppServiceProvider,
85551
+ MiddlewareProvider,
85552
+ RouteProvider,
85553
+ } from './providers'
85554
+
85555
+ export async function bootstrap() {
85556
+ const config = defineConfig({
85557
+ config: appConfig,
85558
+ orbits: [new OrbitAtlas()],
85559
+ })
84848
85560
 
84849
- // Liftoff
85561
+ const core = await PlanetCore.boot(config)
85562
+ core.registerGlobalErrorHandlers()
85563
+
85564
+ core.register(new AppServiceProvider())
85565
+ core.register(new MiddlewareProvider())
85566
+ core.register(new RouteProvider())
85567
+
85568
+ await core.bootstrap()
85569
+
85570
+ return core
85571
+ }
85572
+
85573
+ const core = await bootstrap()
84850
85574
  export default core.liftoff()
84851
85575
  `;
84852
85576
  }
84853
85577
  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
85578
+ return `# ${context.name} - Action Domain Architecture
85579
+
85580
+ ## Overview
85581
+
85582
+ This project uses the **Action Domain** pattern, designed for high-clarity API implementations.
85583
+
85584
+ ## Service Providers
85585
+
85586
+ Service providers are the central place to configure your application. They follow the ServiceProvider pattern with \`register()\` and \`boot()\` lifecycle methods.
85587
+
85588
+ ## Directory Structure
85589
+
85590
+ \`\`\`
85591
+ src/
85592
+ \u251C\u2500\u2500 actions/ # Single Responsibility Business Logic
85593
+ \u2502 \u251C\u2500\u2500 Action.ts # Base Action class
85594
+ \u2502 \u2514\u2500\u2500 [Domain]/ # Domain-specific actions
85595
+ \u251C\u2500\u2500 controllers/ # HTTP Request Handlers
85596
+ \u2502 \u2514\u2500\u2500 api/v1/ # API Controllers
85597
+ \u251C\u2500\u2500 types/ # TypeScript Definitions
85598
+ \u251C\u2500\u2500 repositories/ # Data Access Layer
85599
+ \u251C\u2500\u2500 routes/ # Route Definitions
85600
+ \u251C\u2500\u2500 providers/ # Service Providers
85601
+ \u2514\u2500\u2500 config/ # Configuration
85602
+ \`\`\`
85603
+
85604
+ ## Core Concepts
85605
+
85606
+ ### Actions
85607
+ Every business operation is an "Action". An action:
85608
+ - Does ONE thing.
85609
+ - Takes specific input.
85610
+ - Returns specific output.
85611
+ - Is framework-agnostic (ideally).
85612
+
85613
+ ### Controllers
85614
+ Controllers are thin. They:
85615
+ 1. Parse the request.
85616
+ 2. Instantiate/Call the Action.
85617
+ 3. Return the response.
85618
+
85619
+ Created with \u2764\uFE0F using Gravito Framework
84890
85620
  `;
84891
85621
  }
84892
85622
  generatePackageJson(context) {
@@ -84899,7 +85629,11 @@ export default core.liftoff()
84899
85629
  build: "bun build ./src/bootstrap.ts --outdir ./dist --target bun",
84900
85630
  start: "bun run dist/bootstrap.js",
84901
85631
  test: "bun test",
84902
- typecheck: "tsc --noEmit"
85632
+ typecheck: "tsc --noEmit",
85633
+ check: "bun run typecheck && bun run test",
85634
+ "check:deps": "bun run scripts/check-dependencies.ts",
85635
+ validate: "bun run check && bun run check:deps",
85636
+ precommit: "bun run validate"
84903
85637
  },
84904
85638
  dependencies: {
84905
85639
  "@gravito/core": "workspace:*",
@@ -85096,7 +85830,6 @@ var package_default = {
85096
85830
  // ../core/src/adapters/PhotonAdapter.ts
85097
85831
  class PhotonRequestWrapper {
85098
85832
  photonCtx;
85099
- _cachedJson = null;
85100
85833
  constructor(photonCtx) {
85101
85834
  this.photonCtx = photonCtx;
85102
85835
  }
@@ -88259,6 +88992,11 @@ async function initCommand(options = {}) {
88259
88992
  value: "ddd",
88260
88993
  label: "\uD83C\uDFDB\uFE0F Domain-Driven Design (DDD)",
88261
88994
  hint: "\u5B8C\u6574\u7684 DDD \u67B6\u69CB\uFF0C\u5305\u542B Modules\u3001Bounded Contexts"
88995
+ },
88996
+ {
88997
+ value: "action-domain",
88998
+ label: "\u26A1 Action-Domain-Responder (ADR)",
88999
+ hint: "Web \u7279\u5316\u7684\u8A2D\u8A08\u6A21\u5F0F\uFF0C\u95DC\u6CE8 Actions \u8207 Domain \u908F\u8F2F\u7684\u5206\u96E2"
88262
89000
  }
88263
89001
  ]
88264
89002
  });
@@ -88367,7 +89105,8 @@ async function initCommand(options = {}) {
88367
89105
  "enterprise-mvc": "\uD83D\uDCE6 Enterprise MVC",
88368
89106
  clean: "\uD83E\uDDC5 Clean Architecture",
88369
89107
  ddd: "\uD83C\uDFDB\uFE0F Domain-Driven Design",
88370
- satellite: "\uD83D\uDEF0\uFE0F Satellite Service"
89108
+ satellite: "\uD83D\uDEF0\uFE0F Satellite Service",
89109
+ "action-domain": "\u26A1 Action-Domain-Responder"
88371
89110
  };
88372
89111
  note2(`\u5C08\u6848\u540D\u7A31: ${pc3.cyan(projectName)}
88373
89112
  \u67B6\u69CB\u6A21\u5F0F: ${pc3.green(archLabels[architecture])}
@@ -89922,7 +90661,7 @@ cli.command("schedule:list", "List scheduled tasks").option("--entry <file>", "E
89922
90661
  });
89923
90662
  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
90663
  default: ""
89925
- }).option("--recommend", "Auto-detect profile based on environment").action(async (name, options) => {
90664
+ }).option("--recommend", "Auto-detect profile based on environment").option("--framework <framework>", "Frontend framework (react, vue) for static-site template").action(async (name, options) => {
89926
90665
  console.clear();
89927
90666
  intro2(pc11.bgBlack(pc11.white(" \uD83C\uDF0C Gravito CLI ")));
89928
90667
  if (options.recommend) {
@@ -90016,26 +90755,30 @@ Confidence: ${detection.confidence}`, "Environment Detection");
90016
90755
  }
90017
90756
  let framework = null;
90018
90757
  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);
90758
+ if (options.framework) {
90759
+ framework = options.framework;
90760
+ } else {
90761
+ const frameworkResult = await select2({
90762
+ message: "Choose your frontend framework:",
90763
+ options: [
90764
+ {
90765
+ value: "react",
90766
+ label: "\u269B\uFE0F React",
90767
+ hint: "Recommended for most projects"
90768
+ },
90769
+ {
90770
+ value: "vue",
90771
+ label: "\uD83D\uDFE2 Vue 3",
90772
+ hint: "Composition API with TypeScript"
90773
+ }
90774
+ ]
90775
+ });
90776
+ if (isCancel2(frameworkResult)) {
90777
+ cancel2("Operation cancelled.");
90778
+ process.exit(0);
90779
+ }
90780
+ framework = frameworkResult;
90037
90781
  }
90038
- framework = frameworkResult;
90039
90782
  }
90040
90783
  const s = spinner5();
90041
90784
  s.start("Scaffolding your universe...");