@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.
- package/dist/index.js +1694 -271
- 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(
|
|
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 {
|
|
82682
|
+
import { defineConfig, PlanetCore } from '@gravito/core'
|
|
82170
82683
|
import { OrbitAtlas } from '@gravito/atlas'
|
|
82171
|
-
import
|
|
82172
|
-
import {
|
|
82173
|
-
|
|
82174
|
-
|
|
82175
|
-
|
|
82176
|
-
|
|
82177
|
-
|
|
82178
|
-
|
|
82179
|
-
|
|
82180
|
-
|
|
82181
|
-
|
|
82182
|
-
|
|
82183
|
-
|
|
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
|
-
|
|
82699
|
+
// 2. Boot Core
|
|
82700
|
+
const core = await PlanetCore.boot(config)
|
|
82701
|
+
core.registerGlobalErrorHandlers()
|
|
82213
82702
|
|
|
82214
|
-
|
|
82215
|
-
core.register(new
|
|
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
|
-
|
|
82709
|
+
// 4. Bootstrap All Providers
|
|
82710
|
+
await core.bootstrap()
|
|
82218
82711
|
|
|
82219
|
-
|
|
82220
|
-
|
|
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(
|
|
83116
|
+
generateBootstrapApp(_context) {
|
|
82609
83117
|
return `/**
|
|
82610
83118
|
* Application Bootstrap
|
|
82611
83119
|
*
|
|
82612
|
-
* Central configuration and initialization
|
|
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 {
|
|
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
|
-
|
|
82621
|
-
|
|
82622
|
-
|
|
82623
|
-
|
|
82624
|
-
|
|
83136
|
+
// 1. Configure
|
|
83137
|
+
const config = defineConfig({
|
|
83138
|
+
config: appConfig,
|
|
83139
|
+
orbits: [new OrbitAtlas()],
|
|
83140
|
+
})
|
|
82625
83141
|
|
|
82626
|
-
|
|
82627
|
-
|
|
82628
|
-
|
|
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
|
-
|
|
82656
|
-
|
|
83146
|
+
// 3. Register Providers
|
|
83147
|
+
await registerProviders(core)
|
|
82657
83148
|
|
|
82658
|
-
|
|
82659
|
-
|
|
83149
|
+
// 4. Bootstrap All Providers
|
|
83150
|
+
await core.bootstrap()
|
|
82660
83151
|
|
|
82661
|
-
|
|
82662
|
-
|
|
83152
|
+
// Register routes after bootstrap
|
|
83153
|
+
registerRoutes(core.router)
|
|
82663
83154
|
|
|
82664
|
-
|
|
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
|
|
83163
|
+
* Register all service providers here.
|
|
83164
|
+
* Include both global and module-specific providers.
|
|
82673
83165
|
*/
|
|
82674
83166
|
|
|
82675
|
-
import
|
|
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
|
-
|
|
82681
|
-
|
|
82682
|
-
core.register(new CatalogServiceProvider())
|
|
83197
|
+
// Global Providers
|
|
83198
|
+
core.register(new MiddlewareProvider())
|
|
82683
83199
|
|
|
82684
|
-
|
|
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: "
|
|
83370
|
-
content: this.
|
|
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
|
-
|
|
84382
|
+
generateProvidersIndex() {
|
|
83834
84383
|
return `/**
|
|
83835
|
-
*
|
|
84384
|
+
* Application Service Providers
|
|
83836
84385
|
*
|
|
83837
|
-
*
|
|
83838
|
-
*
|
|
84386
|
+
* Export all providers for easy importing in bootstrap.
|
|
84387
|
+
* Providers are registered in the order they are listed.
|
|
83839
84388
|
*/
|
|
83840
84389
|
|
|
83841
|
-
|
|
83842
|
-
|
|
83843
|
-
|
|
83844
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
83857
|
-
|
|
83858
|
-
|
|
83859
|
-
|
|
83860
|
-
|
|
83861
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
83890
|
-
*
|
|
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 {
|
|
84571
|
+
import { defineConfig, PlanetCore } from '@gravito/core'
|
|
83894
84572
|
import { OrbitAtlas } from '@gravito/atlas'
|
|
83895
|
-
import
|
|
83896
|
-
${spectrumImport}import {
|
|
83897
|
-
|
|
83898
|
-
|
|
83899
|
-
|
|
83900
|
-
|
|
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
|
-
|
|
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
|
-
|
|
83941
|
-
|
|
83942
|
-
core.register(new RouteServiceProvider())
|
|
84595
|
+
],
|
|
84596
|
+
})
|
|
83943
84597
|
|
|
83944
|
-
//
|
|
83945
|
-
|
|
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
|
-
//
|
|
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(
|
|
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
|
-
|
|
85507
|
+
generateProvidersIndex() {
|
|
84815
85508
|
return `/**
|
|
84816
|
-
* Application
|
|
85509
|
+
* Application Service Providers
|
|
84817
85510
|
*/
|
|
84818
85511
|
|
|
84819
|
-
|
|
84820
|
-
|
|
84821
|
-
|
|
84822
|
-
|
|
84823
|
-
|
|
84824
|
-
|
|
84825
|
-
|
|
84826
|
-
|
|
84827
|
-
|
|
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
|
-
|
|
84834
|
-
|
|
84835
|
-
|
|
85522
|
+
import {
|
|
85523
|
+
ServiceProvider,
|
|
85524
|
+
type Container,
|
|
85525
|
+
type PlanetCore,
|
|
85526
|
+
bodySizeLimit,
|
|
85527
|
+
securityHeaders,
|
|
85528
|
+
} from '@gravito/core'
|
|
84836
85529
|
|
|
84837
|
-
|
|
84838
|
-
|
|
85530
|
+
export class MiddlewareProvider extends ServiceProvider {
|
|
85531
|
+
register(_container: Container): void {}
|
|
84839
85532
|
|
|
84840
|
-
|
|
84841
|
-
core.
|
|
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
|
-
|
|
84844
|
-
|
|
85546
|
+
import { ServiceProvider, type Container, type PlanetCore } from '@gravito/core'
|
|
85547
|
+
import { registerApiRoutes } from '../routes/api'
|
|
84845
85548
|
|
|
84846
|
-
|
|
84847
|
-
|
|
85549
|
+
export class RouteProvider extends ServiceProvider {
|
|
85550
|
+
register(_container: Container): void {}
|
|
84848
85551
|
|
|
84849
|
-
|
|
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
|
|
84855
|
-
|
|
84856
|
-
|
|
84857
|
-
|
|
84858
|
-
|
|
84859
|
-
|
|
84860
|
-
|
|
84861
|
-
|
|
84862
|
-
|
|
84863
|
-
|
|
84864
|
-
|
|
84865
|
-
|
|
84866
|
-
|
|
84867
|
-
|
|
84868
|
-
|
|
84869
|
-
|
|
84870
|
-
|
|
84871
|
-
|
|
84872
|
-
|
|
84873
|
-
|
|
84874
|
-
|
|
84875
|
-
|
|
84876
|
-
|
|
84877
|
-
|
|
84878
|
-
|
|
84879
|
-
|
|
84880
|
-
|
|
84881
|
-
|
|
84882
|
-
|
|
84883
|
-
|
|
84884
|
-
|
|
84885
|
-
|
|
84886
|
-
|
|
84887
|
-
|
|
84888
|
-
|
|
84889
|
-
|
|
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.
|
|
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
|
-
|
|
90020
|
-
|
|
90021
|
-
|
|
90022
|
-
|
|
90023
|
-
|
|
90024
|
-
|
|
90025
|
-
|
|
90026
|
-
|
|
90027
|
-
|
|
90028
|
-
|
|
90029
|
-
|
|
90030
|
-
|
|
90031
|
-
|
|
90032
|
-
|
|
90033
|
-
|
|
90034
|
-
|
|
90035
|
-
|
|
90036
|
-
|
|
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...");
|