@gravito/scaffold 1.0.0-beta.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +2385 -419
- package/dist/index.d.cts +159 -9
- package/dist/index.d.ts +159 -9
- package/dist/index.js +2380 -419
- package/package.json +8 -5
- package/templates/features/otel/.env.example +2 -0
- package/templates/features/otel/config/telemetry.ts +10 -0
- package/templates/features/otel/docker-compose.yml +15 -0
- package/templates/features/otel/package.json +7 -0
- package/templates/features/redis/.env.example +7 -0
- package/templates/features/redis/config/cache.ts +14 -0
- package/templates/features/redis/config/queue.ts +13 -0
- package/templates/features/redis/docker-compose.yml +17 -0
- package/templates/features/redis/package.json +5 -0
- package/templates/overlays/core/.env.example +14 -0
- package/templates/overlays/core/config/cache.ts +26 -0
- package/templates/overlays/core/config/database.ts +38 -0
- package/templates/overlays/core/config/queue.ts +31 -0
- package/templates/overlays/core/package.json +5 -0
- package/templates/overlays/enterprise/.env.example +26 -0
- package/templates/overlays/enterprise/config/cache.ts +31 -0
- package/templates/overlays/enterprise/config/database.ts +34 -0
- package/templates/overlays/enterprise/config/logging.ts +32 -0
- package/templates/overlays/enterprise/config/queue.ts +31 -0
- package/templates/overlays/enterprise/config/security.ts +20 -0
- package/templates/overlays/enterprise/docker-compose.yml +32 -0
- package/templates/overlays/enterprise/package.json +7 -0
- package/templates/overlays/scale/.env.example +22 -0
- package/templates/overlays/scale/config/cache.ts +31 -0
- package/templates/overlays/scale/config/database.ts +34 -0
- package/templates/overlays/scale/config/queue.ts +34 -0
- package/templates/overlays/scale/docker-compose.yml +32 -0
- package/templates/overlays/scale/package.json +6 -0
package/dist/index.cjs
CHANGED
|
@@ -34,11 +34,115 @@ __export(index_exports, {
|
|
|
34
34
|
CleanArchitectureGenerator: () => CleanArchitectureGenerator,
|
|
35
35
|
DddGenerator: () => DddGenerator,
|
|
36
36
|
EnterpriseMvcGenerator: () => EnterpriseMvcGenerator,
|
|
37
|
+
EnvironmentDetector: () => EnvironmentDetector,
|
|
38
|
+
FileMerger: () => FileMerger,
|
|
39
|
+
LockGenerator: () => LockGenerator,
|
|
40
|
+
ProfileResolver: () => ProfileResolver,
|
|
41
|
+
SatelliteGenerator: () => SatelliteGenerator,
|
|
37
42
|
Scaffold: () => Scaffold,
|
|
38
43
|
StubGenerator: () => StubGenerator
|
|
39
44
|
});
|
|
40
45
|
module.exports = __toCommonJS(index_exports);
|
|
41
46
|
|
|
47
|
+
// src/EnvironmentDetector.ts
|
|
48
|
+
var EnvironmentDetector = class {
|
|
49
|
+
detect() {
|
|
50
|
+
if (process.env.KUBERNETES_SERVICE_HOST) {
|
|
51
|
+
return {
|
|
52
|
+
platform: "k8s",
|
|
53
|
+
suggestedProfile: "enterprise",
|
|
54
|
+
confidence: "high",
|
|
55
|
+
reason: "Kubernetes environment detected (KUBERNETES_SERVICE_HOST)"
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
if (process.env.AWS_LAMBDA_FUNCTION_NAME || process.env.AWS_EXECUTION_ENV) {
|
|
59
|
+
return {
|
|
60
|
+
platform: "aws",
|
|
61
|
+
suggestedProfile: "scale",
|
|
62
|
+
confidence: "high",
|
|
63
|
+
reason: "AWS Lambda environment detected"
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
if (process.env.VERCEL) {
|
|
67
|
+
return {
|
|
68
|
+
platform: "vercel",
|
|
69
|
+
suggestedProfile: "core",
|
|
70
|
+
confidence: "high",
|
|
71
|
+
reason: "Vercel environment detected"
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
if (process.env.RAILWAY_ENVIRONMENT || process.env.RENDER) {
|
|
75
|
+
return {
|
|
76
|
+
platform: "unknown",
|
|
77
|
+
// Generic cloud
|
|
78
|
+
suggestedProfile: "scale",
|
|
79
|
+
confidence: "medium",
|
|
80
|
+
reason: "Cloud container environment detected"
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
platform: "unknown",
|
|
85
|
+
suggestedProfile: "core",
|
|
86
|
+
confidence: "low",
|
|
87
|
+
reason: "No specific cloud environment detected, defaulting to Core"
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
// src/utils/deepMerge.ts
|
|
93
|
+
function deepMerge(target, source) {
|
|
94
|
+
if (typeof target !== "object" || target === null) {
|
|
95
|
+
return source;
|
|
96
|
+
}
|
|
97
|
+
if (typeof source !== "object" || source === null) {
|
|
98
|
+
return source;
|
|
99
|
+
}
|
|
100
|
+
if (Array.isArray(target) && Array.isArray(source)) {
|
|
101
|
+
return Array.from(/* @__PURE__ */ new Set([...target, ...source]));
|
|
102
|
+
}
|
|
103
|
+
const output = { ...target };
|
|
104
|
+
for (const key of Object.keys(source)) {
|
|
105
|
+
if (source[key] instanceof Object && key in target) {
|
|
106
|
+
output[key] = deepMerge(target[key], source[key]);
|
|
107
|
+
} else {
|
|
108
|
+
output[key] = source[key];
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return output;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// src/FileMerger.ts
|
|
115
|
+
var FileMerger = class {
|
|
116
|
+
/**
|
|
117
|
+
* Merge content of two files based on their type.
|
|
118
|
+
*/
|
|
119
|
+
merge(fileName, baseContent, overlayContent) {
|
|
120
|
+
if (fileName.endsWith(".json")) {
|
|
121
|
+
return this.mergeJson(baseContent, overlayContent);
|
|
122
|
+
}
|
|
123
|
+
if (fileName.endsWith(".env") || fileName.endsWith(".env.example")) {
|
|
124
|
+
return this.mergeEnv(baseContent, overlayContent);
|
|
125
|
+
}
|
|
126
|
+
return overlayContent;
|
|
127
|
+
}
|
|
128
|
+
mergeJson(base, overlay) {
|
|
129
|
+
try {
|
|
130
|
+
const baseJson = JSON.parse(base);
|
|
131
|
+
const overlayJson = JSON.parse(overlay);
|
|
132
|
+
const merged = deepMerge(baseJson, overlayJson);
|
|
133
|
+
return JSON.stringify(merged, null, 2);
|
|
134
|
+
} catch (e) {
|
|
135
|
+
console.warn("Failed to merge JSON, returning overlay", e);
|
|
136
|
+
return overlay;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
mergeEnv(base, overlay) {
|
|
140
|
+
return `${base}
|
|
141
|
+
# --- Overlay ---
|
|
142
|
+
${overlay}`;
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
|
|
42
146
|
// src/generators/BaseGenerator.ts
|
|
43
147
|
var import_promises2 = __toESM(require("fs/promises"), 1);
|
|
44
148
|
var import_node_path2 = __toESM(require("path"), 1);
|
|
@@ -196,9 +300,24 @@ var StubGenerator = class {
|
|
|
196
300
|
};
|
|
197
301
|
|
|
198
302
|
// src/generators/BaseGenerator.ts
|
|
303
|
+
async function walk(dir) {
|
|
304
|
+
const files = await import_promises2.default.readdir(dir);
|
|
305
|
+
const paths = [];
|
|
306
|
+
for (const file of files) {
|
|
307
|
+
const filePath = import_node_path2.default.join(dir, file);
|
|
308
|
+
const stat = await import_promises2.default.stat(filePath);
|
|
309
|
+
if (stat.isDirectory()) {
|
|
310
|
+
paths.push(...await walk(filePath));
|
|
311
|
+
} else {
|
|
312
|
+
paths.push(filePath);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return paths;
|
|
316
|
+
}
|
|
199
317
|
var BaseGenerator = class {
|
|
200
318
|
config;
|
|
201
319
|
stubGenerator;
|
|
320
|
+
fileMerger;
|
|
202
321
|
filesCreated = [];
|
|
203
322
|
constructor(config) {
|
|
204
323
|
this.config = config;
|
|
@@ -207,6 +326,7 @@ var BaseGenerator = class {
|
|
|
207
326
|
outputDir: ""
|
|
208
327
|
// Set per-generation
|
|
209
328
|
});
|
|
329
|
+
this.fileMerger = new FileMerger();
|
|
210
330
|
}
|
|
211
331
|
/**
|
|
212
332
|
* Generate the project scaffold.
|
|
@@ -219,6 +339,8 @@ var BaseGenerator = class {
|
|
|
219
339
|
const structure = this.getDirectoryStructure(context);
|
|
220
340
|
await this.createStructure(context.targetDir, structure, context);
|
|
221
341
|
await this.generateCommonFiles(context);
|
|
342
|
+
await this.applyOverlays(context);
|
|
343
|
+
await this.applyFeatureOverlays(context);
|
|
222
344
|
return this.filesCreated;
|
|
223
345
|
}
|
|
224
346
|
/**
|
|
@@ -263,11 +385,53 @@ var BaseGenerator = class {
|
|
|
263
385
|
await this.writeFile(context.targetDir, ".env", this.generateEnvExample(context));
|
|
264
386
|
await this.writeFile(context.targetDir, ".gitignore", this.generateGitignore());
|
|
265
387
|
await this.writeFile(context.targetDir, "tsconfig.json", this.generateTsConfig());
|
|
388
|
+
await this.writeFile(context.targetDir, "Dockerfile", this.generateDockerfile(context));
|
|
389
|
+
await this.writeFile(context.targetDir, ".dockerignore", this.generateDockerIgnore());
|
|
266
390
|
await this.writeFile(
|
|
267
391
|
context.targetDir,
|
|
268
392
|
"ARCHITECTURE.md",
|
|
269
393
|
this.generateArchitectureDoc(context)
|
|
270
394
|
);
|
|
395
|
+
await this.generateCheckScripts(context);
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Apply profile-specific overlays
|
|
399
|
+
*/
|
|
400
|
+
async applyOverlays(context) {
|
|
401
|
+
const profile = context.profile;
|
|
402
|
+
if (!profile) return;
|
|
403
|
+
const overlayDir = import_node_path2.default.resolve(this.config.templatesDir, "overlays", profile);
|
|
404
|
+
await this.copyOverlayDirectory(overlayDir, context);
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Apply feature-specific overlays
|
|
408
|
+
*/
|
|
409
|
+
async applyFeatureOverlays(context) {
|
|
410
|
+
const features = context.features || [];
|
|
411
|
+
for (const feature of features) {
|
|
412
|
+
const overlayDir = import_node_path2.default.resolve(this.config.templatesDir, "features", feature);
|
|
413
|
+
await this.copyOverlayDirectory(overlayDir, context);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Helper to copy/merge an overlay directory into the target
|
|
418
|
+
*/
|
|
419
|
+
async copyOverlayDirectory(sourceDir, context) {
|
|
420
|
+
try {
|
|
421
|
+
await import_promises2.default.access(sourceDir);
|
|
422
|
+
} catch {
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
const files = await walk(sourceDir);
|
|
426
|
+
for (const filePath of files) {
|
|
427
|
+
const relativePath = import_node_path2.default.relative(sourceDir, filePath);
|
|
428
|
+
let content = await import_promises2.default.readFile(filePath, "utf-8");
|
|
429
|
+
try {
|
|
430
|
+
content = this.stubGenerator.render(content, context);
|
|
431
|
+
} catch {
|
|
432
|
+
}
|
|
433
|
+
await this.writeFile(context.targetDir, relativePath, content);
|
|
434
|
+
}
|
|
271
435
|
}
|
|
272
436
|
/**
|
|
273
437
|
* Write a file and track it.
|
|
@@ -275,7 +439,16 @@ var BaseGenerator = class {
|
|
|
275
439
|
async writeFile(basePath, relativePath, content) {
|
|
276
440
|
const fullPath = import_node_path2.default.resolve(basePath, relativePath);
|
|
277
441
|
await import_promises2.default.mkdir(import_node_path2.default.dirname(fullPath), { recursive: true });
|
|
278
|
-
|
|
442
|
+
let finalContent = content;
|
|
443
|
+
try {
|
|
444
|
+
const existingContent = await import_promises2.default.readFile(fullPath, "utf-8");
|
|
445
|
+
finalContent = this.fileMerger.merge(relativePath, existingContent, content);
|
|
446
|
+
if (finalContent !== content) {
|
|
447
|
+
this.log(`\u{1F504} Merged file: ${relativePath}`);
|
|
448
|
+
}
|
|
449
|
+
} catch {
|
|
450
|
+
}
|
|
451
|
+
await import_promises2.default.writeFile(fullPath, finalContent, "utf-8");
|
|
279
452
|
this.filesCreated.push(fullPath);
|
|
280
453
|
this.log(`\u{1F4C4} Created file: ${relativePath}`);
|
|
281
454
|
}
|
|
@@ -283,6 +456,20 @@ var BaseGenerator = class {
|
|
|
283
456
|
* Generate package.json content.
|
|
284
457
|
*/
|
|
285
458
|
generatePackageJson(context) {
|
|
459
|
+
const profile = context.profile || "core";
|
|
460
|
+
const baseDependencies = {
|
|
461
|
+
"@gravito/core": "^1.0.0-beta.5",
|
|
462
|
+
"@gravito/atlas": "^1.0.0-beta.5",
|
|
463
|
+
"@gravito/plasma": "^1.0.0-beta.5",
|
|
464
|
+
"@gravito/stream": "^1.0.0-beta.5"
|
|
465
|
+
};
|
|
466
|
+
if (profile === "enterprise" || profile === "scale") {
|
|
467
|
+
baseDependencies["@gravito/quasar"] = "^1.0.0-beta.5";
|
|
468
|
+
baseDependencies["@gravito/horizon"] = "^1.0.0-beta.5";
|
|
469
|
+
}
|
|
470
|
+
if (context.withSpectrum) {
|
|
471
|
+
baseDependencies["@gravito/spectrum"] = "^1.0.0-beta.1";
|
|
472
|
+
}
|
|
286
473
|
const pkg = {
|
|
287
474
|
name: context.nameKebabCase,
|
|
288
475
|
version: "0.1.0",
|
|
@@ -292,42 +479,197 @@ var BaseGenerator = class {
|
|
|
292
479
|
build: "bun build ./src/bootstrap.ts --outdir ./dist --target bun",
|
|
293
480
|
start: "bun run dist/bootstrap.js",
|
|
294
481
|
test: "bun test",
|
|
295
|
-
typecheck: "tsc --noEmit"
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
482
|
+
typecheck: "tsc --noEmit",
|
|
483
|
+
check: "bun run typecheck && bun run test",
|
|
484
|
+
"check:deps": "bun run scripts/check-dependencies.ts",
|
|
485
|
+
validate: "bun run check && bun run check:deps",
|
|
486
|
+
precommit: "bun run validate",
|
|
487
|
+
"docker:build": `docker build -t ${context.nameKebabCase} .`,
|
|
488
|
+
"docker:run": `docker run -it -p 3000:3000 ${context.nameKebabCase}`
|
|
299
489
|
},
|
|
490
|
+
dependencies: baseDependencies,
|
|
300
491
|
devDependencies: {
|
|
301
|
-
"
|
|
492
|
+
"bun-types": "latest",
|
|
302
493
|
typescript: "^5.0.0"
|
|
303
494
|
}
|
|
304
495
|
};
|
|
305
496
|
return JSON.stringify(pkg, null, 2);
|
|
306
497
|
}
|
|
498
|
+
/**
|
|
499
|
+
* Generate Dockerfile content.
|
|
500
|
+
*/
|
|
501
|
+
generateDockerfile(context) {
|
|
502
|
+
const entrypoint = context.architecture === "ddd" ? "dist/main.js" : "dist/bootstrap.js";
|
|
503
|
+
return `FROM oven/bun:1.0 AS base
|
|
504
|
+
WORKDIR /usr/src/app
|
|
505
|
+
|
|
506
|
+
# Install dependencies
|
|
507
|
+
FROM base AS install
|
|
508
|
+
RUN mkdir -p /temp/dev
|
|
509
|
+
COPY package.json bun.lockb /temp/dev/
|
|
510
|
+
RUN cd /temp/dev && bun install --frozen-lockfile
|
|
511
|
+
|
|
512
|
+
# Build application
|
|
513
|
+
FROM base AS build
|
|
514
|
+
COPY --from=install /temp/dev/node_modules node_modules
|
|
515
|
+
COPY . .
|
|
516
|
+
ENV NODE_ENV=production
|
|
517
|
+
RUN bun run build
|
|
518
|
+
|
|
519
|
+
# Final production image
|
|
520
|
+
FROM base AS release
|
|
521
|
+
COPY --from=build /usr/src/app/${entrypoint} index.js
|
|
522
|
+
COPY --from=build /usr/src/app/package.json .
|
|
523
|
+
|
|
524
|
+
# Create a non-root user for security
|
|
525
|
+
USER bun
|
|
526
|
+
EXPOSE 3000/tcp
|
|
527
|
+
ENTRYPOINT [ "bun", "run", "index.js" ]
|
|
528
|
+
`;
|
|
529
|
+
}
|
|
530
|
+
/**
|
|
531
|
+
* Generate .dockerignore content.
|
|
532
|
+
*/
|
|
533
|
+
generateDockerIgnore() {
|
|
534
|
+
return `node_modules
|
|
535
|
+
dist
|
|
536
|
+
.git
|
|
537
|
+
.env
|
|
538
|
+
*.log
|
|
539
|
+
.vscode
|
|
540
|
+
.idea
|
|
541
|
+
tests
|
|
542
|
+
`;
|
|
543
|
+
}
|
|
307
544
|
/**
|
|
308
545
|
* Generate .env.example content.
|
|
309
546
|
*/
|
|
310
547
|
generateEnvExample(context) {
|
|
311
|
-
|
|
312
|
-
|
|
548
|
+
const profile = context.profile || "core";
|
|
549
|
+
let envContent = `# ============================================================================
|
|
550
|
+
# Application Configuration
|
|
551
|
+
# ============================================================================
|
|
552
|
+
|
|
553
|
+
APP_NAME=${context.name}
|
|
313
554
|
APP_ENV=development
|
|
314
|
-
APP_KEY=
|
|
315
555
|
APP_DEBUG=true
|
|
316
556
|
APP_URL=http://localhost:3000
|
|
557
|
+
APP_KEY=
|
|
317
558
|
|
|
318
|
-
#
|
|
319
|
-
|
|
559
|
+
# ============================================================================
|
|
560
|
+
# Database Configuration
|
|
561
|
+
# ============================================================================
|
|
320
562
|
|
|
321
|
-
# Database
|
|
322
|
-
DB_CONNECTION
|
|
563
|
+
# Database Connection (sqlite, postgres, mysql)
|
|
564
|
+
DB_CONNECTION=${profile === "core" ? "sqlite" : "postgres"}
|
|
565
|
+
|
|
566
|
+
# SQLite Configuration (when DB_CONNECTION=sqlite)
|
|
323
567
|
DB_DATABASE=database/database.sqlite
|
|
324
568
|
|
|
325
|
-
#
|
|
326
|
-
|
|
569
|
+
# PostgreSQL Configuration (when DB_CONNECTION=postgres)
|
|
570
|
+
${profile !== "core" ? `DB_HOST=127.0.0.1
|
|
571
|
+
DB_PORT=5432
|
|
572
|
+
DB_DATABASE=${context.name}
|
|
573
|
+
DB_USERNAME=postgres
|
|
574
|
+
DB_PASSWORD=
|
|
575
|
+
DB_SSLMODE=prefer` : `# DB_HOST=127.0.0.1
|
|
576
|
+
# DB_PORT=5432
|
|
577
|
+
# DB_DATABASE=${context.name}
|
|
578
|
+
# DB_USERNAME=postgres
|
|
579
|
+
# DB_PASSWORD=
|
|
580
|
+
# DB_SSLMODE=prefer`}
|
|
581
|
+
|
|
582
|
+
# MySQL Configuration (when DB_CONNECTION=mysql)
|
|
583
|
+
# DB_HOST=127.0.0.1
|
|
584
|
+
# DB_PORT=3306
|
|
585
|
+
# DB_DATABASE=${context.name}
|
|
586
|
+
# DB_USERNAME=root
|
|
587
|
+
# DB_PASSWORD=
|
|
588
|
+
|
|
589
|
+
# ============================================================================
|
|
590
|
+
# Redis Configuration (@gravito/plasma)
|
|
591
|
+
# ============================================================================
|
|
592
|
+
|
|
593
|
+
# Default Redis Connection
|
|
594
|
+
REDIS_CONNECTION=default
|
|
595
|
+
REDIS_HOST=127.0.0.1
|
|
596
|
+
REDIS_PORT=6379
|
|
597
|
+
REDIS_PASSWORD=
|
|
598
|
+
REDIS_DB=0
|
|
599
|
+
|
|
600
|
+
# Redis Connection Options
|
|
601
|
+
REDIS_CONNECT_TIMEOUT=10000
|
|
602
|
+
REDIS_COMMAND_TIMEOUT=5000
|
|
603
|
+
REDIS_KEY_PREFIX=
|
|
604
|
+
REDIS_MAX_RETRIES=3
|
|
605
|
+
REDIS_RETRY_DELAY=1000
|
|
606
|
+
|
|
607
|
+
# Cache-specific Redis Connection (optional, falls back to default)
|
|
608
|
+
# REDIS_CACHE_HOST=127.0.0.1
|
|
609
|
+
# REDIS_CACHE_PORT=6379
|
|
610
|
+
# REDIS_CACHE_PASSWORD=
|
|
611
|
+
REDIS_CACHE_DB=1
|
|
612
|
+
|
|
613
|
+
# Queue-specific Redis Connection (optional, falls back to default)
|
|
614
|
+
# REDIS_QUEUE_HOST=127.0.0.1
|
|
615
|
+
# REDIS_QUEUE_PORT=6379
|
|
616
|
+
# REDIS_QUEUE_PASSWORD=
|
|
617
|
+
REDIS_QUEUE_DB=2
|
|
618
|
+
|
|
619
|
+
# ============================================================================
|
|
620
|
+
# Cache Configuration (@gravito/stasis)
|
|
621
|
+
# ============================================================================
|
|
622
|
+
|
|
623
|
+
# Cache Driver (memory, file, redis)
|
|
624
|
+
CACHE_DRIVER=${profile === "core" ? "memory" : "redis"}
|
|
625
|
+
|
|
626
|
+
# File Cache Path (when CACHE_DRIVER=file)
|
|
627
|
+
CACHE_PATH=storage/framework/cache
|
|
628
|
+
|
|
629
|
+
# Redis Cache Configuration (when CACHE_DRIVER=redis)
|
|
630
|
+
REDIS_CACHE_CONNECTION=cache
|
|
631
|
+
REDIS_CACHE_PREFIX=cache:
|
|
632
|
+
|
|
633
|
+
# ============================================================================
|
|
634
|
+
# Queue Configuration (@gravito/stream)
|
|
635
|
+
# ============================================================================
|
|
636
|
+
|
|
637
|
+
# Queue Connection (sync, memory, database, redis, kafka, sqs, rabbitmq)
|
|
638
|
+
QUEUE_CONNECTION=${profile === "core" ? "sync" : "redis"}
|
|
639
|
+
|
|
640
|
+
# Database Queue Configuration (when QUEUE_CONNECTION=database)
|
|
641
|
+
QUEUE_TABLE=jobs
|
|
642
|
+
|
|
643
|
+
# Redis Queue Configuration (when QUEUE_CONNECTION=redis)
|
|
644
|
+
REDIS_PREFIX=queue:
|
|
645
|
+
|
|
646
|
+
`;
|
|
647
|
+
if (profile === "enterprise" || profile === "scale") {
|
|
648
|
+
envContent += `# Kafka Queue Configuration (when QUEUE_CONNECTION=kafka)
|
|
649
|
+
# KAFKA_BROKERS=localhost:9092
|
|
650
|
+
# KAFKA_CONSUMER_GROUP_ID=gravito-workers
|
|
651
|
+
# KAFKA_CLIENT_ID=${context.name}
|
|
652
|
+
|
|
653
|
+
# AWS SQS Queue Configuration (when QUEUE_CONNECTION=sqs)
|
|
654
|
+
# AWS_REGION=us-east-1
|
|
655
|
+
# SQS_QUEUE_URL_PREFIX=
|
|
656
|
+
# SQS_VISIBILITY_TIMEOUT=30
|
|
657
|
+
# SQS_WAIT_TIME_SECONDS=20
|
|
658
|
+
|
|
659
|
+
# RabbitMQ Queue Configuration (when QUEUE_CONNECTION=rabbitmq)
|
|
660
|
+
# RABBITMQ_URL=amqp://localhost
|
|
661
|
+
# RABBITMQ_EXCHANGE=gravito.events
|
|
662
|
+
# RABBITMQ_EXCHANGE_TYPE=fanout
|
|
663
|
+
|
|
664
|
+
`;
|
|
665
|
+
}
|
|
666
|
+
envContent += `# ============================================================================
|
|
667
|
+
# Logging Configuration
|
|
668
|
+
# ============================================================================
|
|
327
669
|
|
|
328
|
-
# Logging
|
|
329
670
|
LOG_LEVEL=debug
|
|
330
671
|
`;
|
|
672
|
+
return envContent;
|
|
331
673
|
}
|
|
332
674
|
/**
|
|
333
675
|
* Generate .gitignore content.
|
|
@@ -379,6 +721,9 @@ coverage/
|
|
|
379
721
|
strict: true,
|
|
380
722
|
skipLibCheck: true,
|
|
381
723
|
declaration: true,
|
|
724
|
+
experimentalDecorators: true,
|
|
725
|
+
emitDecoratorMetadata: true,
|
|
726
|
+
types: ["bun-types"],
|
|
382
727
|
outDir: "./dist",
|
|
383
728
|
rootDir: "./src",
|
|
384
729
|
baseUrl: ".",
|
|
@@ -391,6 +736,430 @@ coverage/
|
|
|
391
736
|
};
|
|
392
737
|
return JSON.stringify(config, null, 2);
|
|
393
738
|
}
|
|
739
|
+
/**
|
|
740
|
+
* Generate check scripts for project validation.
|
|
741
|
+
*/
|
|
742
|
+
async generateCheckScripts(context) {
|
|
743
|
+
const scriptsDir = import_node_path2.default.resolve(context.targetDir, "scripts");
|
|
744
|
+
await import_promises2.default.mkdir(scriptsDir, { recursive: true });
|
|
745
|
+
await this.writeFile(
|
|
746
|
+
scriptsDir,
|
|
747
|
+
"check-dependencies.ts",
|
|
748
|
+
this.generateCheckDependenciesScript()
|
|
749
|
+
);
|
|
750
|
+
await this.writeFile(scriptsDir, "check.sh", this.generateCheckShellScript());
|
|
751
|
+
await this.writeFile(scriptsDir, "pre-commit.sh", this.generatePreCommitScript());
|
|
752
|
+
await this.writeFile(context.targetDir, "CHECK_SYSTEM.md", this.generateCheckSystemDoc(context));
|
|
753
|
+
}
|
|
754
|
+
/**
|
|
755
|
+
* Generate check-dependencies.ts script content.
|
|
756
|
+
*/
|
|
757
|
+
generateCheckDependenciesScript() {
|
|
758
|
+
return `/**
|
|
759
|
+
* \u76F8\u4F9D\u5957\u4EF6\u7248\u672C\u6AA2\u67E5\u8173\u672C
|
|
760
|
+
*
|
|
761
|
+
* \u6AA2\u67E5 package.json \u4E2D\u7684\u5957\u4EF6\u662F\u5426\u70BA\u6700\u65B0\u7A69\u5B9A\u7248\u672C
|
|
762
|
+
* \u4E26\u63D0\u4F9B\u66F4\u65B0\u5EFA\u8B70
|
|
763
|
+
*/
|
|
764
|
+
|
|
765
|
+
import { readFileSync } from 'fs'
|
|
766
|
+
import { join } from 'path'
|
|
767
|
+
|
|
768
|
+
interface PackageJson {
|
|
769
|
+
dependencies?: Record<string, string>
|
|
770
|
+
devDependencies?: Record<string, string>
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
interface PackageInfo {
|
|
774
|
+
name: string
|
|
775
|
+
current: string
|
|
776
|
+
latest: string
|
|
777
|
+
outdated: boolean
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
const colors = {
|
|
781
|
+
reset: '\\x1b[0m',
|
|
782
|
+
green: '\\x1b[32m',
|
|
783
|
+
yellow: '\\x1b[33m',
|
|
784
|
+
red: '\\x1b[31m',
|
|
785
|
+
blue: '\\x1b[36m',
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
function log(message: string, color: keyof typeof colors = 'reset') {
|
|
789
|
+
console.log(\`\${colors[color]}\${message}\${colors.reset}\`)
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
async function getLatestVersion(packageName: string): Promise<string | null> {
|
|
793
|
+
try {
|
|
794
|
+
const response = await fetch(\`https://registry.npmjs.org/\${packageName}/latest\`)
|
|
795
|
+
if (!response.ok) return null
|
|
796
|
+
const data = await response.json()
|
|
797
|
+
return data.version
|
|
798
|
+
} catch {
|
|
799
|
+
return null
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
function parseVersion(version: string): string {
|
|
804
|
+
// \u79FB\u9664 ^, ~, >= \u7B49\u524D\u7DB4
|
|
805
|
+
return version.replace(/^[\\^~>=<]/, '')
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
async function checkPackage(
|
|
809
|
+
name: string,
|
|
810
|
+
currentVersion: string
|
|
811
|
+
): Promise<PackageInfo | null> {
|
|
812
|
+
// \u8DF3\u904E\u672C\u5730\u9023\u7D50\u7684\u5957\u4EF6
|
|
813
|
+
if (currentVersion.startsWith('link:') || currentVersion.startsWith('workspace:')) {
|
|
814
|
+
return null
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
const current = parseVersion(currentVersion)
|
|
818
|
+
const latest = await getLatestVersion(name)
|
|
819
|
+
|
|
820
|
+
if (!latest) {
|
|
821
|
+
return null
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
return {
|
|
825
|
+
name,
|
|
826
|
+
current,
|
|
827
|
+
latest,
|
|
828
|
+
outdated: current !== latest,
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
async function main() {
|
|
833
|
+
log('\\n=== \u76F8\u4F9D\u5957\u4EF6\u7248\u672C\u6AA2\u67E5 ===\\n', 'blue')
|
|
834
|
+
|
|
835
|
+
const packageJsonPath = join(process.cwd(), 'package.json')
|
|
836
|
+
const packageJson: PackageJson = JSON.parse(
|
|
837
|
+
readFileSync(packageJsonPath, 'utf-8')
|
|
838
|
+
)
|
|
839
|
+
|
|
840
|
+
const allDependencies = {
|
|
841
|
+
...packageJson.dependencies,
|
|
842
|
+
...packageJson.devDependencies,
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
log(\`\u6AA2\u67E5 \${Object.keys(allDependencies).length} \u500B\u5957\u4EF6...\\n\`, 'yellow')
|
|
846
|
+
|
|
847
|
+
const results: PackageInfo[] = []
|
|
848
|
+
const outdated: PackageInfo[] = []
|
|
849
|
+
const upToDate: PackageInfo[] = []
|
|
850
|
+
|
|
851
|
+
// \u6AA2\u67E5\u6240\u6709\u5957\u4EF6
|
|
852
|
+
for (const [name, version] of Object.entries(allDependencies)) {
|
|
853
|
+
const info = await checkPackage(name, version)
|
|
854
|
+
if (info) {
|
|
855
|
+
results.push(info)
|
|
856
|
+
if (info.outdated) {
|
|
857
|
+
outdated.push(info)
|
|
858
|
+
} else {
|
|
859
|
+
upToDate.push(info)
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
// \u986F\u793A\u7D50\u679C
|
|
865
|
+
if (upToDate.length > 0) {
|
|
866
|
+
log(\`\\n\u2713 \u5DF2\u662F\u6700\u65B0\u7248\u672C (\${upToDate.length}):\`, 'green')
|
|
867
|
+
upToDate.forEach((pkg) => {
|
|
868
|
+
log(\` \${pkg.name}: \${pkg.current}\`, 'green')
|
|
869
|
+
})
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
if (outdated.length > 0) {
|
|
873
|
+
log(\`\\n\u26A0 \u9700\u8981\u66F4\u65B0 (\${outdated.length}):\`, 'yellow')
|
|
874
|
+
outdated.forEach((pkg) => {
|
|
875
|
+
log(\` \${pkg.name}: \${pkg.current} \u2192 \${pkg.latest}\`, 'yellow')
|
|
876
|
+
})
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
// \u7E3D\u7D50
|
|
880
|
+
log('\\n=== \u6AA2\u67E5\u7D50\u679C ===', 'blue')
|
|
881
|
+
log(\`\u7E3D\u8A08: \${results.length} \u500B\u5957\u4EF6\`, 'blue')
|
|
882
|
+
log(\`\u6700\u65B0: \${upToDate.length} \u500B\`, 'green')
|
|
883
|
+
log(\`\u9700\u66F4\u65B0: \${outdated.length} \u500B\`, outdated.length > 0 ? 'yellow' : 'green')
|
|
884
|
+
|
|
885
|
+
// \u5982\u679C\u6709\u9700\u8981\u66F4\u65B0\u7684\u5957\u4EF6\uFF0C\u8FD4\u56DE\u975E\u96F6\u9000\u51FA\u78BC
|
|
886
|
+
if (outdated.length > 0) {
|
|
887
|
+
log('\\n\u5EFA\u8B70\u57F7\u884C\u4EE5\u4E0B\u547D\u4EE4\u66F4\u65B0\u5957\u4EF6\uFF1A', 'yellow')
|
|
888
|
+
log(' bun update', 'yellow')
|
|
889
|
+
process.exit(1)
|
|
890
|
+
} else {
|
|
891
|
+
log('\\n\u2713 \u6240\u6709\u5957\u4EF6\u90FD\u662F\u6700\u65B0\u7248\u672C\uFF01', 'green')
|
|
892
|
+
process.exit(0)
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
main().catch((error) => {
|
|
897
|
+
log(\`\\n\u932F\u8AA4: \${error.message}\`, 'red')
|
|
898
|
+
process.exit(1)
|
|
899
|
+
})
|
|
900
|
+
`;
|
|
901
|
+
}
|
|
902
|
+
/**
|
|
903
|
+
* Generate check.sh script content.
|
|
904
|
+
*/
|
|
905
|
+
generateCheckShellScript() {
|
|
906
|
+
return `#!/bin/bash
|
|
907
|
+
|
|
908
|
+
# \u5C08\u6848\u6AA2\u67E5\u8173\u672C
|
|
909
|
+
# \u57F7\u884C\u6240\u6709\u5FC5\u8981\u7684\u6AA2\u67E5\uFF1A\u985E\u578B\u6AA2\u67E5\u3001\u6E2C\u8A66\u3001\u4F9D\u8CF4\u6AA2\u67E5\u7B49
|
|
910
|
+
|
|
911
|
+
set -e
|
|
912
|
+
|
|
913
|
+
# \u984F\u8272\u5B9A\u7FA9
|
|
914
|
+
GREEN='\\033[0;32m'
|
|
915
|
+
YELLOW='\\033[1;33m'
|
|
916
|
+
RED='\\033[0;31m'
|
|
917
|
+
BLUE='\\033[0;34m'
|
|
918
|
+
NC='\\033[0m' # No Color
|
|
919
|
+
|
|
920
|
+
echo -e "\${BLUE}=== \u5C08\u6848\u6AA2\u67E5 ===\${NC}\\n"
|
|
921
|
+
|
|
922
|
+
# \u6AA2\u67E5\u662F\u5426\u5728\u6B63\u78BA\u7684\u76EE\u9304
|
|
923
|
+
if [ ! -f "package.json" ]; then
|
|
924
|
+
echo -e "\${RED}\u932F\u8AA4: \u8ACB\u5728\u5C08\u6848\u6839\u76EE\u9304\u57F7\u884C\u6B64\u8173\u672C\${NC}"
|
|
925
|
+
exit 1
|
|
926
|
+
fi
|
|
927
|
+
|
|
928
|
+
# \u6AA2\u67E5 Bun \u662F\u5426\u5B89\u88DD
|
|
929
|
+
if ! command -v bun &> /dev/null; then
|
|
930
|
+
echo -e "\${RED}\u932F\u8AA4: \u672A\u627E\u5230 bun\uFF0C\u8ACB\u5148\u5B89\u88DD Bun\${NC}"
|
|
931
|
+
exit 1
|
|
932
|
+
fi
|
|
933
|
+
|
|
934
|
+
# 1. \u985E\u578B\u6AA2\u67E5
|
|
935
|
+
echo -e "\${YELLOW}[1/3] \u57F7\u884C\u985E\u578B\u6AA2\u67E5...\${NC}"
|
|
936
|
+
if bun run typecheck; then
|
|
937
|
+
echo -e "\${GREEN}\u2713 \u985E\u578B\u6AA2\u67E5\u901A\u904E\${NC}\\n"
|
|
938
|
+
else
|
|
939
|
+
echo -e "\${RED}\u2717 \u985E\u578B\u6AA2\u67E5\u5931\u6557\${NC}"
|
|
940
|
+
exit 1
|
|
941
|
+
fi
|
|
942
|
+
|
|
943
|
+
# 2. \u57F7\u884C\u6E2C\u8A66
|
|
944
|
+
echo -e "\${YELLOW}[2/3] \u57F7\u884C\u6E2C\u8A66...\${NC}"
|
|
945
|
+
if bun test; then
|
|
946
|
+
echo -e "\${GREEN}\u2713 \u6E2C\u8A66\u901A\u904E\${NC}\\n"
|
|
947
|
+
else
|
|
948
|
+
echo -e "\${RED}\u2717 \u6E2C\u8A66\u5931\u6557\${NC}"
|
|
949
|
+
exit 1
|
|
950
|
+
fi
|
|
951
|
+
|
|
952
|
+
# 3. \u6AA2\u67E5\u4F9D\u8CF4\u7248\u672C\uFF08\u53EF\u9078\uFF0C\u56E0\u70BA\u9700\u8981\u7DB2\u8DEF\u9023\u7DDA\uFF09
|
|
953
|
+
echo -e "\${YELLOW}[3/3] \u6AA2\u67E5\u4F9D\u8CF4\u7248\u672C...\${NC}"
|
|
954
|
+
if bun run check:deps; then
|
|
955
|
+
echo -e "\${GREEN}\u2713 \u4F9D\u8CF4\u6AA2\u67E5\u5B8C\u6210\${NC}\\n"
|
|
956
|
+
else
|
|
957
|
+
echo -e "\${YELLOW}\u26A0 \u4F9D\u8CF4\u6AA2\u67E5\u6709\u8B66\u544A\uFF08\u67D0\u4E9B\u5957\u4EF6\u53EF\u80FD\u9700\u8981\u66F4\u65B0\uFF09\${NC}\\n"
|
|
958
|
+
fi
|
|
959
|
+
|
|
960
|
+
echo -e "\${GREEN}=== \u6240\u6709\u6AA2\u67E5\u5B8C\u6210 ===\${NC}"
|
|
961
|
+
`;
|
|
962
|
+
}
|
|
963
|
+
/**
|
|
964
|
+
* Generate pre-commit.sh script content.
|
|
965
|
+
*/
|
|
966
|
+
generatePreCommitScript() {
|
|
967
|
+
return `#!/bin/bash
|
|
968
|
+
|
|
969
|
+
# Pre-commit Hook
|
|
970
|
+
# \u5728 git commit \u524D\u81EA\u52D5\u57F7\u884C\u6AA2\u67E5
|
|
971
|
+
#
|
|
972
|
+
# \u5B89\u88DD\u65B9\u5F0F\uFF1A
|
|
973
|
+
# ln -s ../../scripts/pre-commit.sh .git/hooks/pre-commit
|
|
974
|
+
# \u6216
|
|
975
|
+
# cp scripts/pre-commit.sh .git/hooks/pre-commit
|
|
976
|
+
# chmod +x .git/hooks/pre-commit
|
|
977
|
+
|
|
978
|
+
set -e
|
|
979
|
+
|
|
980
|
+
# \u984F\u8272\u5B9A\u7FA9
|
|
981
|
+
GREEN='\\033[0;32m'
|
|
982
|
+
YELLOW='\\033[1;33m'
|
|
983
|
+
RED='\\033[0;31m'
|
|
984
|
+
BLUE='\\033[0;34m'
|
|
985
|
+
NC='\\033[0m' # No Color
|
|
986
|
+
|
|
987
|
+
echo -e "\${BLUE}=== Pre-commit \u6AA2\u67E5 ===\${NC}\\n"
|
|
988
|
+
|
|
989
|
+
# \u5207\u63DB\u5230\u5C08\u6848\u6839\u76EE\u9304
|
|
990
|
+
cd "$(git rev-parse --show-toplevel)"
|
|
991
|
+
|
|
992
|
+
# \u6AA2\u67E5\u662F\u5426\u5728\u6B63\u78BA\u7684\u76EE\u9304
|
|
993
|
+
if [ ! -f "package.json" ]; then
|
|
994
|
+
echo -e "\${RED}\u932F\u8AA4: \u627E\u4E0D\u5230 package.json\${NC}"
|
|
995
|
+
exit 1
|
|
996
|
+
fi
|
|
997
|
+
|
|
998
|
+
# \u6AA2\u67E5 Bun \u662F\u5426\u5B89\u88DD
|
|
999
|
+
if ! command -v bun &> /dev/null; then
|
|
1000
|
+
echo -e "\${RED}\u932F\u8AA4: \u672A\u627E\u5230 bun\uFF0C\u8ACB\u5148\u5B89\u88DD Bun\${NC}"
|
|
1001
|
+
exit 1
|
|
1002
|
+
fi
|
|
1003
|
+
|
|
1004
|
+
# 1. \u985E\u578B\u6AA2\u67E5\uFF08\u5FEB\u901F\u6AA2\u67E5\uFF09
|
|
1005
|
+
echo -e "\${YELLOW}[1/2] \u57F7\u884C\u985E\u578B\u6AA2\u67E5...\${NC}"
|
|
1006
|
+
if bun run typecheck; then
|
|
1007
|
+
echo -e "\${GREEN}\u2713 \u985E\u578B\u6AA2\u67E5\u901A\u904E\${NC}\\n"
|
|
1008
|
+
else
|
|
1009
|
+
echo -e "\${RED}\u2717 \u985E\u578B\u6AA2\u67E5\u5931\u6557\${NC}"
|
|
1010
|
+
echo -e "\${YELLOW}\u63D0\u793A: \u8ACB\u4FEE\u6B63\u985E\u578B\u932F\u8AA4\u5F8C\u518D\u63D0\u4EA4\${NC}"
|
|
1011
|
+
exit 1
|
|
1012
|
+
fi
|
|
1013
|
+
|
|
1014
|
+
# 2. \u57F7\u884C\u6E2C\u8A66\uFF08\u53EF\u9078\uFF0C\u5982\u679C\u6E2C\u8A66\u6642\u9593\u8F03\u9577\u53EF\u4EE5\u8A3B\u89E3\u6389\uFF09
|
|
1015
|
+
echo -e "\${YELLOW}[2/2] \u57F7\u884C\u6E2C\u8A66...\${NC}"
|
|
1016
|
+
if bun test; then
|
|
1017
|
+
echo -e "\${GREEN}\u2713 \u6E2C\u8A66\u901A\u904E\${NC}\\n"
|
|
1018
|
+
else
|
|
1019
|
+
echo -e "\${RED}\u2717 \u6E2C\u8A66\u5931\u6557\${NC}"
|
|
1020
|
+
echo -e "\${YELLOW}\u63D0\u793A: \u8ACB\u4FEE\u6B63\u6E2C\u8A66\u932F\u8AA4\u5F8C\u518D\u63D0\u4EA4\${NC}"
|
|
1021
|
+
exit 1
|
|
1022
|
+
fi
|
|
1023
|
+
|
|
1024
|
+
echo -e "\${GREEN}=== Pre-commit \u6AA2\u67E5\u901A\u904E ===\${NC}\\n"
|
|
1025
|
+
`;
|
|
1026
|
+
}
|
|
1027
|
+
/**
|
|
1028
|
+
* Generate CHECK_SYSTEM.md documentation.
|
|
1029
|
+
*/
|
|
1030
|
+
generateCheckSystemDoc(context) {
|
|
1031
|
+
return `# \u5C08\u6848\u6AA2\u67E5\u7CFB\u7D71
|
|
1032
|
+
|
|
1033
|
+
\u672C\u5C08\u6848\u5DF2\u5EFA\u7ACB\u5B8C\u6574\u7684\u672C\u5730\u6AA2\u67E5\u6A5F\u5236\uFF0C\u7121\u9700\u4F9D\u8CF4 GitHub CI\u3002
|
|
1034
|
+
|
|
1035
|
+
## \u5FEB\u901F\u958B\u59CB
|
|
1036
|
+
|
|
1037
|
+
### \u57F7\u884C\u5B8C\u6574\u6AA2\u67E5
|
|
1038
|
+
\`\`\`bash
|
|
1039
|
+
bun run validate
|
|
1040
|
+
\`\`\`
|
|
1041
|
+
|
|
1042
|
+
### \u57F7\u884C\u55AE\u9805\u6AA2\u67E5
|
|
1043
|
+
\`\`\`bash
|
|
1044
|
+
# \u985E\u578B\u6AA2\u67E5
|
|
1045
|
+
bun run typecheck
|
|
1046
|
+
|
|
1047
|
+
# \u6E2C\u8A66
|
|
1048
|
+
bun run test
|
|
1049
|
+
|
|
1050
|
+
# \u4F9D\u8CF4\u7248\u672C\u6AA2\u67E5
|
|
1051
|
+
bun run check:deps
|
|
1052
|
+
\`\`\`
|
|
1053
|
+
|
|
1054
|
+
## \u53EF\u7528\u547D\u4EE4
|
|
1055
|
+
|
|
1056
|
+
### Package.json \u8173\u672C
|
|
1057
|
+
|
|
1058
|
+
| \u547D\u4EE4 | \u8AAA\u660E |
|
|
1059
|
+
|------|------|
|
|
1060
|
+
| \`bun run typecheck\` | TypeScript \u985E\u578B\u6AA2\u67E5 |
|
|
1061
|
+
| \`bun run test\` | \u57F7\u884C\u6240\u6709\u6E2C\u8A66 |
|
|
1062
|
+
| \`bun run check\` | \u985E\u578B\u6AA2\u67E5 + \u6E2C\u8A66 |
|
|
1063
|
+
| \`bun run check:deps\` | \u6AA2\u67E5\u4F9D\u8CF4\u7248\u672C |
|
|
1064
|
+
| \`bun run validate\` | \u5B8C\u6574\u9A57\u8B49\uFF08\u985E\u578B + \u6E2C\u8A66 + \u4F9D\u8CF4\uFF09 |
|
|
1065
|
+
| \`bun run precommit\` | \u7B49\u540C\u65BC \`validate\` |
|
|
1066
|
+
|
|
1067
|
+
### Shell \u8173\u672C
|
|
1068
|
+
|
|
1069
|
+
| \u8173\u672C | \u8AAA\u660E |
|
|
1070
|
+
|------|------|
|
|
1071
|
+
| \`./scripts/check.sh\` | \u5B8C\u6574\u5C08\u6848\u6AA2\u67E5\uFF08Shell \u7248\u672C\uFF09 |
|
|
1072
|
+
| \`./scripts/pre-commit.sh\` | Pre-commit hook \u8173\u672C |
|
|
1073
|
+
|
|
1074
|
+
## Pre-commit Hook\uFF08\u63A8\u85A6\uFF09
|
|
1075
|
+
|
|
1076
|
+
\u5B89\u88DD pre-commit hook \u5F8C\uFF0C\u6BCF\u6B21 \`git commit\` \u524D\u6703\u81EA\u52D5\u57F7\u884C\u6AA2\u67E5\uFF1A
|
|
1077
|
+
|
|
1078
|
+
\`\`\`bash
|
|
1079
|
+
# \u5B89\u88DD pre-commit hook
|
|
1080
|
+
ln -s ../../scripts/pre-commit.sh .git/hooks/pre-commit
|
|
1081
|
+
|
|
1082
|
+
# \u6216\u4F7F\u7528\u8907\u88FD\u65B9\u5F0F
|
|
1083
|
+
cp scripts/pre-commit.sh .git/hooks/pre-commit
|
|
1084
|
+
chmod +x .git/hooks/pre-commit
|
|
1085
|
+
\`\`\`
|
|
1086
|
+
|
|
1087
|
+
**\u529F\u80FD\uFF1A**
|
|
1088
|
+
- \u2705 \u81EA\u52D5\u57F7\u884C\u985E\u578B\u6AA2\u67E5
|
|
1089
|
+
- \u2705 \u81EA\u52D5\u57F7\u884C\u6E2C\u8A66
|
|
1090
|
+
- \u274C \u6AA2\u67E5\u5931\u6557\u6642\u963B\u6B62\u63D0\u4EA4
|
|
1091
|
+
|
|
1092
|
+
**\u8DF3\u904E\u6AA2\u67E5\uFF08\u4E0D\u63A8\u85A6\uFF09\uFF1A**
|
|
1093
|
+
\`\`\`bash
|
|
1094
|
+
git commit --no-verify -m "\u7DCA\u6025\u4FEE\u5FA9"
|
|
1095
|
+
\`\`\`
|
|
1096
|
+
|
|
1097
|
+
## \u6AA2\u67E5\u9805\u76EE
|
|
1098
|
+
|
|
1099
|
+
### 1. \u985E\u578B\u6AA2\u67E5
|
|
1100
|
+
- \u4F7F\u7528 \`tsc --noEmit\` \u6AA2\u67E5 TypeScript \u985E\u578B
|
|
1101
|
+
- \u78BA\u4FDD\u6C92\u6709\u985E\u578B\u932F\u8AA4
|
|
1102
|
+
|
|
1103
|
+
### 2. \u6E2C\u8A66
|
|
1104
|
+
- \u57F7\u884C\u6240\u6709\u55AE\u5143\u6E2C\u8A66\u548C\u6574\u5408\u6E2C\u8A66
|
|
1105
|
+
- \u78BA\u4FDD\u6E2C\u8A66\u901A\u904E
|
|
1106
|
+
|
|
1107
|
+
### 3. \u4F9D\u8CF4\u6AA2\u67E5\uFF08\u53EF\u9078\uFF09
|
|
1108
|
+
- \u6AA2\u67E5\u5957\u4EF6\u7248\u672C\u662F\u5426\u70BA\u6700\u65B0
|
|
1109
|
+
- \u63D0\u4F9B\u66F4\u65B0\u5EFA\u8B70
|
|
1110
|
+
- \u9700\u8981\u7DB2\u8DEF\u9023\u7DDA
|
|
1111
|
+
|
|
1112
|
+
## \u5DE5\u4F5C\u6D41\u7A0B\u5EFA\u8B70
|
|
1113
|
+
|
|
1114
|
+
### \u958B\u767C\u6642
|
|
1115
|
+
1. \u958B\u767C\u529F\u80FD
|
|
1116
|
+
2. \u63D0\u4EA4\u524D\u57F7\u884C \`bun run validate\`
|
|
1117
|
+
3. \u4FEE\u6B63\u554F\u984C
|
|
1118
|
+
4. \u63D0\u4EA4\u7A0B\u5F0F\u78BC
|
|
1119
|
+
|
|
1120
|
+
### \u4F7F\u7528 Pre-commit Hook\uFF08\u63A8\u85A6\uFF09
|
|
1121
|
+
1. \u5B89\u88DD pre-commit hook\uFF08\u53EA\u9700\u4E00\u6B21\uFF09
|
|
1122
|
+
2. \u6B63\u5E38\u958B\u767C\u548C\u63D0\u4EA4
|
|
1123
|
+
3. \u6AA2\u67E5\u6703\u81EA\u52D5\u57F7\u884C
|
|
1124
|
+
4. \u5982\u6709\u554F\u984C\uFF0C\u4FEE\u6B63\u5F8C\u91CD\u65B0\u63D0\u4EA4
|
|
1125
|
+
|
|
1126
|
+
## \u6A94\u6848\u7D50\u69CB
|
|
1127
|
+
|
|
1128
|
+
\`\`\`
|
|
1129
|
+
${context.nameKebabCase}/
|
|
1130
|
+
\u251C\u2500\u2500 package.json # \u6AA2\u67E5\u8173\u672C\u5B9A\u7FA9
|
|
1131
|
+
\u251C\u2500\u2500 scripts/
|
|
1132
|
+
\u2502 \u251C\u2500\u2500 check.sh # \u5B8C\u6574\u6AA2\u67E5\u8173\u672C\uFF08Shell\uFF09
|
|
1133
|
+
\u2502 \u251C\u2500\u2500 check-dependencies.ts # \u4F9D\u8CF4\u7248\u672C\u6AA2\u67E5
|
|
1134
|
+
\u2502 \u2514\u2500\u2500 pre-commit.sh # Pre-commit hook
|
|
1135
|
+
\u2514\u2500\u2500 CHECK_SYSTEM.md # \u672C\u6587\u4EF6
|
|
1136
|
+
\`\`\`
|
|
1137
|
+
|
|
1138
|
+
## \u6CE8\u610F\u4E8B\u9805
|
|
1139
|
+
|
|
1140
|
+
1. **\u4F9D\u8CF4\u6AA2\u67E5\u9700\u8981\u7DB2\u8DEF\u9023\u7DDA**\uFF1A\`check:deps\` \u9700\u8981\u9023\u63A5\u5230 npm registry
|
|
1141
|
+
2. **\u6E2C\u8A66\u6642\u9593**\uFF1A\u5982\u679C\u6E2C\u8A66\u6642\u9593\u8F03\u9577\uFF0C\u53EF\u4EE5\u7DE8\u8F2F \`pre-commit.sh\` \u8A3B\u89E3\u6389\u6E2C\u8A66\u90E8\u5206
|
|
1142
|
+
3. **\u985E\u578B\u932F\u8AA4**\uFF1A\u5C08\u6848\u4E2D\u53EF\u80FD\u9084\u6709\u4E00\u4E9B\u65E2\u6709\u7684\u985E\u578B\u932F\u8AA4\uFF0C\u5EFA\u8B70\u9010\u6B65\u4FEE\u6B63
|
|
1143
|
+
|
|
1144
|
+
## \u6545\u969C\u6392\u9664
|
|
1145
|
+
|
|
1146
|
+
### \u6AA2\u67E5\u5931\u6557
|
|
1147
|
+
1. \u67E5\u770B\u932F\u8AA4\u8A0A\u606F
|
|
1148
|
+
2. \u4FEE\u6B63\u554F\u984C
|
|
1149
|
+
3. \u91CD\u65B0\u57F7\u884C\u6AA2\u67E5
|
|
1150
|
+
|
|
1151
|
+
### \u8DF3\u904E\u6AA2\u67E5
|
|
1152
|
+
\u53EA\u6709\u5728\u7DCA\u6025\u60C5\u6CC1\u4E0B\u624D\u4F7F\u7528\uFF1A
|
|
1153
|
+
\`\`\`bash
|
|
1154
|
+
git commit --no-verify
|
|
1155
|
+
\`\`\`
|
|
1156
|
+
|
|
1157
|
+
### \u79FB\u9664 Pre-commit Hook
|
|
1158
|
+
\`\`\`bash
|
|
1159
|
+
rm .git/hooks/pre-commit
|
|
1160
|
+
\`\`\`
|
|
1161
|
+
`;
|
|
1162
|
+
}
|
|
394
1163
|
/**
|
|
395
1164
|
* Log a message if verbose mode is enabled.
|
|
396
1165
|
*/
|
|
@@ -448,6 +1217,11 @@ var CleanArchitectureGenerator = class extends BaseGenerator {
|
|
|
448
1217
|
{ type: "file", name: "logging.ts", content: this.generateLoggingConfig() }
|
|
449
1218
|
]
|
|
450
1219
|
},
|
|
1220
|
+
{
|
|
1221
|
+
type: "directory",
|
|
1222
|
+
name: "database",
|
|
1223
|
+
children: [{ type: "file", name: ".gitkeep", content: "" }]
|
|
1224
|
+
},
|
|
451
1225
|
{
|
|
452
1226
|
type: "directory",
|
|
453
1227
|
name: "src",
|
|
@@ -460,16 +1234,12 @@ var CleanArchitectureGenerator = class extends BaseGenerator {
|
|
|
460
1234
|
{
|
|
461
1235
|
type: "directory",
|
|
462
1236
|
name: "Entities",
|
|
463
|
-
children: [
|
|
464
|
-
{ type: "file", name: "Entity.ts", content: this.generateBaseEntity() },
|
|
465
|
-
{ type: "file", name: "User.ts", content: this.generateUserEntity() }
|
|
466
|
-
]
|
|
1237
|
+
children: [{ type: "file", name: "User.ts", content: this.generateUserEntity() }]
|
|
467
1238
|
},
|
|
468
1239
|
{
|
|
469
1240
|
type: "directory",
|
|
470
1241
|
name: "ValueObjects",
|
|
471
1242
|
children: [
|
|
472
|
-
{ type: "file", name: "ValueObject.ts", content: this.generateBaseValueObject() },
|
|
473
1243
|
{ type: "file", name: "Email.ts", content: this.generateEmailValueObject() }
|
|
474
1244
|
]
|
|
475
1245
|
},
|
|
@@ -487,13 +1257,7 @@ var CleanArchitectureGenerator = class extends BaseGenerator {
|
|
|
487
1257
|
{
|
|
488
1258
|
type: "directory",
|
|
489
1259
|
name: "Exceptions",
|
|
490
|
-
children: [
|
|
491
|
-
{
|
|
492
|
-
type: "file",
|
|
493
|
-
name: "DomainException.ts",
|
|
494
|
-
content: this.generateDomainException()
|
|
495
|
-
}
|
|
496
|
-
]
|
|
1260
|
+
children: [{ type: "file", name: ".gitkeep", content: "" }]
|
|
497
1261
|
}
|
|
498
1262
|
]
|
|
499
1263
|
},
|
|
@@ -576,6 +1340,11 @@ var CleanArchitectureGenerator = class extends BaseGenerator {
|
|
|
576
1340
|
type: "directory",
|
|
577
1341
|
name: "Providers",
|
|
578
1342
|
children: [
|
|
1343
|
+
{
|
|
1344
|
+
type: "file",
|
|
1345
|
+
name: "index.ts",
|
|
1346
|
+
content: this.generateProvidersIndex()
|
|
1347
|
+
},
|
|
579
1348
|
{
|
|
580
1349
|
type: "file",
|
|
581
1350
|
name: "AppServiceProvider.ts",
|
|
@@ -585,6 +1354,16 @@ var CleanArchitectureGenerator = class extends BaseGenerator {
|
|
|
585
1354
|
type: "file",
|
|
586
1355
|
name: "RepositoryServiceProvider.ts",
|
|
587
1356
|
content: this.generateRepositoryServiceProvider()
|
|
1357
|
+
},
|
|
1358
|
+
{
|
|
1359
|
+
type: "file",
|
|
1360
|
+
name: "MiddlewareProvider.ts",
|
|
1361
|
+
content: this.generateMiddlewareProvider()
|
|
1362
|
+
},
|
|
1363
|
+
{
|
|
1364
|
+
type: "file",
|
|
1365
|
+
name: "RouteProvider.ts",
|
|
1366
|
+
content: this.generateRouteProvider()
|
|
588
1367
|
}
|
|
589
1368
|
]
|
|
590
1369
|
}
|
|
@@ -714,42 +1493,6 @@ var CleanArchitectureGenerator = class extends BaseGenerator {
|
|
|
714
1493
|
console: { driver: 'console', level: process.env.LOG_LEVEL ?? 'debug' },
|
|
715
1494
|
},
|
|
716
1495
|
}
|
|
717
|
-
`;
|
|
718
|
-
}
|
|
719
|
-
// ─────────────────────────────────────────────────────────────
|
|
720
|
-
// Domain Layer
|
|
721
|
-
// ─────────────────────────────────────────────────────────────
|
|
722
|
-
generateBaseEntity() {
|
|
723
|
-
return `/**
|
|
724
|
-
* Base Entity
|
|
725
|
-
*
|
|
726
|
-
* All domain entities extend this base class.
|
|
727
|
-
* Entities have identity and lifecycle.
|
|
728
|
-
*
|
|
729
|
-
* IMPORTANT: This layer must have NO external dependencies.
|
|
730
|
-
*/
|
|
731
|
-
|
|
732
|
-
export abstract class Entity<T> {
|
|
733
|
-
protected readonly _id: T
|
|
734
|
-
|
|
735
|
-
constructor(id: T) {
|
|
736
|
-
this._id = id
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
get id(): T {
|
|
740
|
-
return this._id
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
equals(other: Entity<T>): boolean {
|
|
744
|
-
if (other === null || other === undefined) {
|
|
745
|
-
return false
|
|
746
|
-
}
|
|
747
|
-
if (!(other instanceof Entity)) {
|
|
748
|
-
return false
|
|
749
|
-
}
|
|
750
|
-
return this._id === other._id
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
1496
|
`;
|
|
754
1497
|
}
|
|
755
1498
|
generateUserEntity() {
|
|
@@ -760,7 +1503,7 @@ export abstract class Entity<T> {
|
|
|
760
1503
|
* Contains business logic related to users.
|
|
761
1504
|
*/
|
|
762
1505
|
|
|
763
|
-
import { Entity } from '
|
|
1506
|
+
import { Entity } from '@gravito/enterprise'
|
|
764
1507
|
import { Email } from '../ValueObjects/Email'
|
|
765
1508
|
|
|
766
1509
|
export interface UserProps {
|
|
@@ -810,30 +1553,6 @@ export class User extends Entity<string> {
|
|
|
810
1553
|
this.props.updatedAt = new Date()
|
|
811
1554
|
}
|
|
812
1555
|
}
|
|
813
|
-
`;
|
|
814
|
-
}
|
|
815
|
-
generateBaseValueObject() {
|
|
816
|
-
return `/**
|
|
817
|
-
* Base Value Object
|
|
818
|
-
*
|
|
819
|
-
* Value objects are immutable and compared by value.
|
|
820
|
-
* They have no identity of their own.
|
|
821
|
-
*/
|
|
822
|
-
|
|
823
|
-
export abstract class ValueObject<T> {
|
|
824
|
-
protected readonly props: T
|
|
825
|
-
|
|
826
|
-
constructor(props: T) {
|
|
827
|
-
this.props = Object.freeze(props)
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
equals(other: ValueObject<T>): boolean {
|
|
831
|
-
if (other === null || other === undefined) {
|
|
832
|
-
return false
|
|
833
|
-
}
|
|
834
|
-
return JSON.stringify(this.props) === JSON.stringify(other.props)
|
|
835
|
-
}
|
|
836
|
-
}
|
|
837
1556
|
`;
|
|
838
1557
|
}
|
|
839
1558
|
generateEmailValueObject() {
|
|
@@ -843,7 +1562,7 @@ export abstract class ValueObject<T> {
|
|
|
843
1562
|
* Encapsulates email validation and comparison.
|
|
844
1563
|
*/
|
|
845
1564
|
|
|
846
|
-
import { ValueObject } from '
|
|
1565
|
+
import { ValueObject } from '@gravito/enterprise'
|
|
847
1566
|
|
|
848
1567
|
interface EmailProps {
|
|
849
1568
|
value: string
|
|
@@ -884,43 +1603,11 @@ export class Email extends ValueObject<EmailProps> {
|
|
|
884
1603
|
* Implementations are in Infrastructure layer.
|
|
885
1604
|
*/
|
|
886
1605
|
|
|
1606
|
+
import type { Repository } from '@gravito/enterprise'
|
|
887
1607
|
import type { User } from '../Entities/User'
|
|
888
1608
|
|
|
889
|
-
export interface IUserRepository {
|
|
890
|
-
findById(id: string): Promise<User | null>
|
|
1609
|
+
export interface IUserRepository extends Repository<User, string> {
|
|
891
1610
|
findByEmail(email: string): Promise<User | null>
|
|
892
|
-
save(user: User): Promise<void>
|
|
893
|
-
delete(id: string): Promise<void>
|
|
894
|
-
findAll(): Promise<User[]>
|
|
895
|
-
}
|
|
896
|
-
`;
|
|
897
|
-
}
|
|
898
|
-
generateDomainException() {
|
|
899
|
-
return `/**
|
|
900
|
-
* Domain Exception
|
|
901
|
-
*
|
|
902
|
-
* Base exception for domain-level errors.
|
|
903
|
-
*/
|
|
904
|
-
|
|
905
|
-
export class DomainException extends Error {
|
|
906
|
-
constructor(message: string) {
|
|
907
|
-
super(message)
|
|
908
|
-
this.name = 'DomainException'
|
|
909
|
-
}
|
|
910
|
-
}
|
|
911
|
-
|
|
912
|
-
export class EntityNotFoundException extends DomainException {
|
|
913
|
-
constructor(entity: string, id: string) {
|
|
914
|
-
super(\`\${entity} with id \${id} not found\`)
|
|
915
|
-
this.name = 'EntityNotFoundException'
|
|
916
|
-
}
|
|
917
|
-
}
|
|
918
|
-
|
|
919
|
-
export class InvalidValueException extends DomainException {
|
|
920
|
-
constructor(message: string) {
|
|
921
|
-
super(message)
|
|
922
|
-
this.name = 'InvalidValueException'
|
|
923
|
-
}
|
|
924
1611
|
}
|
|
925
1612
|
`;
|
|
926
1613
|
}
|
|
@@ -934,6 +1621,7 @@ export class InvalidValueException extends DomainException {
|
|
|
934
1621
|
* Application service for creating new users.
|
|
935
1622
|
*/
|
|
936
1623
|
|
|
1624
|
+
import { UseCase } from '@gravito/enterprise'
|
|
937
1625
|
import { User } from '../../../Domain/Entities/User'
|
|
938
1626
|
import type { IUserRepository } from '../../../Domain/Interfaces/IUserRepository'
|
|
939
1627
|
import type { UserDTO } from '../../DTOs/UserDTO'
|
|
@@ -947,8 +1635,10 @@ export interface CreateUserOutput {
|
|
|
947
1635
|
user: UserDTO
|
|
948
1636
|
}
|
|
949
1637
|
|
|
950
|
-
export class CreateUserUseCase {
|
|
951
|
-
constructor(private userRepository: IUserRepository) {
|
|
1638
|
+
export class CreateUserUseCase extends UseCase<CreateUserInput, CreateUserOutput> {
|
|
1639
|
+
constructor(private userRepository: IUserRepository) {
|
|
1640
|
+
super()
|
|
1641
|
+
}
|
|
952
1642
|
|
|
953
1643
|
async execute(input: CreateUserInput): Promise<CreateUserOutput> {
|
|
954
1644
|
// Check if email already exists
|
|
@@ -985,8 +1675,8 @@ export class CreateUserUseCase {
|
|
|
985
1675
|
* Get User Use Case
|
|
986
1676
|
*/
|
|
987
1677
|
|
|
1678
|
+
import { UseCase } from '@gravito/enterprise'
|
|
988
1679
|
import type { IUserRepository } from '../../../Domain/Interfaces/IUserRepository'
|
|
989
|
-
import { EntityNotFoundException } from '../../../Domain/Exceptions/DomainException'
|
|
990
1680
|
import type { UserDTO } from '../../DTOs/UserDTO'
|
|
991
1681
|
|
|
992
1682
|
export interface GetUserInput {
|
|
@@ -997,14 +1687,16 @@ export interface GetUserOutput {
|
|
|
997
1687
|
user: UserDTO
|
|
998
1688
|
}
|
|
999
1689
|
|
|
1000
|
-
export class GetUserUseCase {
|
|
1001
|
-
constructor(private userRepository: IUserRepository) {
|
|
1690
|
+
export class GetUserUseCase extends UseCase<GetUserInput, GetUserOutput> {
|
|
1691
|
+
constructor(private userRepository: IUserRepository) {
|
|
1692
|
+
super()
|
|
1693
|
+
}
|
|
1002
1694
|
|
|
1003
1695
|
async execute(input: GetUserInput): Promise<GetUserOutput> {
|
|
1004
1696
|
const user = await this.userRepository.findById(input.id)
|
|
1005
1697
|
|
|
1006
1698
|
if (!user) {
|
|
1007
|
-
throw new
|
|
1699
|
+
throw new Error(\`User with id \${input.id} not found\`)
|
|
1008
1700
|
}
|
|
1009
1701
|
|
|
1010
1702
|
return {
|
|
@@ -1105,6 +1797,10 @@ export class UserRepository implements IUserRepository {
|
|
|
1105
1797
|
async findAll(): Promise<User[]> {
|
|
1106
1798
|
return Array.from(users.values())
|
|
1107
1799
|
}
|
|
1800
|
+
|
|
1801
|
+
async exists(id: string): Promise<boolean> {
|
|
1802
|
+
return users.has(id)
|
|
1803
|
+
}
|
|
1108
1804
|
}
|
|
1109
1805
|
`;
|
|
1110
1806
|
}
|
|
@@ -1128,7 +1824,7 @@ export class MailService implements IMailService {
|
|
|
1128
1824
|
* App Service Provider
|
|
1129
1825
|
*/
|
|
1130
1826
|
|
|
1131
|
-
import { ServiceProvider, type Container, type PlanetCore } from 'gravito
|
|
1827
|
+
import { ServiceProvider, type Container, type PlanetCore } from '@gravito/core'
|
|
1132
1828
|
|
|
1133
1829
|
export class AppServiceProvider extends ServiceProvider {
|
|
1134
1830
|
register(_container: Container): void {
|
|
@@ -1148,7 +1844,7 @@ export class AppServiceProvider extends ServiceProvider {
|
|
|
1148
1844
|
* Binds repository interfaces to implementations.
|
|
1149
1845
|
*/
|
|
1150
1846
|
|
|
1151
|
-
import { ServiceProvider, type Container } from 'gravito
|
|
1847
|
+
import { ServiceProvider, type Container } from '@gravito/core'
|
|
1152
1848
|
import { UserRepository } from '../Persistence/Repositories/UserRepository'
|
|
1153
1849
|
import { MailService } from '../ExternalServices/MailService'
|
|
1154
1850
|
|
|
@@ -1161,6 +1857,65 @@ export class RepositoryServiceProvider extends ServiceProvider {
|
|
|
1161
1857
|
container.singleton('mailService', () => new MailService())
|
|
1162
1858
|
}
|
|
1163
1859
|
}
|
|
1860
|
+
`;
|
|
1861
|
+
}
|
|
1862
|
+
generateProvidersIndex() {
|
|
1863
|
+
return `/**
|
|
1864
|
+
* Application Service Providers
|
|
1865
|
+
*/
|
|
1866
|
+
|
|
1867
|
+
export { AppServiceProvider } from './AppServiceProvider'
|
|
1868
|
+
export { RepositoryServiceProvider } from './RepositoryServiceProvider'
|
|
1869
|
+
export { MiddlewareProvider } from './MiddlewareProvider'
|
|
1870
|
+
export { RouteProvider } from './RouteProvider'
|
|
1871
|
+
`;
|
|
1872
|
+
}
|
|
1873
|
+
generateMiddlewareProvider() {
|
|
1874
|
+
return `/**
|
|
1875
|
+
* Middleware Service Provider
|
|
1876
|
+
*/
|
|
1877
|
+
|
|
1878
|
+
import {
|
|
1879
|
+
ServiceProvider,
|
|
1880
|
+
type Container,
|
|
1881
|
+
type PlanetCore,
|
|
1882
|
+
bodySizeLimit,
|
|
1883
|
+
securityHeaders,
|
|
1884
|
+
} from '@gravito/core'
|
|
1885
|
+
|
|
1886
|
+
export class MiddlewareProvider extends ServiceProvider {
|
|
1887
|
+
register(_container: Container): void {}
|
|
1888
|
+
|
|
1889
|
+
boot(core: PlanetCore): void {
|
|
1890
|
+
const isDev = process.env.NODE_ENV !== 'production'
|
|
1891
|
+
|
|
1892
|
+
core.adapter.use('*', securityHeaders({
|
|
1893
|
+
contentSecurityPolicy: isDev ? false : undefined,
|
|
1894
|
+
}))
|
|
1895
|
+
|
|
1896
|
+
core.adapter.use('*', bodySizeLimit(10 * 1024 * 1024))
|
|
1897
|
+
|
|
1898
|
+
core.logger.info('\u{1F6E1}\uFE0F Middleware registered')
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
`;
|
|
1902
|
+
}
|
|
1903
|
+
generateRouteProvider() {
|
|
1904
|
+
return `/**
|
|
1905
|
+
* Route Service Provider
|
|
1906
|
+
*/
|
|
1907
|
+
|
|
1908
|
+
import { ServiceProvider, type Container, type PlanetCore } from '@gravito/core'
|
|
1909
|
+
import { registerApiRoutes } from '../../Interface/Http/Routes/api'
|
|
1910
|
+
|
|
1911
|
+
export class RouteProvider extends ServiceProvider {
|
|
1912
|
+
register(_container: Container): void {}
|
|
1913
|
+
|
|
1914
|
+
boot(core: PlanetCore): void {
|
|
1915
|
+
registerApiRoutes(core.router)
|
|
1916
|
+
core.logger.info('\u{1F6E4}\uFE0F Routes registered')
|
|
1917
|
+
}
|
|
1918
|
+
}
|
|
1164
1919
|
`;
|
|
1165
1920
|
}
|
|
1166
1921
|
// ─────────────────────────────────────────────────────────────
|
|
@@ -1171,7 +1926,7 @@ export class RepositoryServiceProvider extends ServiceProvider {
|
|
|
1171
1926
|
* User Controller
|
|
1172
1927
|
*/
|
|
1173
1928
|
|
|
1174
|
-
import type { GravitoContext } from 'gravito
|
|
1929
|
+
import type { GravitoContext } from '@gravito/core'
|
|
1175
1930
|
import { CreateUserUseCase } from '../../../Application/UseCases/User/CreateUser'
|
|
1176
1931
|
import { GetUserUseCase } from '../../../Application/UseCases/User/GetUser'
|
|
1177
1932
|
import { UserRepository } from '../../../Infrastructure/Persistence/Repositories/UserRepository'
|
|
@@ -1245,28 +2000,55 @@ export class UserPresenter {
|
|
|
1245
2000
|
}
|
|
1246
2001
|
`;
|
|
1247
2002
|
}
|
|
1248
|
-
generateBootstrap(
|
|
2003
|
+
generateBootstrap(_context) {
|
|
1249
2004
|
return `/**
|
|
1250
2005
|
* Application Bootstrap
|
|
2006
|
+
*
|
|
2007
|
+
* The entry point for your Clean Architecture application.
|
|
2008
|
+
* Uses the ServiceProvider pattern for modular initialization.
|
|
2009
|
+
*
|
|
2010
|
+
* Lifecycle:
|
|
2011
|
+
* 1. Configure: Load app config and orbits
|
|
2012
|
+
* 2. Boot: Initialize PlanetCore
|
|
2013
|
+
* 3. Register Providers: Bind services to container
|
|
2014
|
+
* 4. Bootstrap: Boot all providers
|
|
1251
2015
|
*/
|
|
1252
2016
|
|
|
1253
|
-
import { PlanetCore } from 'gravito
|
|
1254
|
-
import {
|
|
1255
|
-
import
|
|
1256
|
-
import {
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
2017
|
+
import { defineConfig, PlanetCore } from '@gravito/core'
|
|
2018
|
+
import { OrbitAtlas } from '@gravito/atlas'
|
|
2019
|
+
import appConfig from '../config/app'
|
|
2020
|
+
import {
|
|
2021
|
+
AppServiceProvider,
|
|
2022
|
+
RepositoryServiceProvider,
|
|
2023
|
+
MiddlewareProvider,
|
|
2024
|
+
RouteProvider,
|
|
2025
|
+
} from './Infrastructure/Providers'
|
|
2026
|
+
|
|
2027
|
+
export async function bootstrap() {
|
|
2028
|
+
// 1. Configure
|
|
2029
|
+
const config = defineConfig({
|
|
2030
|
+
config: appConfig,
|
|
2031
|
+
orbits: [new OrbitAtlas()],
|
|
2032
|
+
})
|
|
2033
|
+
|
|
2034
|
+
// 2. Boot Core
|
|
2035
|
+
const core = await PlanetCore.boot(config)
|
|
2036
|
+
core.registerGlobalErrorHandlers()
|
|
2037
|
+
|
|
2038
|
+
// 3. Register Providers
|
|
2039
|
+
core.register(new RepositoryServiceProvider())
|
|
2040
|
+
core.register(new AppServiceProvider())
|
|
2041
|
+
core.register(new MiddlewareProvider())
|
|
2042
|
+
core.register(new RouteProvider())
|
|
2043
|
+
|
|
2044
|
+
// 4. Bootstrap All Providers
|
|
2045
|
+
await core.bootstrap()
|
|
2046
|
+
|
|
2047
|
+
return core
|
|
2048
|
+
}
|
|
1269
2049
|
|
|
2050
|
+
// Application Entry Point
|
|
2051
|
+
const core = await bootstrap()
|
|
1270
2052
|
export default core.liftoff()
|
|
1271
2053
|
`;
|
|
1272
2054
|
}
|
|
@@ -1278,6 +2060,15 @@ export default core.liftoff()
|
|
|
1278
2060
|
This project follows **Clean Architecture** (by Robert C. Martin).
|
|
1279
2061
|
The key principle is the **Dependency Rule**: dependencies point inward.
|
|
1280
2062
|
|
|
2063
|
+
## Service Providers
|
|
2064
|
+
|
|
2065
|
+
Service providers are the central place to configure your application. They follow the ServiceProvider pattern with \`register()\` and \`boot()\` lifecycle methods.
|
|
2066
|
+
|
|
2067
|
+
### Provider Lifecycle
|
|
2068
|
+
|
|
2069
|
+
1. **register()**: Bind services to the container (sync or async).
|
|
2070
|
+
2. **boot()**: Called after ALL providers have registered. Safe to use other services.
|
|
2071
|
+
|
|
1281
2072
|
## Layer Structure
|
|
1282
2073
|
|
|
1283
2074
|
\`\`\`
|
|
@@ -1338,6 +2129,36 @@ src/
|
|
|
1338
2129
|
Created with \u2764\uFE0F using Gravito Framework
|
|
1339
2130
|
`;
|
|
1340
2131
|
}
|
|
2132
|
+
generatePackageJson(context) {
|
|
2133
|
+
const pkg = {
|
|
2134
|
+
name: context.nameKebabCase,
|
|
2135
|
+
version: "0.1.0",
|
|
2136
|
+
type: "module",
|
|
2137
|
+
scripts: {
|
|
2138
|
+
dev: "bun run --watch src/bootstrap.ts",
|
|
2139
|
+
build: "bun build ./src/bootstrap.ts --outdir ./dist --target bun",
|
|
2140
|
+
start: "bun run dist/bootstrap.js",
|
|
2141
|
+
test: "bun test",
|
|
2142
|
+
typecheck: "tsc --noEmit",
|
|
2143
|
+
check: "bun run typecheck && bun run test",
|
|
2144
|
+
"check:deps": "bun run scripts/check-dependencies.ts",
|
|
2145
|
+
validate: "bun run check && bun run check:deps",
|
|
2146
|
+
precommit: "bun run validate",
|
|
2147
|
+
"docker:build": `docker build -t ${context.nameKebabCase} .`,
|
|
2148
|
+
"docker:run": `docker run -it -p 3000:3000 ${context.nameKebabCase}`
|
|
2149
|
+
},
|
|
2150
|
+
dependencies: {
|
|
2151
|
+
"@gravito/core": "workspace:*",
|
|
2152
|
+
"@gravito/enterprise": "workspace:*",
|
|
2153
|
+
...context.withSpectrum ? { "@gravito/spectrum": "workspace:*" } : {}
|
|
2154
|
+
},
|
|
2155
|
+
devDependencies: {
|
|
2156
|
+
"bun-types": "latest",
|
|
2157
|
+
typescript: "^5.0.0"
|
|
2158
|
+
}
|
|
2159
|
+
};
|
|
2160
|
+
return JSON.stringify(pkg, null, 2);
|
|
2161
|
+
}
|
|
1341
2162
|
};
|
|
1342
2163
|
|
|
1343
2164
|
// src/generators/DddGenerator.ts
|
|
@@ -1465,22 +2286,6 @@ var DddGenerator = class extends BaseGenerator {
|
|
|
1465
2286
|
{ type: "file", name: "Money.ts", content: this.generateMoneyValueObject() },
|
|
1466
2287
|
{ type: "file", name: "Email.ts", content: this.generateEmailValueObject() }
|
|
1467
2288
|
]
|
|
1468
|
-
},
|
|
1469
|
-
{
|
|
1470
|
-
type: "directory",
|
|
1471
|
-
name: "Events",
|
|
1472
|
-
children: [
|
|
1473
|
-
{ type: "file", name: "DomainEvent.ts", content: this.generateDomainEvent() }
|
|
1474
|
-
]
|
|
1475
|
-
},
|
|
1476
|
-
{
|
|
1477
|
-
type: "directory",
|
|
1478
|
-
name: "Primitives",
|
|
1479
|
-
children: [
|
|
1480
|
-
{ type: "file", name: "AggregateRoot.ts", content: this.generateAggregateRoot() },
|
|
1481
|
-
{ type: "file", name: "Entity.ts", content: this.generateEntity() },
|
|
1482
|
-
{ type: "file", name: "ValueObject.ts", content: this.generateValueObject() }
|
|
1483
|
-
]
|
|
1484
2289
|
}
|
|
1485
2290
|
]
|
|
1486
2291
|
},
|
|
@@ -1662,34 +2467,46 @@ var DddGenerator = class extends BaseGenerator {
|
|
|
1662
2467
|
// ─────────────────────────────────────────────────────────────
|
|
1663
2468
|
// Bootstrap File Generators
|
|
1664
2469
|
// ─────────────────────────────────────────────────────────────
|
|
1665
|
-
generateBootstrapApp(
|
|
2470
|
+
generateBootstrapApp(_context) {
|
|
1666
2471
|
return `/**
|
|
1667
2472
|
* Application Bootstrap
|
|
1668
2473
|
*
|
|
1669
|
-
* Central configuration and initialization
|
|
2474
|
+
* Central configuration and initialization using the ServiceProvider pattern.
|
|
2475
|
+
*
|
|
2476
|
+
* Lifecycle:
|
|
2477
|
+
* 1. Configure: Load app config and orbits
|
|
2478
|
+
* 2. Boot: Initialize PlanetCore
|
|
2479
|
+
* 3. Register Providers: Bind services to container
|
|
2480
|
+
* 4. Bootstrap: Boot all providers
|
|
1670
2481
|
*/
|
|
1671
2482
|
|
|
1672
|
-
import { PlanetCore } from 'gravito
|
|
2483
|
+
import { defineConfig, PlanetCore } from '@gravito/core'
|
|
2484
|
+
import { OrbitAtlas } from '@gravito/atlas'
|
|
2485
|
+
import appConfig from '../../config/app'
|
|
1673
2486
|
import { registerProviders } from './providers'
|
|
1674
2487
|
import { registerRoutes } from './routes'
|
|
1675
2488
|
|
|
1676
2489
|
export async function createApp(): Promise<PlanetCore> {
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
2490
|
+
// 1. Configure
|
|
2491
|
+
const config = defineConfig({
|
|
2492
|
+
config: appConfig,
|
|
2493
|
+
orbits: [new OrbitAtlas()],
|
|
2494
|
+
})
|
|
1682
2495
|
|
|
1683
|
-
|
|
1684
|
-
|
|
2496
|
+
// 2. Boot Core
|
|
2497
|
+
const core = await PlanetCore.boot(config)
|
|
2498
|
+
core.registerGlobalErrorHandlers()
|
|
1685
2499
|
|
|
1686
|
-
|
|
1687
|
-
|
|
2500
|
+
// 3. Register Providers
|
|
2501
|
+
await registerProviders(core)
|
|
1688
2502
|
|
|
1689
|
-
|
|
1690
|
-
|
|
2503
|
+
// 4. Bootstrap All Providers
|
|
2504
|
+
await core.bootstrap()
|
|
2505
|
+
|
|
2506
|
+
// Register routes after bootstrap
|
|
2507
|
+
registerRoutes(core.router)
|
|
1691
2508
|
|
|
1692
|
-
|
|
2509
|
+
return core
|
|
1693
2510
|
}
|
|
1694
2511
|
`;
|
|
1695
2512
|
}
|
|
@@ -1697,19 +2514,48 @@ export async function createApp(): Promise<PlanetCore> {
|
|
|
1697
2514
|
return `/**
|
|
1698
2515
|
* Service Providers Registry
|
|
1699
2516
|
*
|
|
1700
|
-
* Register all
|
|
2517
|
+
* Register all service providers here.
|
|
2518
|
+
* Include both global and module-specific providers.
|
|
1701
2519
|
*/
|
|
1702
2520
|
|
|
1703
|
-
import
|
|
2521
|
+
import {
|
|
2522
|
+
ServiceProvider,
|
|
2523
|
+
type Container,
|
|
2524
|
+
type PlanetCore,
|
|
2525
|
+
bodySizeLimit,
|
|
2526
|
+
securityHeaders,
|
|
2527
|
+
} from '@gravito/core'
|
|
1704
2528
|
import { OrderingServiceProvider } from '../Modules/Ordering/Infrastructure/Providers/OrderingServiceProvider'
|
|
1705
2529
|
import { CatalogServiceProvider } from '../Modules/Catalog/Infrastructure/Providers/CatalogServiceProvider'
|
|
1706
2530
|
|
|
2531
|
+
/**
|
|
2532
|
+
* Middleware Provider - Global middleware registration
|
|
2533
|
+
*/
|
|
2534
|
+
export class MiddlewareProvider extends ServiceProvider {
|
|
2535
|
+
register(_container: Container): void {}
|
|
2536
|
+
|
|
2537
|
+
boot(core: PlanetCore): void {
|
|
2538
|
+
const isDev = process.env.NODE_ENV !== 'production'
|
|
2539
|
+
|
|
2540
|
+
core.adapter.use('*', securityHeaders({
|
|
2541
|
+
contentSecurityPolicy: isDev ? false : undefined,
|
|
2542
|
+
}))
|
|
2543
|
+
|
|
2544
|
+
core.adapter.use('*', bodySizeLimit(10 * 1024 * 1024))
|
|
2545
|
+
|
|
2546
|
+
core.logger.info('\u{1F6E1}\uFE0F Global middleware registered')
|
|
2547
|
+
}
|
|
2548
|
+
}
|
|
2549
|
+
|
|
1707
2550
|
export async function registerProviders(core: PlanetCore): Promise<void> {
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
2551
|
+
// Global Providers
|
|
2552
|
+
core.register(new MiddlewareProvider())
|
|
2553
|
+
|
|
2554
|
+
// Module Providers
|
|
2555
|
+
core.register(new OrderingServiceProvider())
|
|
2556
|
+
core.register(new CatalogServiceProvider())
|
|
1711
2557
|
|
|
1712
|
-
|
|
2558
|
+
// Add more providers as needed
|
|
1713
2559
|
}
|
|
1714
2560
|
`;
|
|
1715
2561
|
}
|
|
@@ -1795,7 +2641,7 @@ export default {
|
|
|
1795
2641
|
* ${name} Service Provider
|
|
1796
2642
|
*/
|
|
1797
2643
|
|
|
1798
|
-
import { ServiceProvider, type Container, type PlanetCore } from 'gravito
|
|
2644
|
+
import { ServiceProvider, type Container, type PlanetCore } from '@gravito/core'
|
|
1799
2645
|
import { ${name}Repository } from '../Persistence/${name}Repository'
|
|
1800
2646
|
|
|
1801
2647
|
export class ${name}ServiceProvider extends ServiceProvider {
|
|
@@ -1822,13 +2668,20 @@ export class ${name}ServiceProvider extends ServiceProvider {
|
|
|
1822
2668
|
build: "bun build ./src/main.ts --outdir ./dist --target bun",
|
|
1823
2669
|
start: "bun run dist/main.js",
|
|
1824
2670
|
test: "bun test",
|
|
1825
|
-
typecheck: "tsc --noEmit"
|
|
2671
|
+
typecheck: "tsc --noEmit",
|
|
2672
|
+
check: "bun run typecheck && bun run test",
|
|
2673
|
+
"check:deps": "bun run scripts/check-dependencies.ts",
|
|
2674
|
+
validate: "bun run check && bun run check:deps",
|
|
2675
|
+
precommit: "bun run validate",
|
|
2676
|
+
"docker:build": `docker build -t ${context.nameKebabCase} .`,
|
|
2677
|
+
"docker:run": `docker run -it -p 3000:3000 ${context.nameKebabCase}`
|
|
1826
2678
|
},
|
|
1827
2679
|
dependencies: {
|
|
1828
|
-
"gravito
|
|
2680
|
+
"@gravito/core": "^1.0.0-beta.5",
|
|
2681
|
+
"@gravito/enterprise": "workspace:*"
|
|
1829
2682
|
},
|
|
1830
2683
|
devDependencies: {
|
|
1831
|
-
"
|
|
2684
|
+
"bun-types": "latest",
|
|
1832
2685
|
typescript: "^5.0.0"
|
|
1833
2686
|
}
|
|
1834
2687
|
};
|
|
@@ -1879,11 +2732,15 @@ export class ${name}ServiceProvider extends ServiceProvider {
|
|
|
1879
2732
|
* Shared identifier across all contexts.
|
|
1880
2733
|
*/
|
|
1881
2734
|
|
|
1882
|
-
|
|
1883
|
-
|
|
2735
|
+
import { ValueObject } from '@gravito/enterprise'
|
|
2736
|
+
|
|
2737
|
+
interface IdProps {
|
|
2738
|
+
value: string
|
|
2739
|
+
}
|
|
1884
2740
|
|
|
2741
|
+
export class Id extends ValueObject<IdProps> {
|
|
1885
2742
|
private constructor(value: string) {
|
|
1886
|
-
|
|
2743
|
+
super({ value })
|
|
1887
2744
|
}
|
|
1888
2745
|
|
|
1889
2746
|
static create(): Id {
|
|
@@ -1896,15 +2753,11 @@ export class Id {
|
|
|
1896
2753
|
}
|
|
1897
2754
|
|
|
1898
2755
|
get value(): string {
|
|
1899
|
-
return this.
|
|
1900
|
-
}
|
|
1901
|
-
|
|
1902
|
-
equals(other: Id): boolean {
|
|
1903
|
-
return this._value === other._value
|
|
2756
|
+
return this.props.value
|
|
1904
2757
|
}
|
|
1905
2758
|
|
|
1906
2759
|
toString(): string {
|
|
1907
|
-
return this.
|
|
2760
|
+
return this.props.value
|
|
1908
2761
|
}
|
|
1909
2762
|
}
|
|
1910
2763
|
`;
|
|
@@ -1914,12 +2767,25 @@ export class Id {
|
|
|
1914
2767
|
* Money Value Object
|
|
1915
2768
|
*/
|
|
1916
2769
|
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
2770
|
+
import { ValueObject } from '@gravito/enterprise'
|
|
2771
|
+
|
|
2772
|
+
interface MoneyProps {
|
|
2773
|
+
amount: number
|
|
2774
|
+
currency: string
|
|
2775
|
+
}
|
|
2776
|
+
|
|
2777
|
+
export class Money extends ValueObject<MoneyProps> {
|
|
2778
|
+
constructor(amount: number, currency: string = 'USD') {
|
|
1922
2779
|
if (amount < 0) throw new Error('Amount cannot be negative')
|
|
2780
|
+
super({ amount, currency })
|
|
2781
|
+
}
|
|
2782
|
+
|
|
2783
|
+
get amount(): number {
|
|
2784
|
+
return this.props.amount
|
|
2785
|
+
}
|
|
2786
|
+
|
|
2787
|
+
get currency(): string {
|
|
2788
|
+
return this.props.currency
|
|
1923
2789
|
}
|
|
1924
2790
|
|
|
1925
2791
|
add(other: Money): Money {
|
|
@@ -1937,10 +2803,6 @@ export class Money {
|
|
|
1937
2803
|
throw new Error('Cannot operate on different currencies')
|
|
1938
2804
|
}
|
|
1939
2805
|
}
|
|
1940
|
-
|
|
1941
|
-
equals(other: Money): boolean {
|
|
1942
|
-
return this.amount === other.amount && this.currency === other.currency
|
|
1943
|
-
}
|
|
1944
2806
|
}
|
|
1945
2807
|
`;
|
|
1946
2808
|
}
|
|
@@ -1949,11 +2811,15 @@ export class Money {
|
|
|
1949
2811
|
* Email Value Object
|
|
1950
2812
|
*/
|
|
1951
2813
|
|
|
1952
|
-
|
|
1953
|
-
|
|
2814
|
+
import { ValueObject } from '@gravito/enterprise'
|
|
2815
|
+
|
|
2816
|
+
interface EmailProps {
|
|
2817
|
+
value: string
|
|
2818
|
+
}
|
|
1954
2819
|
|
|
2820
|
+
export class Email extends ValueObject<EmailProps> {
|
|
1955
2821
|
private constructor(value: string) {
|
|
1956
|
-
|
|
2822
|
+
super({ value: value.toLowerCase().trim() })
|
|
1957
2823
|
}
|
|
1958
2824
|
|
|
1959
2825
|
static create(email: string): Email {
|
|
@@ -1968,97 +2834,7 @@ export class Email {
|
|
|
1968
2834
|
}
|
|
1969
2835
|
|
|
1970
2836
|
get value(): string {
|
|
1971
|
-
return this.
|
|
1972
|
-
}
|
|
1973
|
-
}
|
|
1974
|
-
`;
|
|
1975
|
-
}
|
|
1976
|
-
generateDomainEvent() {
|
|
1977
|
-
return `/**
|
|
1978
|
-
* Domain Event Base
|
|
1979
|
-
*/
|
|
1980
|
-
|
|
1981
|
-
export abstract class DomainEvent {
|
|
1982
|
-
readonly occurredOn: Date
|
|
1983
|
-
readonly eventId: string
|
|
1984
|
-
|
|
1985
|
-
constructor() {
|
|
1986
|
-
this.occurredOn = new Date()
|
|
1987
|
-
this.eventId = crypto.randomUUID()
|
|
1988
|
-
}
|
|
1989
|
-
|
|
1990
|
-
abstract get eventName(): string
|
|
1991
|
-
abstract get aggregateId(): string
|
|
1992
|
-
}
|
|
1993
|
-
`;
|
|
1994
|
-
}
|
|
1995
|
-
generateAggregateRoot() {
|
|
1996
|
-
return `/**
|
|
1997
|
-
* Aggregate Root Base
|
|
1998
|
-
*/
|
|
1999
|
-
|
|
2000
|
-
import type { DomainEvent } from '../Events/DomainEvent'
|
|
2001
|
-
import type { Id } from '../ValueObjects/Id'
|
|
2002
|
-
|
|
2003
|
-
export abstract class AggregateRoot<T extends Id = Id> {
|
|
2004
|
-
private _domainEvents: DomainEvent[] = []
|
|
2005
|
-
|
|
2006
|
-
protected constructor(protected readonly _id: T) {}
|
|
2007
|
-
|
|
2008
|
-
get id(): T {
|
|
2009
|
-
return this._id
|
|
2010
|
-
}
|
|
2011
|
-
|
|
2012
|
-
get domainEvents(): DomainEvent[] {
|
|
2013
|
-
return [...this._domainEvents]
|
|
2014
|
-
}
|
|
2015
|
-
|
|
2016
|
-
protected addDomainEvent(event: DomainEvent): void {
|
|
2017
|
-
this._domainEvents.push(event)
|
|
2018
|
-
}
|
|
2019
|
-
|
|
2020
|
-
clearDomainEvents(): DomainEvent[] {
|
|
2021
|
-
const events = [...this._domainEvents]
|
|
2022
|
-
this._domainEvents = []
|
|
2023
|
-
return events
|
|
2024
|
-
}
|
|
2025
|
-
}
|
|
2026
|
-
`;
|
|
2027
|
-
}
|
|
2028
|
-
generateEntity() {
|
|
2029
|
-
return `/**
|
|
2030
|
-
* Entity Base
|
|
2031
|
-
*/
|
|
2032
|
-
|
|
2033
|
-
import type { Id } from '../ValueObjects/Id'
|
|
2034
|
-
|
|
2035
|
-
export abstract class Entity<T extends Id = Id> {
|
|
2036
|
-
protected constructor(protected readonly _id: T) {}
|
|
2037
|
-
|
|
2038
|
-
get id(): T {
|
|
2039
|
-
return this._id
|
|
2040
|
-
}
|
|
2041
|
-
|
|
2042
|
-
equals(other: Entity<T>): boolean {
|
|
2043
|
-
return this._id.equals(other._id)
|
|
2044
|
-
}
|
|
2045
|
-
}
|
|
2046
|
-
`;
|
|
2047
|
-
}
|
|
2048
|
-
generateValueObject() {
|
|
2049
|
-
return `/**
|
|
2050
|
-
* Value Object Base
|
|
2051
|
-
*/
|
|
2052
|
-
|
|
2053
|
-
export abstract class ValueObject<T> {
|
|
2054
|
-
protected readonly props: T
|
|
2055
|
-
|
|
2056
|
-
constructor(props: T) {
|
|
2057
|
-
this.props = Object.freeze(props)
|
|
2058
|
-
}
|
|
2059
|
-
|
|
2060
|
-
equals(other: ValueObject<T>): boolean {
|
|
2061
|
-
return JSON.stringify(this.props) === JSON.stringify(other.props)
|
|
2837
|
+
return this.props.value
|
|
2062
2838
|
}
|
|
2063
2839
|
}
|
|
2064
2840
|
`;
|
|
@@ -2068,7 +2844,7 @@ export abstract class ValueObject<T> {
|
|
|
2068
2844
|
* Event Dispatcher
|
|
2069
2845
|
*/
|
|
2070
2846
|
|
|
2071
|
-
import type { DomainEvent } from '
|
|
2847
|
+
import type { DomainEvent } from '@gravito/enterprise'
|
|
2072
2848
|
|
|
2073
2849
|
type EventHandler = (event: DomainEvent) => void | Promise<void>
|
|
2074
2850
|
|
|
@@ -2104,7 +2880,7 @@ export class EventDispatcher {
|
|
|
2104
2880
|
* ${name} Aggregate Root
|
|
2105
2881
|
*/
|
|
2106
2882
|
|
|
2107
|
-
import { AggregateRoot } from '
|
|
2883
|
+
import { AggregateRoot } from '@gravito/enterprise'
|
|
2108
2884
|
import { Id } from '../../../../../Shared/Domain/ValueObjects/Id'
|
|
2109
2885
|
import { ${name}Created } from '../../Events/${name}Created'
|
|
2110
2886
|
import { ${name}Status } from './${name}Status'
|
|
@@ -2115,7 +2891,7 @@ export interface ${name}Props {
|
|
|
2115
2891
|
createdAt: Date
|
|
2116
2892
|
}
|
|
2117
2893
|
|
|
2118
|
-
export class ${name} extends AggregateRoot {
|
|
2894
|
+
export class ${name} extends AggregateRoot<Id> {
|
|
2119
2895
|
private props: ${name}Props
|
|
2120
2896
|
|
|
2121
2897
|
private constructor(id: Id, props: ${name}Props) {
|
|
@@ -2160,14 +2936,14 @@ export enum ${name}Status {
|
|
|
2160
2936
|
* ${name} Created Event
|
|
2161
2937
|
*/
|
|
2162
2938
|
|
|
2163
|
-
import { DomainEvent } from '
|
|
2939
|
+
import { DomainEvent } from '@gravito/enterprise'
|
|
2164
2940
|
|
|
2165
2941
|
export class ${name}Created extends DomainEvent {
|
|
2166
2942
|
constructor(public readonly ${name.toLowerCase()}Id: string) {
|
|
2167
2943
|
super()
|
|
2168
2944
|
}
|
|
2169
2945
|
|
|
2170
|
-
get eventName(): string {
|
|
2946
|
+
override get eventName(): string {
|
|
2171
2947
|
return '${name.toLowerCase()}.created'
|
|
2172
2948
|
}
|
|
2173
2949
|
|
|
@@ -2182,12 +2958,12 @@ export class ${name}Created extends DomainEvent {
|
|
|
2182
2958
|
* ${name} Repository Interface
|
|
2183
2959
|
*/
|
|
2184
2960
|
|
|
2961
|
+
import { Repository } from '@gravito/enterprise'
|
|
2185
2962
|
import type { ${name} } from '../Aggregates/${name}/${name}'
|
|
2963
|
+
import { Id } from '../../../../../Shared/Domain/ValueObjects/Id'
|
|
2186
2964
|
|
|
2187
|
-
export interface I${name}Repository {
|
|
2188
|
-
|
|
2189
|
-
save(aggregate: ${name}): Promise<void>
|
|
2190
|
-
delete(id: string): Promise<void>
|
|
2965
|
+
export interface I${name}Repository extends Repository<${name}, Id> {
|
|
2966
|
+
// Add specific methods for this repository if needed
|
|
2191
2967
|
}
|
|
2192
2968
|
`;
|
|
2193
2969
|
}
|
|
@@ -2196,11 +2972,15 @@ export interface I${name}Repository {
|
|
|
2196
2972
|
* Create ${name} Command
|
|
2197
2973
|
*/
|
|
2198
2974
|
|
|
2199
|
-
|
|
2975
|
+
import { Command } from '@gravito/enterprise'
|
|
2976
|
+
|
|
2977
|
+
export class Create${name}Command extends Command {
|
|
2200
2978
|
constructor(
|
|
2201
2979
|
// Add command properties
|
|
2202
2980
|
public readonly id?: string
|
|
2203
|
-
) {
|
|
2981
|
+
) {
|
|
2982
|
+
super()
|
|
2983
|
+
}
|
|
2204
2984
|
}
|
|
2205
2985
|
`;
|
|
2206
2986
|
}
|
|
@@ -2209,12 +2989,13 @@ export class Create${name}Command {
|
|
|
2209
2989
|
* Create ${name} Handler
|
|
2210
2990
|
*/
|
|
2211
2991
|
|
|
2992
|
+
import { CommandHandler } from '@gravito/enterprise'
|
|
2212
2993
|
import type { I${name}Repository } from '../../../Domain/Repositories/I${name}Repository'
|
|
2213
2994
|
import { ${name} } from '../../../Domain/Aggregates/${name}/${name}'
|
|
2214
2995
|
import { Id } from '../../../../../Shared/Domain/ValueObjects/Id'
|
|
2215
2996
|
import type { Create${name}Command } from './Create${name}Command'
|
|
2216
2997
|
|
|
2217
|
-
export class Create${name}Handler {
|
|
2998
|
+
export class Create${name}Handler implements CommandHandler<Create${name}Command, string> {
|
|
2218
2999
|
constructor(private repository: I${name}Repository) {}
|
|
2219
3000
|
|
|
2220
3001
|
async handle(command: Create${name}Command): Promise<string> {
|
|
@@ -2233,8 +3014,12 @@ export class Create${name}Handler {
|
|
|
2233
3014
|
* Get ${name} By Id Query
|
|
2234
3015
|
*/
|
|
2235
3016
|
|
|
2236
|
-
|
|
2237
|
-
|
|
3017
|
+
import { Query } from '@gravito/enterprise'
|
|
3018
|
+
|
|
3019
|
+
export class Get${name}ByIdQuery extends Query {
|
|
3020
|
+
constructor(public readonly id: string) {
|
|
3021
|
+
super()
|
|
3022
|
+
}
|
|
2238
3023
|
}
|
|
2239
3024
|
`;
|
|
2240
3025
|
}
|
|
@@ -2243,15 +3028,16 @@ export class Get${name}ByIdQuery {
|
|
|
2243
3028
|
* Get ${name} By Id Handler
|
|
2244
3029
|
*/
|
|
2245
3030
|
|
|
3031
|
+
import { QueryHandler } from '@gravito/enterprise'
|
|
2246
3032
|
import type { I${name}Repository } from '../../../Domain/Repositories/I${name}Repository'
|
|
2247
3033
|
import type { ${name}DTO } from '../../DTOs/${name}DTO'
|
|
2248
3034
|
import type { Get${name}ByIdQuery } from './Get${name}ByIdQuery'
|
|
2249
3035
|
|
|
2250
|
-
export class Get${name}ByIdHandler {
|
|
3036
|
+
export class Get${name}ByIdHandler implements QueryHandler<Get${name}ByIdQuery, ${name}DTO | null> {
|
|
2251
3037
|
constructor(private repository: I${name}Repository) {}
|
|
2252
3038
|
|
|
2253
3039
|
async handle(query: Get${name}ByIdQuery): Promise<${name}DTO | null> {
|
|
2254
|
-
const aggregate = await this.repository.findById(query.id)
|
|
3040
|
+
const aggregate = await this.repository.findById(query.id as any) // Simplified for demo
|
|
2255
3041
|
if (!aggregate) return null
|
|
2256
3042
|
|
|
2257
3043
|
return {
|
|
@@ -2283,20 +3069,29 @@ export interface ${name}DTO {
|
|
|
2283
3069
|
|
|
2284
3070
|
import type { ${name} } from '../../Domain/Aggregates/${name}/${name}'
|
|
2285
3071
|
import type { I${name}Repository } from '../../Domain/Repositories/I${name}Repository'
|
|
3072
|
+
import type { Id } from '../../../../../Shared/Domain/ValueObjects/Id'
|
|
2286
3073
|
|
|
2287
3074
|
const store = new Map<string, ${name}>()
|
|
2288
3075
|
|
|
2289
3076
|
export class ${name}Repository implements I${name}Repository {
|
|
2290
|
-
async findById(id:
|
|
2291
|
-
return store.get(id) ?? null
|
|
3077
|
+
async findById(id: Id): Promise<${name} | null> {
|
|
3078
|
+
return store.get(id.value) ?? null
|
|
2292
3079
|
}
|
|
2293
3080
|
|
|
2294
3081
|
async save(aggregate: ${name}): Promise<void> {
|
|
2295
3082
|
store.set(aggregate.id.value, aggregate)
|
|
2296
3083
|
}
|
|
2297
3084
|
|
|
2298
|
-
async delete(id:
|
|
2299
|
-
store.delete(id)
|
|
3085
|
+
async delete(id: Id): Promise<void> {
|
|
3086
|
+
store.delete(id.value)
|
|
3087
|
+
}
|
|
3088
|
+
|
|
3089
|
+
async findAll(): Promise<${name}[]> {
|
|
3090
|
+
return Array.from(store.values())
|
|
3091
|
+
}
|
|
3092
|
+
|
|
3093
|
+
async exists(id: Id): Promise<boolean> {
|
|
3094
|
+
return store.has(id.value)
|
|
2300
3095
|
}
|
|
2301
3096
|
}
|
|
2302
3097
|
`;
|
|
@@ -2318,6 +3113,16 @@ export function report(error: unknown): void {
|
|
|
2318
3113
|
|
|
2319
3114
|
This project follows **Domain-Driven Design (DDD)** with strategic and tactical patterns.
|
|
2320
3115
|
|
|
3116
|
+
## Service Providers
|
|
3117
|
+
|
|
3118
|
+
Service providers are the central place to configure your application and modules. They follow the ServiceProvider pattern with \`register()\` and \`boot()\` lifecycle methods.
|
|
3119
|
+
|
|
3120
|
+
### Internal Bootstrapping
|
|
3121
|
+
|
|
3122
|
+
1. **Bootstrap/app.ts**: Orchestrates the 4-step lifecycle (Configure, Boot, Register, Bootstrap).
|
|
3123
|
+
2. **Bootstrap/providers.ts**: Central registry for all global and module-specific providers.
|
|
3124
|
+
3. **Infrastructure/Providers/[Module]ServiceProvider.ts**: Module-specific service registration.
|
|
3125
|
+
|
|
2321
3126
|
## Bounded Contexts
|
|
2322
3127
|
|
|
2323
3128
|
\`\`\`
|
|
@@ -2455,6 +3260,11 @@ var EnterpriseMvcGenerator = class extends BaseGenerator {
|
|
|
2455
3260
|
type: "directory",
|
|
2456
3261
|
name: "Providers",
|
|
2457
3262
|
children: [
|
|
3263
|
+
{
|
|
3264
|
+
type: "file",
|
|
3265
|
+
name: "index.ts",
|
|
3266
|
+
content: this.generateProvidersIndex()
|
|
3267
|
+
},
|
|
2458
3268
|
{
|
|
2459
3269
|
type: "file",
|
|
2460
3270
|
name: "AppServiceProvider.ts",
|
|
@@ -2462,8 +3272,18 @@ var EnterpriseMvcGenerator = class extends BaseGenerator {
|
|
|
2462
3272
|
},
|
|
2463
3273
|
{
|
|
2464
3274
|
type: "file",
|
|
2465
|
-
name: "
|
|
2466
|
-
content: this.
|
|
3275
|
+
name: "DatabaseProvider.ts",
|
|
3276
|
+
content: this.generateDatabaseProvider()
|
|
3277
|
+
},
|
|
3278
|
+
{
|
|
3279
|
+
type: "file",
|
|
3280
|
+
name: "MiddlewareProvider.ts",
|
|
3281
|
+
content: this.generateMiddlewareProvider()
|
|
3282
|
+
},
|
|
3283
|
+
{
|
|
3284
|
+
type: "file",
|
|
3285
|
+
name: "RouteProvider.ts",
|
|
3286
|
+
content: this.generateRouteProvider()
|
|
2467
3287
|
}
|
|
2468
3288
|
]
|
|
2469
3289
|
},
|
|
@@ -2739,7 +3559,7 @@ export default {
|
|
|
2739
3559
|
* can be assigned to specific routes.
|
|
2740
3560
|
*/
|
|
2741
3561
|
|
|
2742
|
-
import type { GravitoMiddleware } from 'gravito
|
|
3562
|
+
import type { GravitoMiddleware } from '@gravito/core'
|
|
2743
3563
|
|
|
2744
3564
|
/**
|
|
2745
3565
|
* Global middleware stack.
|
|
@@ -2828,7 +3648,7 @@ export abstract class Controller {
|
|
|
2828
3648
|
* Home Controller
|
|
2829
3649
|
*/
|
|
2830
3650
|
|
|
2831
|
-
import type { GravitoContext } from 'gravito
|
|
3651
|
+
import type { GravitoContext } from '@gravito/core'
|
|
2832
3652
|
import { Controller } from './Controller'
|
|
2833
3653
|
|
|
2834
3654
|
export class HomeController extends Controller {
|
|
@@ -2862,7 +3682,7 @@ export class HomeController extends Controller {
|
|
|
2862
3682
|
* Protects routes that require authentication.
|
|
2863
3683
|
*/
|
|
2864
3684
|
|
|
2865
|
-
import type { GravitoContext, GravitoNext } from 'gravito
|
|
3685
|
+
import type { GravitoContext, GravitoNext } from '@gravito/core'
|
|
2866
3686
|
|
|
2867
3687
|
export async function Authenticate(c: GravitoContext, next: GravitoNext) {
|
|
2868
3688
|
// TODO: Implement authentication check
|
|
@@ -2872,6 +3692,7 @@ export async function Authenticate(c: GravitoContext, next: GravitoNext) {
|
|
|
2872
3692
|
// }
|
|
2873
3693
|
|
|
2874
3694
|
await next()
|
|
3695
|
+
return undefined
|
|
2875
3696
|
}
|
|
2876
3697
|
`;
|
|
2877
3698
|
}
|
|
@@ -2883,7 +3704,7 @@ export async function Authenticate(c: GravitoContext, next: GravitoNext) {
|
|
|
2883
3704
|
* Register and bootstrap application services here.
|
|
2884
3705
|
*/
|
|
2885
3706
|
|
|
2886
|
-
import { ServiceProvider, type Container, type PlanetCore } from 'gravito
|
|
3707
|
+
import { ServiceProvider, type Container, type PlanetCore } from '@gravito/core'
|
|
2887
3708
|
|
|
2888
3709
|
export class AppServiceProvider extends ServiceProvider {
|
|
2889
3710
|
/**
|
|
@@ -2911,7 +3732,7 @@ export class AppServiceProvider extends ServiceProvider {
|
|
|
2911
3732
|
* Configures and registers application routes.
|
|
2912
3733
|
*/
|
|
2913
3734
|
|
|
2914
|
-
import { ServiceProvider, type Container, type PlanetCore } from 'gravito
|
|
3735
|
+
import { ServiceProvider, type Container, type PlanetCore } from '@gravito/core'
|
|
2915
3736
|
import { registerRoutes } from '../routes'
|
|
2916
3737
|
|
|
2917
3738
|
export class RouteServiceProvider extends ServiceProvider {
|
|
@@ -2931,80 +3752,250 @@ export class RouteServiceProvider extends ServiceProvider {
|
|
|
2931
3752
|
}
|
|
2932
3753
|
`;
|
|
2933
3754
|
}
|
|
2934
|
-
|
|
3755
|
+
// ─────────────────────────────────────────────────────────────
|
|
3756
|
+
// Modern Provider Generators (ServiceProvider Pattern)
|
|
3757
|
+
// ─────────────────────────────────────────────────────────────
|
|
3758
|
+
generateProvidersIndex() {
|
|
2935
3759
|
return `/**
|
|
2936
|
-
*
|
|
3760
|
+
* Application Service Providers
|
|
2937
3761
|
*
|
|
2938
|
-
*
|
|
2939
|
-
*
|
|
3762
|
+
* Export all providers for easy importing in bootstrap.
|
|
3763
|
+
* Providers are registered in the order they are listed.
|
|
2940
3764
|
*/
|
|
2941
3765
|
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
export function report(error: unknown, context: ErrorHandlerContext): void {
|
|
2948
|
-
// Log to external service (Sentry, etc.)
|
|
2949
|
-
if (!context.isProduction) {
|
|
2950
|
-
console.error('[Exception Handler]', error)
|
|
3766
|
+
export { AppServiceProvider } from './AppServiceProvider'
|
|
3767
|
+
export { DatabaseProvider } from './DatabaseProvider'
|
|
3768
|
+
export { MiddlewareProvider } from './MiddlewareProvider'
|
|
3769
|
+
export { RouteProvider } from './RouteProvider'
|
|
3770
|
+
`;
|
|
2951
3771
|
}
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
*
|
|
3772
|
+
generateDatabaseProvider() {
|
|
3773
|
+
return `/**
|
|
3774
|
+
* Database Service Provider
|
|
3775
|
+
*
|
|
3776
|
+
* Handles database initialization and migrations.
|
|
3777
|
+
*
|
|
3778
|
+
* Lifecycle:
|
|
3779
|
+
* - register(): Bind database config to container
|
|
3780
|
+
* - boot(): Run migrations and seeders
|
|
2956
3781
|
*/
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
3782
|
+
|
|
3783
|
+
import { ServiceProvider, type Container, type PlanetCore } from '@gravito/core'
|
|
3784
|
+
import databaseConfig from '../../config/database'
|
|
3785
|
+
|
|
3786
|
+
export class DatabaseProvider extends ServiceProvider {
|
|
3787
|
+
/**
|
|
3788
|
+
* Register database configuration.
|
|
3789
|
+
*/
|
|
3790
|
+
register(_container: Container): void {
|
|
3791
|
+
this.mergeConfig(this.core!.config, 'database', databaseConfig)
|
|
2964
3792
|
}
|
|
2965
3793
|
|
|
2966
|
-
|
|
3794
|
+
/**
|
|
3795
|
+
* Initialize database connections.
|
|
3796
|
+
*/
|
|
3797
|
+
async boot(core: PlanetCore): Promise<void> {
|
|
3798
|
+
// Database initialization will be handled by Atlas orbit
|
|
3799
|
+
core.logger.info('\u{1F4E6} Database provider booted')
|
|
3800
|
+
}
|
|
2967
3801
|
}
|
|
2968
|
-
|
|
2969
|
-
/**
|
|
2970
|
-
* A list of exception types that should not be reported.
|
|
2971
|
-
*/
|
|
2972
|
-
export const dontReport: string[] = [
|
|
2973
|
-
'ValidationException',
|
|
2974
|
-
'NotFoundException',
|
|
2975
|
-
]
|
|
2976
3802
|
`;
|
|
2977
3803
|
}
|
|
2978
|
-
|
|
3804
|
+
generateMiddlewareProvider() {
|
|
2979
3805
|
return `/**
|
|
2980
|
-
*
|
|
3806
|
+
* Middleware Service Provider
|
|
2981
3807
|
*
|
|
2982
|
-
*
|
|
2983
|
-
*
|
|
3808
|
+
* Registers global middleware stack.
|
|
3809
|
+
* Provides a centralized location for middleware configuration.
|
|
3810
|
+
*
|
|
3811
|
+
* Lifecycle:
|
|
3812
|
+
* - register(): N/A (no container bindings)
|
|
3813
|
+
* - boot(): Register global middleware
|
|
2984
3814
|
*/
|
|
2985
3815
|
|
|
2986
|
-
import {
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
3816
|
+
import {
|
|
3817
|
+
ServiceProvider,
|
|
3818
|
+
type Container,
|
|
3819
|
+
type PlanetCore,
|
|
3820
|
+
bodySizeLimit,
|
|
3821
|
+
securityHeaders,
|
|
3822
|
+
} from '@gravito/core'
|
|
2992
3823
|
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
3824
|
+
export class MiddlewareProvider extends ServiceProvider {
|
|
3825
|
+
/**
|
|
3826
|
+
* No container bindings needed.
|
|
3827
|
+
*/
|
|
3828
|
+
register(_container: Container): void {
|
|
3829
|
+
// Middleware doesn't require container bindings
|
|
3830
|
+
}
|
|
2999
3831
|
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3832
|
+
/**
|
|
3833
|
+
* Register global middleware stack.
|
|
3834
|
+
*/
|
|
3835
|
+
boot(core: PlanetCore): void {
|
|
3836
|
+
const isDev = process.env.NODE_ENV !== 'production'
|
|
3837
|
+
|
|
3838
|
+
// Security Headers
|
|
3839
|
+
core.adapter.use('*', securityHeaders({
|
|
3840
|
+
contentSecurityPolicy: isDev ? false : undefined,
|
|
3841
|
+
}))
|
|
3842
|
+
|
|
3843
|
+
// Body Parser Limits
|
|
3844
|
+
core.adapter.use('*', bodySizeLimit(10 * 1024 * 1024)) // 10MB limit
|
|
3845
|
+
|
|
3846
|
+
core.logger.info('\u{1F6E1}\uFE0F Middleware registered')
|
|
3847
|
+
}
|
|
3848
|
+
}
|
|
3849
|
+
`;
|
|
3850
|
+
}
|
|
3851
|
+
generateRouteProvider() {
|
|
3852
|
+
return `/**
|
|
3853
|
+
* Route Service Provider
|
|
3854
|
+
*
|
|
3855
|
+
* Registers application routes.
|
|
3856
|
+
* Routes are registered in the boot phase after all services are available.
|
|
3857
|
+
*
|
|
3858
|
+
* Lifecycle:
|
|
3859
|
+
* - register(): N/A
|
|
3860
|
+
* - boot(): Register routes
|
|
3861
|
+
*/
|
|
3862
|
+
|
|
3863
|
+
import { ServiceProvider, type Container, type PlanetCore } from '@gravito/core'
|
|
3864
|
+
import { registerRoutes } from '../routes'
|
|
3865
|
+
|
|
3866
|
+
export class RouteProvider extends ServiceProvider {
|
|
3867
|
+
/**
|
|
3868
|
+
* No container bindings needed.
|
|
3869
|
+
*/
|
|
3870
|
+
register(_container: Container): void {
|
|
3871
|
+
// Routes don't require container bindings
|
|
3872
|
+
}
|
|
3873
|
+
|
|
3874
|
+
/**
|
|
3875
|
+
* Register application routes.
|
|
3876
|
+
*/
|
|
3877
|
+
boot(core: PlanetCore): void {
|
|
3878
|
+
registerRoutes(core.router)
|
|
3879
|
+
core.logger.info('\u{1F6E4}\uFE0F Routes registered')
|
|
3880
|
+
}
|
|
3881
|
+
}
|
|
3882
|
+
`;
|
|
3883
|
+
}
|
|
3884
|
+
generateExceptionHandler() {
|
|
3885
|
+
return `/**
|
|
3886
|
+
* Exception Handler
|
|
3887
|
+
*
|
|
3888
|
+
* Handles all exceptions thrown by the application.
|
|
3889
|
+
* Customize error responses and logging here.
|
|
3890
|
+
*/
|
|
3891
|
+
|
|
3892
|
+
import type { ErrorHandlerContext } from '@gravito/core'
|
|
3893
|
+
|
|
3894
|
+
/**
|
|
3895
|
+
* Report an exception (logging, monitoring, etc.)
|
|
3896
|
+
*/
|
|
3897
|
+
export function report(error: unknown, context: ErrorHandlerContext): void {
|
|
3898
|
+
// Log to external service (Sentry, etc.)
|
|
3899
|
+
if (!context.isProduction) {
|
|
3900
|
+
console.error('[Exception Handler]', error)
|
|
3901
|
+
}
|
|
3902
|
+
}
|
|
3903
|
+
|
|
3904
|
+
/**
|
|
3905
|
+
* Determine if the exception should be reported.
|
|
3906
|
+
*/
|
|
3907
|
+
export function shouldReport(error: unknown): boolean {
|
|
3908
|
+
// Don't report 4xx errors
|
|
3909
|
+
if (error instanceof Error && 'status' in error) {
|
|
3910
|
+
const status = (error as any).status
|
|
3911
|
+
if (status >= 400 && status < 500) {
|
|
3912
|
+
return false
|
|
3913
|
+
}
|
|
3914
|
+
}
|
|
3915
|
+
|
|
3916
|
+
return true
|
|
3917
|
+
}
|
|
3918
|
+
|
|
3919
|
+
/**
|
|
3920
|
+
* A list of exception types that should not be reported.
|
|
3921
|
+
*/
|
|
3922
|
+
export const dontReport: string[] = [
|
|
3923
|
+
'ValidationException',
|
|
3924
|
+
'NotFoundException',
|
|
3925
|
+
]
|
|
3926
|
+
`;
|
|
3927
|
+
}
|
|
3928
|
+
generateBootstrap(context) {
|
|
3929
|
+
const spectrumImport = context.withSpectrum ? "import { SpectrumOrbit } from '@gravito/spectrum'\n" : "";
|
|
3930
|
+
const spectrumOrbit = context.withSpectrum ? " new SpectrumOrbit()," : "";
|
|
3931
|
+
return `/**
|
|
3932
|
+
* Application Bootstrap
|
|
3933
|
+
*
|
|
3934
|
+
* The entry point for your Gravito application.
|
|
3935
|
+
* Uses the ServiceProvider pattern for modular, maintainable initialization.
|
|
3936
|
+
*
|
|
3937
|
+
* Lifecycle:
|
|
3938
|
+
* 1. Configure: Load app config and orbits
|
|
3939
|
+
* 2. Boot: Initialize PlanetCore
|
|
3940
|
+
* 3. Register Providers: Bind services to container
|
|
3941
|
+
* 4. Bootstrap: Boot all providers
|
|
3942
|
+
*
|
|
3943
|
+
* @module bootstrap
|
|
3944
|
+
*/
|
|
3003
3945
|
|
|
3004
|
-
|
|
3005
|
-
|
|
3946
|
+
import { defineConfig, PlanetCore } from '@gravito/core'
|
|
3947
|
+
import { OrbitAtlas } from '@gravito/atlas'
|
|
3948
|
+
import appConfig from '../config/app'
|
|
3949
|
+
${spectrumImport}import {
|
|
3950
|
+
AppServiceProvider,
|
|
3951
|
+
DatabaseProvider,
|
|
3952
|
+
MiddlewareProvider,
|
|
3953
|
+
RouteProvider,
|
|
3954
|
+
} from './Providers'
|
|
3955
|
+
|
|
3956
|
+
/**
|
|
3957
|
+
* Bootstrap the application with service providers.
|
|
3958
|
+
*
|
|
3959
|
+
* @returns The booted PlanetCore instance
|
|
3960
|
+
*/
|
|
3961
|
+
export async function bootstrap() {
|
|
3962
|
+
// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
3963
|
+
// 1. Configure
|
|
3964
|
+
// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
3965
|
+
const config = defineConfig({
|
|
3966
|
+
config: appConfig,
|
|
3967
|
+
orbits: [
|
|
3968
|
+
new OrbitAtlas(),
|
|
3969
|
+
${spectrumOrbit}
|
|
3970
|
+
],
|
|
3971
|
+
})
|
|
3972
|
+
|
|
3973
|
+
// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
3974
|
+
// 2. Boot Core
|
|
3975
|
+
// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
3976
|
+
const core = await PlanetCore.boot(config)
|
|
3977
|
+
core.registerGlobalErrorHandlers()
|
|
3978
|
+
|
|
3979
|
+
// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
3980
|
+
// 3. Register Providers
|
|
3981
|
+
// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
3982
|
+
core.register(new AppServiceProvider())
|
|
3983
|
+
core.register(new DatabaseProvider())
|
|
3984
|
+
core.register(new MiddlewareProvider())
|
|
3985
|
+
core.register(new RouteProvider())
|
|
3986
|
+
|
|
3987
|
+
// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
3988
|
+
// 4. Bootstrap All Providers
|
|
3989
|
+
// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
3990
|
+
await core.bootstrap()
|
|
3991
|
+
|
|
3992
|
+
return core
|
|
3993
|
+
}
|
|
3006
3994
|
|
|
3007
|
-
//
|
|
3995
|
+
// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
3996
|
+
// Application Entry Point
|
|
3997
|
+
// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
3998
|
+
const core = await bootstrap()
|
|
3008
3999
|
export default core.liftoff()
|
|
3009
4000
|
`;
|
|
3010
4001
|
}
|
|
@@ -3167,51 +4158,1017 @@ Created with \u2764\uFE0F using Gravito Framework
|
|
|
3167
4158
|
}
|
|
3168
4159
|
};
|
|
3169
4160
|
|
|
3170
|
-
// src/
|
|
3171
|
-
var
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
verbose;
|
|
3175
|
-
constructor(options = {}) {
|
|
3176
|
-
this.templatesDir = options.templatesDir ?? import_node_path3.default.resolve(__dirname, "../templates");
|
|
3177
|
-
this.verbose = options.verbose ?? false;
|
|
4161
|
+
// src/generators/SatelliteGenerator.ts
|
|
4162
|
+
var SatelliteGenerator = class extends BaseGenerator {
|
|
4163
|
+
get architectureType() {
|
|
4164
|
+
return "satellite";
|
|
3178
4165
|
}
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
4166
|
+
get displayName() {
|
|
4167
|
+
return "Gravito Satellite";
|
|
4168
|
+
}
|
|
4169
|
+
get description() {
|
|
4170
|
+
return "A modular plugin for Gravito following DDD and Clean Architecture";
|
|
4171
|
+
}
|
|
4172
|
+
getDirectoryStructure(context) {
|
|
4173
|
+
const name = context.namePascalCase;
|
|
3183
4174
|
return [
|
|
3184
4175
|
{
|
|
3185
|
-
type: "
|
|
3186
|
-
name: "
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
4176
|
+
type: "directory",
|
|
4177
|
+
name: "src",
|
|
4178
|
+
children: [
|
|
4179
|
+
// Domain Layer
|
|
4180
|
+
{
|
|
4181
|
+
type: "directory",
|
|
4182
|
+
name: "Domain",
|
|
4183
|
+
children: [
|
|
4184
|
+
{
|
|
4185
|
+
type: "directory",
|
|
4186
|
+
name: "Entities",
|
|
4187
|
+
children: [
|
|
4188
|
+
{ type: "file", name: `${name}.ts`, content: this.generateEntity(name) }
|
|
4189
|
+
]
|
|
4190
|
+
},
|
|
4191
|
+
{
|
|
4192
|
+
type: "directory",
|
|
4193
|
+
name: "Contracts",
|
|
4194
|
+
children: [
|
|
4195
|
+
{
|
|
4196
|
+
type: "file",
|
|
4197
|
+
name: `I${name}Repository.ts`,
|
|
4198
|
+
content: this.generateRepositoryInterface(name)
|
|
4199
|
+
}
|
|
4200
|
+
]
|
|
4201
|
+
},
|
|
4202
|
+
{ type: "directory", name: "ValueObjects", children: [] },
|
|
4203
|
+
{ type: "directory", name: "Events", children: [] }
|
|
4204
|
+
]
|
|
4205
|
+
},
|
|
4206
|
+
// Application Layer
|
|
4207
|
+
{
|
|
4208
|
+
type: "directory",
|
|
4209
|
+
name: "Application",
|
|
4210
|
+
children: [
|
|
4211
|
+
{
|
|
4212
|
+
type: "directory",
|
|
4213
|
+
name: "UseCases",
|
|
4214
|
+
children: [
|
|
4215
|
+
{
|
|
4216
|
+
type: "file",
|
|
4217
|
+
name: `Create${name}.ts`,
|
|
4218
|
+
content: this.generateUseCase(name)
|
|
4219
|
+
}
|
|
4220
|
+
]
|
|
4221
|
+
},
|
|
4222
|
+
{ type: "directory", name: "DTOs", children: [] }
|
|
4223
|
+
]
|
|
4224
|
+
},
|
|
4225
|
+
// Infrastructure Layer
|
|
4226
|
+
{
|
|
4227
|
+
type: "directory",
|
|
4228
|
+
name: "Infrastructure",
|
|
4229
|
+
children: [
|
|
4230
|
+
{
|
|
4231
|
+
type: "directory",
|
|
4232
|
+
name: "Persistence",
|
|
4233
|
+
children: [
|
|
4234
|
+
{
|
|
4235
|
+
type: "file",
|
|
4236
|
+
name: `Atlas${name}Repository.ts`,
|
|
4237
|
+
content: this.generateAtlasRepository(name)
|
|
4238
|
+
},
|
|
4239
|
+
{ type: "directory", name: "Migrations", children: [] }
|
|
4240
|
+
]
|
|
4241
|
+
}
|
|
4242
|
+
]
|
|
4243
|
+
},
|
|
4244
|
+
// Entry Point
|
|
4245
|
+
{ type: "file", name: "index.ts", content: this.generateEntryPoint(name) },
|
|
4246
|
+
{
|
|
4247
|
+
type: "file",
|
|
4248
|
+
name: "env.d.ts",
|
|
4249
|
+
content: "interface ImportMeta {\n readonly dir: string\n readonly path: string\n}\n"
|
|
4250
|
+
},
|
|
4251
|
+
{ type: "file", name: "manifest.json", content: this.generateManifest(context) }
|
|
4252
|
+
]
|
|
3193
4253
|
},
|
|
3194
4254
|
{
|
|
3195
|
-
type: "
|
|
3196
|
-
name: "
|
|
3197
|
-
|
|
4255
|
+
type: "directory",
|
|
4256
|
+
name: "tests",
|
|
4257
|
+
children: [
|
|
4258
|
+
{
|
|
4259
|
+
type: "file",
|
|
4260
|
+
name: "unit.test.ts",
|
|
4261
|
+
content: `import { describe, it, expect } from "bun:test";
|
|
4262
|
+
|
|
4263
|
+
describe("${name}", () => {
|
|
4264
|
+
it("should work", () => {
|
|
4265
|
+
expect(true).toBe(true);
|
|
4266
|
+
});
|
|
4267
|
+
});`
|
|
4268
|
+
}
|
|
4269
|
+
]
|
|
3198
4270
|
}
|
|
3199
4271
|
];
|
|
3200
4272
|
}
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
4273
|
+
// ─────────────────────────────────────────────────────────────
|
|
4274
|
+
// Domain Templates
|
|
4275
|
+
// ─────────────────────────────────────────────────────────────
|
|
4276
|
+
generateEntity(name) {
|
|
4277
|
+
return `import { Entity } from '@gravito/enterprise'
|
|
4278
|
+
|
|
4279
|
+
export interface ${name}Props {
|
|
4280
|
+
name: string
|
|
4281
|
+
createdAt: Date
|
|
4282
|
+
}
|
|
4283
|
+
|
|
4284
|
+
export class ${name} extends Entity<string> {
|
|
4285
|
+
constructor(id: string, private props: ${name}Props) {
|
|
4286
|
+
super(id)
|
|
4287
|
+
}
|
|
4288
|
+
|
|
4289
|
+
static create(id: string, name: string): ${name} {
|
|
4290
|
+
return new ${name}(id, {
|
|
4291
|
+
name,
|
|
4292
|
+
createdAt: new Date()
|
|
4293
|
+
})
|
|
4294
|
+
}
|
|
4295
|
+
|
|
4296
|
+
get name() { return this.props.name }
|
|
4297
|
+
}
|
|
4298
|
+
`;
|
|
4299
|
+
}
|
|
4300
|
+
generateRepositoryInterface(name) {
|
|
4301
|
+
return `import { Repository } from '@gravito/enterprise'
|
|
4302
|
+
import { ${name} } from '../Entities/${name}'
|
|
4303
|
+
|
|
4304
|
+
export interface I${name}Repository extends Repository<${name}, string> {
|
|
4305
|
+
// Add custom methods here
|
|
4306
|
+
}
|
|
4307
|
+
`;
|
|
4308
|
+
}
|
|
4309
|
+
// ─────────────────────────────────────────────────────────────
|
|
4310
|
+
// Application Templates
|
|
4311
|
+
// ─────────────────────────────────────────────────────────────
|
|
4312
|
+
generateUseCase(name) {
|
|
4313
|
+
return `import { UseCase } from '@gravito/enterprise'
|
|
4314
|
+
import { I${name}Repository } from '../../Domain/Contracts/I${name}Repository'
|
|
4315
|
+
import { ${name} } from '../../Domain/Entities/${name}'
|
|
4316
|
+
|
|
4317
|
+
export interface Create${name}Input {
|
|
4318
|
+
name: string
|
|
4319
|
+
}
|
|
4320
|
+
|
|
4321
|
+
export class Create${name} extends UseCase<Create${name}Input, string> {
|
|
4322
|
+
constructor(private repository: I${name}Repository) {
|
|
4323
|
+
super()
|
|
4324
|
+
}
|
|
4325
|
+
|
|
4326
|
+
async execute(input: Create${name}Input): Promise<string> {
|
|
4327
|
+
const id = crypto.randomUUID()
|
|
4328
|
+
const entity = ${name}.create(id, input.name)
|
|
4329
|
+
|
|
4330
|
+
await this.repository.save(entity)
|
|
4331
|
+
|
|
4332
|
+
return id
|
|
4333
|
+
}
|
|
4334
|
+
}
|
|
4335
|
+
`;
|
|
4336
|
+
}
|
|
4337
|
+
// ─────────────────────────────────────────────────────────────
|
|
4338
|
+
// Infrastructure Templates (Dogfooding Atlas)
|
|
4339
|
+
// ─────────────────────────────────────────────────────────────
|
|
4340
|
+
generateAtlasRepository(name) {
|
|
4341
|
+
return `import { I${name}Repository } from '../../Domain/Contracts/I${name}Repository'
|
|
4342
|
+
import { ${name} } from '../../Domain/Entities/${name}'
|
|
4343
|
+
import { DB } from '@gravito/atlas'
|
|
4344
|
+
|
|
4345
|
+
export class Atlas${name}Repository implements I${name}Repository {
|
|
4346
|
+
async save(entity: ${name}): Promise<void> {
|
|
4347
|
+
// Dogfooding: Use @gravito/atlas for persistence
|
|
4348
|
+
console.log('[Atlas] Saving entity:', entity.id)
|
|
4349
|
+
// await DB.table('${name.toLowerCase()}s').insert({ ... })
|
|
4350
|
+
}
|
|
4351
|
+
|
|
4352
|
+
async findById(id: string): Promise<${name} | null> {
|
|
4353
|
+
return null
|
|
4354
|
+
}
|
|
4355
|
+
|
|
4356
|
+
async findAll(): Promise<${name}[]> {
|
|
4357
|
+
return []
|
|
4358
|
+
}
|
|
4359
|
+
|
|
4360
|
+
async delete(id: string): Promise<void> {}
|
|
4361
|
+
|
|
4362
|
+
async exists(id: string): Promise<boolean> {
|
|
4363
|
+
return false
|
|
4364
|
+
}
|
|
4365
|
+
}
|
|
4366
|
+
`;
|
|
4367
|
+
}
|
|
4368
|
+
// ─────────────────────────────────────────────────────────────
|
|
4369
|
+
// Entry Point & Manifest
|
|
4370
|
+
// ─────────────────────────────────────────────────────────────
|
|
4371
|
+
generateEntryPoint(name) {
|
|
4372
|
+
return `import { ServiceProvider, type Container } from '@gravito/core'
|
|
4373
|
+
import { Atlas${name}Repository } from './Infrastructure/Persistence/Atlas${name}Repository'
|
|
4374
|
+
|
|
4375
|
+
export class ${name}ServiceProvider extends ServiceProvider {
|
|
4376
|
+
register(container: Container): void {
|
|
4377
|
+
// Bind Repository
|
|
4378
|
+
container.singleton('${name.toLowerCase()}.repo', () => new Atlas${name}Repository())
|
|
4379
|
+
|
|
4380
|
+
// Bind UseCases
|
|
4381
|
+
container.singleton('${name.toLowerCase()}.create', () => {
|
|
4382
|
+
return new (require('./Application/UseCases/Create${name}').Create${name})(
|
|
4383
|
+
container.make('${name.toLowerCase()}.repo')
|
|
4384
|
+
)
|
|
4385
|
+
})
|
|
4386
|
+
}
|
|
4387
|
+
|
|
4388
|
+
boot(): void {
|
|
4389
|
+
this.core?.logger.info('\u{1F6F0}\uFE0F Satellite ${name} is operational')
|
|
4390
|
+
}
|
|
4391
|
+
}
|
|
4392
|
+
`;
|
|
4393
|
+
}
|
|
4394
|
+
generateManifest(context) {
|
|
4395
|
+
return JSON.stringify(
|
|
4396
|
+
{
|
|
4397
|
+
name: context.name,
|
|
4398
|
+
id: context.nameKebabCase,
|
|
4399
|
+
version: "0.1.0",
|
|
4400
|
+
description: context.description || "A Gravito Satellite",
|
|
4401
|
+
capabilities: [`create-${context.nameKebabCase}`],
|
|
4402
|
+
requirements: [
|
|
4403
|
+
"cache"
|
|
4404
|
+
// Example requirement
|
|
4405
|
+
],
|
|
4406
|
+
hooks: [`${context.nameKebabCase}:created`]
|
|
4407
|
+
},
|
|
4408
|
+
null,
|
|
4409
|
+
2
|
|
4410
|
+
);
|
|
4411
|
+
}
|
|
4412
|
+
generatePackageJson(context) {
|
|
4413
|
+
const isInternal = context.isInternal || false;
|
|
4414
|
+
const depVersion = isInternal ? "workspace:*" : "^1.0.0-beta.1";
|
|
4415
|
+
const pkg = {
|
|
4416
|
+
name: isInternal ? `@gravito/satellite-${context.nameKebabCase}` : `gravito-satellite-${context.nameKebabCase}`,
|
|
4417
|
+
version: "0.1.0",
|
|
4418
|
+
type: "module",
|
|
4419
|
+
main: "dist/index.js",
|
|
4420
|
+
module: "dist/index.mjs",
|
|
4421
|
+
types: "dist/index.d.ts",
|
|
4422
|
+
scripts: {
|
|
4423
|
+
build: "tsup src/index.ts --format cjs,esm --dts",
|
|
4424
|
+
test: "bun test",
|
|
4425
|
+
typecheck: "tsc --noEmit",
|
|
4426
|
+
check: "bun run typecheck && bun run test",
|
|
4427
|
+
validate: "bun run check"
|
|
4428
|
+
},
|
|
4429
|
+
dependencies: {
|
|
4430
|
+
"@gravito/core": depVersion,
|
|
4431
|
+
"@gravito/enterprise": depVersion,
|
|
4432
|
+
"@gravito/atlas": depVersion,
|
|
4433
|
+
"@gravito/stasis": depVersion
|
|
4434
|
+
},
|
|
4435
|
+
devDependencies: {
|
|
4436
|
+
tsup: "^8.0.0",
|
|
4437
|
+
typescript: "^5.0.0"
|
|
4438
|
+
}
|
|
4439
|
+
};
|
|
4440
|
+
return JSON.stringify(pkg, null, 2);
|
|
4441
|
+
}
|
|
4442
|
+
generateArchitectureDoc(context) {
|
|
4443
|
+
return `# ${context.name} Satellite Architecture
|
|
4444
|
+
|
|
4445
|
+
This satellite follows the Gravito Satellite Specification v1.0.
|
|
4446
|
+
|
|
4447
|
+
## Design
|
|
4448
|
+
- **DDD**: Domain logic is separated from framework concerns.
|
|
4449
|
+
- **Dogfooding**: Uses official Gravito modules (@gravito/atlas, @gravito/stasis).
|
|
4450
|
+
- **Decoupled**: Inter-satellite communication happens via Contracts and Events.
|
|
4451
|
+
|
|
4452
|
+
## Layers
|
|
4453
|
+
- **Domain**: Pure business rules.
|
|
4454
|
+
- **Application**: Orchestration of domain tasks.
|
|
4455
|
+
- **Infrastructure**: Implementation of persistence and external services.
|
|
4456
|
+
- **Interface**: HTTP and Event entry points.
|
|
4457
|
+
`;
|
|
4458
|
+
}
|
|
4459
|
+
};
|
|
4460
|
+
|
|
4461
|
+
// src/LockGenerator.ts
|
|
4462
|
+
var LockGenerator = class {
|
|
4463
|
+
generate(profileName, config, templateName = "basic", templateVersion = "1.0.0") {
|
|
4464
|
+
const lock = {
|
|
4465
|
+
profile: {
|
|
4466
|
+
name: profileName,
|
|
4467
|
+
version: "1.0.0"
|
|
4468
|
+
// This could be dynamic based on profile system version
|
|
4469
|
+
},
|
|
4470
|
+
features: config.features,
|
|
4471
|
+
drivers: config.drivers,
|
|
4472
|
+
manifest: {
|
|
4473
|
+
template: templateName,
|
|
4474
|
+
version: templateVersion
|
|
4475
|
+
},
|
|
4476
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4477
|
+
};
|
|
4478
|
+
return JSON.stringify(lock, null, 2);
|
|
4479
|
+
}
|
|
4480
|
+
};
|
|
4481
|
+
|
|
4482
|
+
// src/ProfileResolver.ts
|
|
4483
|
+
var ProfileResolver = class _ProfileResolver {
|
|
4484
|
+
static DEFAULTS = {
|
|
4485
|
+
core: {
|
|
4486
|
+
drivers: {
|
|
4487
|
+
database: "sqlite",
|
|
4488
|
+
cache: "memory",
|
|
4489
|
+
queue: "sync",
|
|
4490
|
+
storage: "local",
|
|
4491
|
+
session: "file"
|
|
4492
|
+
},
|
|
4493
|
+
features: []
|
|
4494
|
+
},
|
|
4495
|
+
scale: {
|
|
4496
|
+
drivers: {
|
|
4497
|
+
database: "postgresql",
|
|
4498
|
+
cache: "redis",
|
|
4499
|
+
queue: "redis",
|
|
4500
|
+
storage: "s3",
|
|
4501
|
+
session: "redis"
|
|
4502
|
+
},
|
|
4503
|
+
features: ["stream", "nebula"]
|
|
4504
|
+
},
|
|
4505
|
+
enterprise: {
|
|
4506
|
+
drivers: {
|
|
4507
|
+
database: "postgresql",
|
|
4508
|
+
cache: "redis",
|
|
4509
|
+
queue: "redis",
|
|
4510
|
+
storage: "s3",
|
|
4511
|
+
session: "redis"
|
|
4512
|
+
},
|
|
4513
|
+
features: ["stream", "nebula", "monitor", "sentinel", "fortify"]
|
|
4514
|
+
}
|
|
4515
|
+
};
|
|
4516
|
+
resolve(profile = "core", withFeatures = []) {
|
|
4517
|
+
const base = _ProfileResolver.DEFAULTS[profile] || _ProfileResolver.DEFAULTS.core;
|
|
4518
|
+
const config = {
|
|
4519
|
+
drivers: { ...base.drivers },
|
|
4520
|
+
features: [...base.features]
|
|
4521
|
+
};
|
|
4522
|
+
for (const feature of withFeatures) {
|
|
4523
|
+
this.applyFeature(config, feature);
|
|
4524
|
+
}
|
|
4525
|
+
return config;
|
|
4526
|
+
}
|
|
4527
|
+
applyFeature(config, feature) {
|
|
4528
|
+
switch (feature) {
|
|
4529
|
+
case "redis":
|
|
4530
|
+
config.drivers.cache = "redis";
|
|
4531
|
+
config.drivers.queue = "redis";
|
|
4532
|
+
config.drivers.session = "redis";
|
|
4533
|
+
break;
|
|
4534
|
+
case "postgres":
|
|
4535
|
+
case "postgresql":
|
|
4536
|
+
config.drivers.database = "postgresql";
|
|
4537
|
+
break;
|
|
4538
|
+
case "mysql":
|
|
4539
|
+
config.drivers.database = "mysql";
|
|
4540
|
+
break;
|
|
4541
|
+
case "s3":
|
|
4542
|
+
config.drivers.storage = "s3";
|
|
4543
|
+
break;
|
|
4544
|
+
case "r2":
|
|
4545
|
+
config.drivers.storage = "r2";
|
|
4546
|
+
break;
|
|
4547
|
+
case "queue":
|
|
4548
|
+
if (config.drivers.queue === "sync") {
|
|
4549
|
+
config.drivers.queue = "redis";
|
|
4550
|
+
}
|
|
4551
|
+
break;
|
|
4552
|
+
default:
|
|
4553
|
+
if (!config.features.includes(feature)) {
|
|
4554
|
+
config.features.push(feature);
|
|
4555
|
+
}
|
|
4556
|
+
}
|
|
4557
|
+
}
|
|
4558
|
+
/**
|
|
4559
|
+
* Validator for profile names
|
|
4560
|
+
*/
|
|
4561
|
+
isValidProfile(profile) {
|
|
4562
|
+
return profile in _ProfileResolver.DEFAULTS;
|
|
4563
|
+
}
|
|
4564
|
+
/**
|
|
4565
|
+
* Validator for feature names
|
|
4566
|
+
*/
|
|
4567
|
+
isValidFeature(feature) {
|
|
4568
|
+
const validFeatures = [
|
|
4569
|
+
"redis",
|
|
4570
|
+
"postgres",
|
|
4571
|
+
"postgresql",
|
|
4572
|
+
"mysql",
|
|
4573
|
+
"s3",
|
|
4574
|
+
"r2",
|
|
4575
|
+
"queue",
|
|
4576
|
+
"stream",
|
|
4577
|
+
"nebula",
|
|
4578
|
+
"monitor",
|
|
4579
|
+
"sentinel",
|
|
4580
|
+
"fortify"
|
|
4581
|
+
];
|
|
4582
|
+
return validFeatures.includes(feature);
|
|
4583
|
+
}
|
|
4584
|
+
};
|
|
4585
|
+
|
|
4586
|
+
// src/Scaffold.ts
|
|
4587
|
+
var import_node_path3 = __toESM(require("path"), 1);
|
|
4588
|
+
|
|
4589
|
+
// src/generators/ActionDomainGenerator.ts
|
|
4590
|
+
var ActionDomainGenerator = class extends BaseGenerator {
|
|
4591
|
+
get architectureType() {
|
|
4592
|
+
return "action-domain";
|
|
4593
|
+
}
|
|
4594
|
+
get displayName() {
|
|
4595
|
+
return "Action Domain";
|
|
4596
|
+
}
|
|
4597
|
+
get description() {
|
|
4598
|
+
return "Single-responsibility Action pattern for clear business logic separation";
|
|
4599
|
+
}
|
|
4600
|
+
getDirectoryStructure(context) {
|
|
4601
|
+
return [
|
|
4602
|
+
{
|
|
4603
|
+
type: "directory",
|
|
4604
|
+
name: "config",
|
|
4605
|
+
children: [
|
|
4606
|
+
{ type: "file", name: "app.ts", content: this.generateAppConfig(context) },
|
|
4607
|
+
{ type: "file", name: "database.ts", content: this.generateDatabaseConfig() }
|
|
4608
|
+
]
|
|
4609
|
+
},
|
|
4610
|
+
{
|
|
4611
|
+
type: "directory",
|
|
4612
|
+
name: "database",
|
|
4613
|
+
children: [{ type: "file", name: ".gitkeep", content: "" }]
|
|
4614
|
+
},
|
|
4615
|
+
{
|
|
4616
|
+
type: "directory",
|
|
4617
|
+
name: "src",
|
|
4618
|
+
children: [
|
|
4619
|
+
{
|
|
4620
|
+
type: "directory",
|
|
4621
|
+
name: "actions",
|
|
4622
|
+
children: [
|
|
4623
|
+
{ type: "file", name: "Action.ts", content: this.generateActionBase() },
|
|
4624
|
+
{
|
|
4625
|
+
type: "directory",
|
|
4626
|
+
name: "server",
|
|
4627
|
+
children: [
|
|
4628
|
+
{
|
|
4629
|
+
type: "file",
|
|
4630
|
+
name: "GetServerStatusAction.ts",
|
|
4631
|
+
content: this.generateGetServerStatusAction()
|
|
4632
|
+
}
|
|
4633
|
+
]
|
|
4634
|
+
}
|
|
4635
|
+
]
|
|
4636
|
+
},
|
|
4637
|
+
{
|
|
4638
|
+
type: "directory",
|
|
4639
|
+
name: "controllers",
|
|
4640
|
+
children: [
|
|
4641
|
+
{
|
|
4642
|
+
type: "directory",
|
|
4643
|
+
name: "api",
|
|
4644
|
+
children: [
|
|
4645
|
+
{
|
|
4646
|
+
type: "directory",
|
|
4647
|
+
name: "v1",
|
|
4648
|
+
children: [
|
|
4649
|
+
{
|
|
4650
|
+
type: "file",
|
|
4651
|
+
name: "ServerController.ts",
|
|
4652
|
+
content: this.generateServerController()
|
|
4653
|
+
}
|
|
4654
|
+
]
|
|
4655
|
+
}
|
|
4656
|
+
]
|
|
4657
|
+
}
|
|
4658
|
+
]
|
|
4659
|
+
},
|
|
4660
|
+
{
|
|
4661
|
+
type: "directory",
|
|
4662
|
+
name: "types",
|
|
4663
|
+
children: [
|
|
4664
|
+
{
|
|
4665
|
+
type: "directory",
|
|
4666
|
+
name: "requests",
|
|
4667
|
+
children: [
|
|
4668
|
+
{
|
|
4669
|
+
type: "directory",
|
|
4670
|
+
name: "api",
|
|
4671
|
+
children: [
|
|
4672
|
+
{
|
|
4673
|
+
type: "directory",
|
|
4674
|
+
name: "v1",
|
|
4675
|
+
children: [{ type: "file", name: ".gitkeep", content: "" }]
|
|
4676
|
+
}
|
|
4677
|
+
]
|
|
4678
|
+
}
|
|
4679
|
+
]
|
|
4680
|
+
},
|
|
4681
|
+
{
|
|
4682
|
+
type: "directory",
|
|
4683
|
+
name: "responses",
|
|
4684
|
+
children: [
|
|
4685
|
+
{
|
|
4686
|
+
type: "directory",
|
|
4687
|
+
name: "api",
|
|
4688
|
+
children: [
|
|
4689
|
+
{
|
|
4690
|
+
type: "directory",
|
|
4691
|
+
name: "v1",
|
|
4692
|
+
children: [
|
|
4693
|
+
{
|
|
4694
|
+
type: "file",
|
|
4695
|
+
name: "ServerStatusResponse.ts",
|
|
4696
|
+
content: this.generateServerStatusResponse()
|
|
4697
|
+
}
|
|
4698
|
+
]
|
|
4699
|
+
}
|
|
4700
|
+
]
|
|
4701
|
+
}
|
|
4702
|
+
]
|
|
4703
|
+
}
|
|
4704
|
+
]
|
|
4705
|
+
},
|
|
4706
|
+
{
|
|
4707
|
+
type: "directory",
|
|
4708
|
+
name: "models",
|
|
4709
|
+
children: [{ type: "file", name: "User.ts", content: this.generateUserModel() }]
|
|
4710
|
+
},
|
|
4711
|
+
{
|
|
4712
|
+
type: "directory",
|
|
4713
|
+
name: "repositories",
|
|
4714
|
+
children: [{ type: "file", name: ".gitkeep", content: "" }]
|
|
4715
|
+
},
|
|
4716
|
+
{
|
|
4717
|
+
type: "directory",
|
|
4718
|
+
name: "routes",
|
|
4719
|
+
children: [{ type: "file", name: "api.ts", content: this.generateApiRoutes() }]
|
|
4720
|
+
},
|
|
4721
|
+
{
|
|
4722
|
+
type: "directory",
|
|
4723
|
+
name: "providers",
|
|
4724
|
+
children: [
|
|
4725
|
+
{
|
|
4726
|
+
type: "file",
|
|
4727
|
+
name: "index.ts",
|
|
4728
|
+
content: this.generateProvidersIndex()
|
|
4729
|
+
},
|
|
4730
|
+
{
|
|
4731
|
+
type: "file",
|
|
4732
|
+
name: "AppServiceProvider.ts",
|
|
4733
|
+
content: this.generateAppServiceProvider(context)
|
|
4734
|
+
},
|
|
4735
|
+
{
|
|
4736
|
+
type: "file",
|
|
4737
|
+
name: "MiddlewareProvider.ts",
|
|
4738
|
+
content: this.generateMiddlewareProvider()
|
|
4739
|
+
},
|
|
4740
|
+
{
|
|
4741
|
+
type: "file",
|
|
4742
|
+
name: "RouteProvider.ts",
|
|
4743
|
+
content: this.generateRouteProvider()
|
|
4744
|
+
}
|
|
4745
|
+
]
|
|
4746
|
+
},
|
|
4747
|
+
{ type: "file", name: "bootstrap.ts", content: this.generateBootstrap(context) }
|
|
4748
|
+
]
|
|
4749
|
+
},
|
|
4750
|
+
{
|
|
4751
|
+
type: "directory",
|
|
4752
|
+
name: "tests",
|
|
4753
|
+
children: [{ type: "file", name: ".gitkeep", content: "" }]
|
|
4754
|
+
}
|
|
4755
|
+
];
|
|
4756
|
+
}
|
|
4757
|
+
// ─────────────────────────────────────────────────────────────
|
|
4758
|
+
// Config Generators
|
|
4759
|
+
// ─────────────────────────────────────────────────────────────
|
|
4760
|
+
generateAppConfig(context) {
|
|
4761
|
+
return `export default {
|
|
4762
|
+
name: process.env.APP_NAME ?? '${context.name}',
|
|
4763
|
+
env: process.env.APP_ENV ?? 'development',
|
|
4764
|
+
debug: process.env.APP_DEBUG === 'true',
|
|
4765
|
+
url: process.env.APP_URL ?? 'http://localhost:3000',
|
|
4766
|
+
key: process.env.APP_KEY,
|
|
4767
|
+
}
|
|
4768
|
+
`;
|
|
4769
|
+
}
|
|
4770
|
+
generateDatabaseConfig() {
|
|
4771
|
+
return `export default {
|
|
4772
|
+
default: process.env.DB_CONNECTION ?? 'sqlite',
|
|
4773
|
+
connections: {
|
|
4774
|
+
sqlite: {
|
|
4775
|
+
driver: 'sqlite',
|
|
4776
|
+
database: process.env.DB_DATABASE ?? 'database/database.sqlite',
|
|
4777
|
+
},
|
|
4778
|
+
},
|
|
4779
|
+
}
|
|
4780
|
+
`;
|
|
4781
|
+
}
|
|
4782
|
+
// ─────────────────────────────────────────────────────────────
|
|
4783
|
+
// Model Generators
|
|
4784
|
+
// ─────────────────────────────────────────────────────────────
|
|
4785
|
+
generateUserModel() {
|
|
4786
|
+
return `/**
|
|
4787
|
+
* User Model
|
|
4788
|
+
*/
|
|
4789
|
+
|
|
4790
|
+
import { Model, column } from '@gravito/atlas'
|
|
4791
|
+
|
|
4792
|
+
export class User extends Model {
|
|
4793
|
+
static table = 'users'
|
|
4794
|
+
|
|
4795
|
+
@column({ isPrimary: true })
|
|
4796
|
+
id!: number
|
|
4797
|
+
|
|
4798
|
+
@column()
|
|
4799
|
+
name!: string
|
|
4800
|
+
|
|
4801
|
+
@column()
|
|
4802
|
+
email!: string
|
|
4803
|
+
|
|
4804
|
+
@column()
|
|
4805
|
+
created_at!: Date
|
|
4806
|
+
|
|
4807
|
+
@column()
|
|
4808
|
+
updated_at!: Date
|
|
4809
|
+
}
|
|
4810
|
+
`;
|
|
4811
|
+
}
|
|
4812
|
+
// ─────────────────────────────────────────────────────────────
|
|
4813
|
+
// Action Generators
|
|
4814
|
+
// ─────────────────────────────────────────────────────────────
|
|
4815
|
+
generateActionBase() {
|
|
4816
|
+
return `/**
|
|
4817
|
+
* Action Base Class
|
|
4818
|
+
*
|
|
4819
|
+
* All business logic actions should extend this class.
|
|
4820
|
+
* It enforces a consistent execution method.
|
|
4821
|
+
*/
|
|
4822
|
+
|
|
4823
|
+
export abstract class Action<TInput = unknown, TOutput = unknown> {
|
|
4824
|
+
/**
|
|
4825
|
+
* Execute the business logic.
|
|
4826
|
+
*/
|
|
4827
|
+
abstract execute(input: TInput): Promise<TOutput> | TOutput
|
|
4828
|
+
}
|
|
4829
|
+
`;
|
|
4830
|
+
}
|
|
4831
|
+
generateGetServerStatusAction() {
|
|
4832
|
+
return `/**
|
|
4833
|
+
* Get Server Status Action
|
|
4834
|
+
*/
|
|
4835
|
+
|
|
4836
|
+
import { Action } from '../Action'
|
|
4837
|
+
import type { ServerStatusResponse } from '../../types/responses/api/v1/ServerStatusResponse'
|
|
4838
|
+
|
|
4839
|
+
export class GetServerStatusAction extends Action<void, ServerStatusResponse> {
|
|
4840
|
+
execute(): Promise<ServerStatusResponse> {
|
|
4841
|
+
return Promise.resolve({
|
|
4842
|
+
status: 'active',
|
|
4843
|
+
timestamp: new Date().toISOString(),
|
|
4844
|
+
service: 'Gravito Hub'
|
|
4845
|
+
})
|
|
4846
|
+
}
|
|
4847
|
+
}
|
|
4848
|
+
`;
|
|
4849
|
+
}
|
|
4850
|
+
// ─────────────────────────────────────────────────────────────
|
|
4851
|
+
// Controller Generators
|
|
4852
|
+
// ─────────────────────────────────────────────────────────────
|
|
4853
|
+
generateServerController() {
|
|
4854
|
+
return `/**
|
|
4855
|
+
* Server Controller
|
|
4856
|
+
*/
|
|
4857
|
+
|
|
4858
|
+
import type { GravitoContext } from '@gravito/core'
|
|
4859
|
+
import { GetServerStatusAction } from '../../../actions/server/GetServerStatusAction'
|
|
4860
|
+
|
|
4861
|
+
export class ServerController {
|
|
4862
|
+
/**
|
|
4863
|
+
* GET /v1/server/status
|
|
4864
|
+
*/
|
|
4865
|
+
async status(c: GravitoContext) {
|
|
4866
|
+
const action = new GetServerStatusAction()
|
|
4867
|
+
const result = await action.execute()
|
|
4868
|
+
|
|
4869
|
+
return c.json({
|
|
4870
|
+
success: true,
|
|
4871
|
+
data: result
|
|
4872
|
+
})
|
|
4873
|
+
}
|
|
4874
|
+
}
|
|
4875
|
+
`;
|
|
4876
|
+
}
|
|
4877
|
+
// ─────────────────────────────────────────────────────────────
|
|
4878
|
+
// Type Generators
|
|
4879
|
+
// ─────────────────────────────────────────────────────────────
|
|
4880
|
+
generateServerStatusResponse() {
|
|
4881
|
+
return `/**
|
|
4882
|
+
* Server Status Response Type
|
|
4883
|
+
*/
|
|
4884
|
+
|
|
4885
|
+
export interface ServerStatusResponse {
|
|
4886
|
+
status: string
|
|
4887
|
+
timestamp: string
|
|
4888
|
+
service: string
|
|
4889
|
+
}
|
|
4890
|
+
`;
|
|
4891
|
+
}
|
|
4892
|
+
// ─────────────────────────────────────────────────────────────
|
|
4893
|
+
// Routes & Bootstrap
|
|
4894
|
+
// ─────────────────────────────────────────────────────────────
|
|
4895
|
+
generateApiRoutes() {
|
|
4896
|
+
return `/**
|
|
4897
|
+
* API Routes Registration
|
|
4898
|
+
*/
|
|
4899
|
+
|
|
4900
|
+
import type { Router } from '@gravito/core'
|
|
4901
|
+
import { ServerController } from '../controllers/api/v1/ServerController'
|
|
4902
|
+
|
|
4903
|
+
export function registerApiRoutes(router: Router) {
|
|
4904
|
+
const server = new ServerController()
|
|
4905
|
+
|
|
4906
|
+
router.prefix('/v1').group((group) => {
|
|
4907
|
+
// Server Domain
|
|
4908
|
+
group.get('/server/status', (c) => server.status(c))
|
|
4909
|
+
})
|
|
4910
|
+
}
|
|
4911
|
+
`;
|
|
4912
|
+
}
|
|
4913
|
+
generateAppServiceProvider(context) {
|
|
4914
|
+
return `/**
|
|
4915
|
+
* App Service Provider
|
|
4916
|
+
*/
|
|
4917
|
+
|
|
4918
|
+
import { ServiceProvider, type Container, type PlanetCore } from '@gravito/core'
|
|
4919
|
+
|
|
4920
|
+
export class AppServiceProvider extends ServiceProvider {
|
|
4921
|
+
register(_container: Container): void {
|
|
4922
|
+
// Register global services here
|
|
4923
|
+
}
|
|
4924
|
+
|
|
4925
|
+
boot(core: PlanetCore): void {
|
|
4926
|
+
core.logger.info('${context.name} (Action Domain) booted!')
|
|
4927
|
+
}
|
|
4928
|
+
}
|
|
4929
|
+
`;
|
|
4930
|
+
}
|
|
4931
|
+
generateProvidersIndex() {
|
|
4932
|
+
return `/**
|
|
4933
|
+
* Application Service Providers
|
|
4934
|
+
*/
|
|
4935
|
+
|
|
4936
|
+
export { AppServiceProvider } from './AppServiceProvider'
|
|
4937
|
+
export { MiddlewareProvider } from './MiddlewareProvider'
|
|
4938
|
+
export { RouteProvider } from './RouteProvider'
|
|
4939
|
+
`;
|
|
4940
|
+
}
|
|
4941
|
+
generateMiddlewareProvider() {
|
|
4942
|
+
return `/**
|
|
4943
|
+
* Middleware Service Provider
|
|
4944
|
+
*/
|
|
4945
|
+
|
|
4946
|
+
import {
|
|
4947
|
+
ServiceProvider,
|
|
4948
|
+
type Container,
|
|
4949
|
+
type PlanetCore,
|
|
4950
|
+
bodySizeLimit,
|
|
4951
|
+
securityHeaders,
|
|
4952
|
+
} from '@gravito/core'
|
|
4953
|
+
|
|
4954
|
+
export class MiddlewareProvider extends ServiceProvider {
|
|
4955
|
+
register(_container: Container): void {}
|
|
4956
|
+
|
|
4957
|
+
boot(core: PlanetCore): void {
|
|
4958
|
+
core.adapter.use('*', securityHeaders())
|
|
4959
|
+
core.adapter.use('*', bodySizeLimit(1024 * 1024))
|
|
4960
|
+
core.logger.info('\u{1F6E1}\uFE0F Middleware registered')
|
|
4961
|
+
}
|
|
4962
|
+
}
|
|
4963
|
+
`;
|
|
4964
|
+
}
|
|
4965
|
+
generateRouteProvider() {
|
|
4966
|
+
return `/**
|
|
4967
|
+
* Route Service Provider
|
|
4968
|
+
*/
|
|
4969
|
+
|
|
4970
|
+
import { ServiceProvider, type Container, type PlanetCore } from '@gravito/core'
|
|
4971
|
+
import { registerApiRoutes } from '../routes/api'
|
|
4972
|
+
|
|
4973
|
+
export class RouteProvider extends ServiceProvider {
|
|
4974
|
+
register(_container: Container): void {}
|
|
4975
|
+
|
|
4976
|
+
boot(core: PlanetCore): void {
|
|
4977
|
+
registerApiRoutes(core.router)
|
|
4978
|
+
core.logger.info('\u{1F6E4}\uFE0F Routes registered')
|
|
4979
|
+
}
|
|
4980
|
+
}
|
|
4981
|
+
`;
|
|
4982
|
+
}
|
|
4983
|
+
generateBootstrap(_context) {
|
|
4984
|
+
return `/**
|
|
4985
|
+
* Application Bootstrap
|
|
4986
|
+
*
|
|
4987
|
+
* Uses the ServiceProvider pattern for modular initialization.
|
|
4988
|
+
*/
|
|
4989
|
+
|
|
4990
|
+
import { defineConfig, PlanetCore } from '@gravito/core'
|
|
4991
|
+
import { OrbitAtlas } from '@gravito/atlas'
|
|
4992
|
+
import appConfig from '../config/app'
|
|
4993
|
+
import {
|
|
4994
|
+
AppServiceProvider,
|
|
4995
|
+
MiddlewareProvider,
|
|
4996
|
+
RouteProvider,
|
|
4997
|
+
} from './providers'
|
|
4998
|
+
|
|
4999
|
+
export async function bootstrap() {
|
|
5000
|
+
const config = defineConfig({
|
|
5001
|
+
config: appConfig,
|
|
5002
|
+
orbits: [new OrbitAtlas()],
|
|
5003
|
+
})
|
|
5004
|
+
|
|
5005
|
+
const core = await PlanetCore.boot(config)
|
|
5006
|
+
core.registerGlobalErrorHandlers()
|
|
5007
|
+
|
|
5008
|
+
core.register(new AppServiceProvider())
|
|
5009
|
+
core.register(new MiddlewareProvider())
|
|
5010
|
+
core.register(new RouteProvider())
|
|
5011
|
+
|
|
5012
|
+
await core.bootstrap()
|
|
5013
|
+
|
|
5014
|
+
return core
|
|
5015
|
+
}
|
|
5016
|
+
|
|
5017
|
+
const core = await bootstrap()
|
|
5018
|
+
export default core.liftoff()
|
|
5019
|
+
`;
|
|
5020
|
+
}
|
|
5021
|
+
generateArchitectureDoc(context) {
|
|
5022
|
+
return `# ${context.name} - Action Domain Architecture
|
|
5023
|
+
|
|
5024
|
+
## Overview
|
|
5025
|
+
|
|
5026
|
+
This project uses the **Action Domain** pattern, designed for high-clarity API implementations.
|
|
5027
|
+
|
|
5028
|
+
## Service Providers
|
|
5029
|
+
|
|
5030
|
+
Service providers are the central place to configure your application. They follow the ServiceProvider pattern with \`register()\` and \`boot()\` lifecycle methods.
|
|
5031
|
+
|
|
5032
|
+
## Directory Structure
|
|
5033
|
+
|
|
5034
|
+
\`\`\`
|
|
5035
|
+
src/
|
|
5036
|
+
\u251C\u2500\u2500 actions/ # Single Responsibility Business Logic
|
|
5037
|
+
\u2502 \u251C\u2500\u2500 Action.ts # Base Action class
|
|
5038
|
+
\u2502 \u2514\u2500\u2500 [Domain]/ # Domain-specific actions
|
|
5039
|
+
\u251C\u2500\u2500 controllers/ # HTTP Request Handlers
|
|
5040
|
+
\u2502 \u2514\u2500\u2500 api/v1/ # API Controllers
|
|
5041
|
+
\u251C\u2500\u2500 types/ # TypeScript Definitions
|
|
5042
|
+
\u251C\u2500\u2500 repositories/ # Data Access Layer
|
|
5043
|
+
\u251C\u2500\u2500 routes/ # Route Definitions
|
|
5044
|
+
\u251C\u2500\u2500 providers/ # Service Providers
|
|
5045
|
+
\u2514\u2500\u2500 config/ # Configuration
|
|
5046
|
+
\`\`\`
|
|
5047
|
+
|
|
5048
|
+
## Core Concepts
|
|
5049
|
+
|
|
5050
|
+
### Actions
|
|
5051
|
+
Every business operation is an "Action". An action:
|
|
5052
|
+
- Does ONE thing.
|
|
5053
|
+
- Takes specific input.
|
|
5054
|
+
- Returns specific output.
|
|
5055
|
+
- Is framework-agnostic (ideally).
|
|
5056
|
+
|
|
5057
|
+
### Controllers
|
|
5058
|
+
Controllers are thin. They:
|
|
5059
|
+
1. Parse the request.
|
|
5060
|
+
2. Instantiate/Call the Action.
|
|
5061
|
+
3. Return the response.
|
|
5062
|
+
|
|
5063
|
+
Created with \u2764\uFE0F using Gravito Framework
|
|
5064
|
+
`;
|
|
5065
|
+
}
|
|
5066
|
+
generatePackageJson(context) {
|
|
5067
|
+
const pkg = {
|
|
5068
|
+
name: context.nameKebabCase,
|
|
5069
|
+
version: "0.1.0",
|
|
5070
|
+
type: "module",
|
|
5071
|
+
scripts: {
|
|
5072
|
+
dev: "bun run --watch src/bootstrap.ts",
|
|
5073
|
+
build: "bun build ./src/bootstrap.ts --outdir ./dist --target bun",
|
|
5074
|
+
start: "bun run dist/bootstrap.js",
|
|
5075
|
+
test: "bun test",
|
|
5076
|
+
typecheck: "tsc --noEmit",
|
|
5077
|
+
check: "bun run typecheck && bun run test",
|
|
5078
|
+
"check:deps": "bun run scripts/check-dependencies.ts",
|
|
5079
|
+
validate: "bun run check && bun run check:deps",
|
|
5080
|
+
precommit: "bun run validate"
|
|
5081
|
+
},
|
|
5082
|
+
dependencies: {
|
|
5083
|
+
"@gravito/core": "workspace:*",
|
|
5084
|
+
"@gravito/enterprise": "workspace:*",
|
|
5085
|
+
"@gravito/atlas": "workspace:*"
|
|
5086
|
+
// Usually needed for repositories
|
|
5087
|
+
},
|
|
5088
|
+
devDependencies: {
|
|
5089
|
+
"bun-types": "latest",
|
|
5090
|
+
typescript: "^5.0.0"
|
|
5091
|
+
}
|
|
5092
|
+
};
|
|
5093
|
+
return JSON.stringify(pkg, null, 2);
|
|
5094
|
+
}
|
|
5095
|
+
};
|
|
5096
|
+
|
|
5097
|
+
// src/Scaffold.ts
|
|
5098
|
+
var Scaffold = class {
|
|
5099
|
+
templatesDir;
|
|
5100
|
+
verbose;
|
|
5101
|
+
constructor(options = {}) {
|
|
5102
|
+
this.templatesDir = options.templatesDir ?? import_node_path3.default.resolve(__dirname, "../templates");
|
|
5103
|
+
this.verbose = options.verbose ?? false;
|
|
5104
|
+
}
|
|
5105
|
+
/**
|
|
5106
|
+
* Get all available architecture types.
|
|
5107
|
+
*/
|
|
5108
|
+
getArchitectureTypes() {
|
|
5109
|
+
return [
|
|
5110
|
+
{
|
|
5111
|
+
type: "enterprise-mvc",
|
|
5112
|
+
name: "Enterprise MVC",
|
|
5113
|
+
description: "Laravel-inspired MVC with Services and Repositories"
|
|
5114
|
+
},
|
|
5115
|
+
{
|
|
5116
|
+
type: "clean",
|
|
5117
|
+
name: "Clean Architecture",
|
|
5118
|
+
description: "Uncle Bob's Clean Architecture with strict dependency rules"
|
|
5119
|
+
},
|
|
5120
|
+
{
|
|
5121
|
+
type: "ddd",
|
|
5122
|
+
name: "Domain-Driven Design",
|
|
5123
|
+
description: "Full DDD with Bounded Contexts and CQRS"
|
|
5124
|
+
},
|
|
5125
|
+
{
|
|
5126
|
+
type: "action-domain",
|
|
5127
|
+
name: "Action Domain",
|
|
5128
|
+
description: "Single Action Controllers pattern for high-clarity APIs"
|
|
5129
|
+
},
|
|
5130
|
+
{
|
|
5131
|
+
type: "satellite",
|
|
5132
|
+
name: "Gravito Satellite",
|
|
5133
|
+
description: "Plug-and-play module for the Gravito ecosystem"
|
|
5134
|
+
}
|
|
5135
|
+
];
|
|
5136
|
+
}
|
|
5137
|
+
/**
|
|
5138
|
+
* Create a new project scaffold.
|
|
5139
|
+
*/
|
|
5140
|
+
async create(options) {
|
|
5141
|
+
const generator = this.createGenerator(options.architecture);
|
|
5142
|
+
const fs3 = await import("fs/promises");
|
|
5143
|
+
const profileResolver = new ProfileResolver();
|
|
5144
|
+
const profileConfig = profileResolver.resolve(options.profile, options.features);
|
|
5145
|
+
const context = BaseGenerator.createContext(
|
|
5146
|
+
options.name,
|
|
5147
|
+
options.targetDir,
|
|
5148
|
+
options.architecture,
|
|
5149
|
+
options.packageManager ?? "bun",
|
|
5150
|
+
{
|
|
5151
|
+
...options.context,
|
|
5152
|
+
withSpectrum: options.withSpectrum ?? false,
|
|
5153
|
+
isInternal: options.isInternal ?? false,
|
|
5154
|
+
profile: options.profile ?? "core",
|
|
5155
|
+
features: options.features ?? [],
|
|
5156
|
+
profileConfig
|
|
5157
|
+
}
|
|
3212
5158
|
);
|
|
3213
5159
|
try {
|
|
3214
5160
|
const filesCreated = await generator.generate(context);
|
|
5161
|
+
const lockGenerator = new LockGenerator();
|
|
5162
|
+
const lockContent = lockGenerator.generate(
|
|
5163
|
+
options.profile ?? "core",
|
|
5164
|
+
profileConfig,
|
|
5165
|
+
"basic",
|
|
5166
|
+
// Default template for now, should come from options if applicable
|
|
5167
|
+
"1.0.0"
|
|
5168
|
+
);
|
|
5169
|
+
const lockPath = import_node_path3.default.resolve(options.targetDir, "gravito.lock.json");
|
|
5170
|
+
await fs3.writeFile(lockPath, lockContent, "utf-8");
|
|
5171
|
+
filesCreated.push(lockPath);
|
|
3215
5172
|
return {
|
|
3216
5173
|
success: true,
|
|
3217
5174
|
targetDir: options.targetDir,
|
|
@@ -3241,6 +5198,10 @@ var Scaffold = class {
|
|
|
3241
5198
|
return new CleanArchitectureGenerator(config);
|
|
3242
5199
|
case "ddd":
|
|
3243
5200
|
return new DddGenerator(config);
|
|
5201
|
+
case "action-domain":
|
|
5202
|
+
return new ActionDomainGenerator(config);
|
|
5203
|
+
case "satellite":
|
|
5204
|
+
return new SatelliteGenerator(config);
|
|
3244
5205
|
default:
|
|
3245
5206
|
throw new Error(`Unknown architecture type: ${type}`);
|
|
3246
5207
|
}
|
|
@@ -3264,6 +5225,11 @@ var Scaffold = class {
|
|
|
3264
5225
|
CleanArchitectureGenerator,
|
|
3265
5226
|
DddGenerator,
|
|
3266
5227
|
EnterpriseMvcGenerator,
|
|
5228
|
+
EnvironmentDetector,
|
|
5229
|
+
FileMerger,
|
|
5230
|
+
LockGenerator,
|
|
5231
|
+
ProfileResolver,
|
|
5232
|
+
SatelliteGenerator,
|
|
3267
5233
|
Scaffold,
|
|
3268
5234
|
StubGenerator
|
|
3269
5235
|
});
|