@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.js
CHANGED
|
@@ -1,3 +1,102 @@
|
|
|
1
|
+
// src/EnvironmentDetector.ts
|
|
2
|
+
var EnvironmentDetector = class {
|
|
3
|
+
detect() {
|
|
4
|
+
if (process.env.KUBERNETES_SERVICE_HOST) {
|
|
5
|
+
return {
|
|
6
|
+
platform: "k8s",
|
|
7
|
+
suggestedProfile: "enterprise",
|
|
8
|
+
confidence: "high",
|
|
9
|
+
reason: "Kubernetes environment detected (KUBERNETES_SERVICE_HOST)"
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
if (process.env.AWS_LAMBDA_FUNCTION_NAME || process.env.AWS_EXECUTION_ENV) {
|
|
13
|
+
return {
|
|
14
|
+
platform: "aws",
|
|
15
|
+
suggestedProfile: "scale",
|
|
16
|
+
confidence: "high",
|
|
17
|
+
reason: "AWS Lambda environment detected"
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
if (process.env.VERCEL) {
|
|
21
|
+
return {
|
|
22
|
+
platform: "vercel",
|
|
23
|
+
suggestedProfile: "core",
|
|
24
|
+
confidence: "high",
|
|
25
|
+
reason: "Vercel environment detected"
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
if (process.env.RAILWAY_ENVIRONMENT || process.env.RENDER) {
|
|
29
|
+
return {
|
|
30
|
+
platform: "unknown",
|
|
31
|
+
// Generic cloud
|
|
32
|
+
suggestedProfile: "scale",
|
|
33
|
+
confidence: "medium",
|
|
34
|
+
reason: "Cloud container environment detected"
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
platform: "unknown",
|
|
39
|
+
suggestedProfile: "core",
|
|
40
|
+
confidence: "low",
|
|
41
|
+
reason: "No specific cloud environment detected, defaulting to Core"
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// src/utils/deepMerge.ts
|
|
47
|
+
function deepMerge(target, source) {
|
|
48
|
+
if (typeof target !== "object" || target === null) {
|
|
49
|
+
return source;
|
|
50
|
+
}
|
|
51
|
+
if (typeof source !== "object" || source === null) {
|
|
52
|
+
return source;
|
|
53
|
+
}
|
|
54
|
+
if (Array.isArray(target) && Array.isArray(source)) {
|
|
55
|
+
return Array.from(/* @__PURE__ */ new Set([...target, ...source]));
|
|
56
|
+
}
|
|
57
|
+
const output = { ...target };
|
|
58
|
+
for (const key of Object.keys(source)) {
|
|
59
|
+
if (source[key] instanceof Object && key in target) {
|
|
60
|
+
output[key] = deepMerge(target[key], source[key]);
|
|
61
|
+
} else {
|
|
62
|
+
output[key] = source[key];
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return output;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// src/FileMerger.ts
|
|
69
|
+
var FileMerger = class {
|
|
70
|
+
/**
|
|
71
|
+
* Merge content of two files based on their type.
|
|
72
|
+
*/
|
|
73
|
+
merge(fileName, baseContent, overlayContent) {
|
|
74
|
+
if (fileName.endsWith(".json")) {
|
|
75
|
+
return this.mergeJson(baseContent, overlayContent);
|
|
76
|
+
}
|
|
77
|
+
if (fileName.endsWith(".env") || fileName.endsWith(".env.example")) {
|
|
78
|
+
return this.mergeEnv(baseContent, overlayContent);
|
|
79
|
+
}
|
|
80
|
+
return overlayContent;
|
|
81
|
+
}
|
|
82
|
+
mergeJson(base, overlay) {
|
|
83
|
+
try {
|
|
84
|
+
const baseJson = JSON.parse(base);
|
|
85
|
+
const overlayJson = JSON.parse(overlay);
|
|
86
|
+
const merged = deepMerge(baseJson, overlayJson);
|
|
87
|
+
return JSON.stringify(merged, null, 2);
|
|
88
|
+
} catch (e) {
|
|
89
|
+
console.warn("Failed to merge JSON, returning overlay", e);
|
|
90
|
+
return overlay;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
mergeEnv(base, overlay) {
|
|
94
|
+
return `${base}
|
|
95
|
+
# --- Overlay ---
|
|
96
|
+
${overlay}`;
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
1
100
|
// src/generators/BaseGenerator.ts
|
|
2
101
|
import fs2 from "fs/promises";
|
|
3
102
|
import path2 from "path";
|
|
@@ -155,9 +254,24 @@ var StubGenerator = class {
|
|
|
155
254
|
};
|
|
156
255
|
|
|
157
256
|
// src/generators/BaseGenerator.ts
|
|
257
|
+
async function walk(dir) {
|
|
258
|
+
const files = await fs2.readdir(dir);
|
|
259
|
+
const paths = [];
|
|
260
|
+
for (const file of files) {
|
|
261
|
+
const filePath = path2.join(dir, file);
|
|
262
|
+
const stat = await fs2.stat(filePath);
|
|
263
|
+
if (stat.isDirectory()) {
|
|
264
|
+
paths.push(...await walk(filePath));
|
|
265
|
+
} else {
|
|
266
|
+
paths.push(filePath);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return paths;
|
|
270
|
+
}
|
|
158
271
|
var BaseGenerator = class {
|
|
159
272
|
config;
|
|
160
273
|
stubGenerator;
|
|
274
|
+
fileMerger;
|
|
161
275
|
filesCreated = [];
|
|
162
276
|
constructor(config) {
|
|
163
277
|
this.config = config;
|
|
@@ -166,6 +280,7 @@ var BaseGenerator = class {
|
|
|
166
280
|
outputDir: ""
|
|
167
281
|
// Set per-generation
|
|
168
282
|
});
|
|
283
|
+
this.fileMerger = new FileMerger();
|
|
169
284
|
}
|
|
170
285
|
/**
|
|
171
286
|
* Generate the project scaffold.
|
|
@@ -178,6 +293,8 @@ var BaseGenerator = class {
|
|
|
178
293
|
const structure = this.getDirectoryStructure(context);
|
|
179
294
|
await this.createStructure(context.targetDir, structure, context);
|
|
180
295
|
await this.generateCommonFiles(context);
|
|
296
|
+
await this.applyOverlays(context);
|
|
297
|
+
await this.applyFeatureOverlays(context);
|
|
181
298
|
return this.filesCreated;
|
|
182
299
|
}
|
|
183
300
|
/**
|
|
@@ -222,11 +339,53 @@ var BaseGenerator = class {
|
|
|
222
339
|
await this.writeFile(context.targetDir, ".env", this.generateEnvExample(context));
|
|
223
340
|
await this.writeFile(context.targetDir, ".gitignore", this.generateGitignore());
|
|
224
341
|
await this.writeFile(context.targetDir, "tsconfig.json", this.generateTsConfig());
|
|
342
|
+
await this.writeFile(context.targetDir, "Dockerfile", this.generateDockerfile(context));
|
|
343
|
+
await this.writeFile(context.targetDir, ".dockerignore", this.generateDockerIgnore());
|
|
225
344
|
await this.writeFile(
|
|
226
345
|
context.targetDir,
|
|
227
346
|
"ARCHITECTURE.md",
|
|
228
347
|
this.generateArchitectureDoc(context)
|
|
229
348
|
);
|
|
349
|
+
await this.generateCheckScripts(context);
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Apply profile-specific overlays
|
|
353
|
+
*/
|
|
354
|
+
async applyOverlays(context) {
|
|
355
|
+
const profile = context.profile;
|
|
356
|
+
if (!profile) return;
|
|
357
|
+
const overlayDir = path2.resolve(this.config.templatesDir, "overlays", profile);
|
|
358
|
+
await this.copyOverlayDirectory(overlayDir, context);
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Apply feature-specific overlays
|
|
362
|
+
*/
|
|
363
|
+
async applyFeatureOverlays(context) {
|
|
364
|
+
const features = context.features || [];
|
|
365
|
+
for (const feature of features) {
|
|
366
|
+
const overlayDir = path2.resolve(this.config.templatesDir, "features", feature);
|
|
367
|
+
await this.copyOverlayDirectory(overlayDir, context);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Helper to copy/merge an overlay directory into the target
|
|
372
|
+
*/
|
|
373
|
+
async copyOverlayDirectory(sourceDir, context) {
|
|
374
|
+
try {
|
|
375
|
+
await fs2.access(sourceDir);
|
|
376
|
+
} catch {
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
const files = await walk(sourceDir);
|
|
380
|
+
for (const filePath of files) {
|
|
381
|
+
const relativePath = path2.relative(sourceDir, filePath);
|
|
382
|
+
let content = await fs2.readFile(filePath, "utf-8");
|
|
383
|
+
try {
|
|
384
|
+
content = this.stubGenerator.render(content, context);
|
|
385
|
+
} catch {
|
|
386
|
+
}
|
|
387
|
+
await this.writeFile(context.targetDir, relativePath, content);
|
|
388
|
+
}
|
|
230
389
|
}
|
|
231
390
|
/**
|
|
232
391
|
* Write a file and track it.
|
|
@@ -234,7 +393,16 @@ var BaseGenerator = class {
|
|
|
234
393
|
async writeFile(basePath, relativePath, content) {
|
|
235
394
|
const fullPath = path2.resolve(basePath, relativePath);
|
|
236
395
|
await fs2.mkdir(path2.dirname(fullPath), { recursive: true });
|
|
237
|
-
|
|
396
|
+
let finalContent = content;
|
|
397
|
+
try {
|
|
398
|
+
const existingContent = await fs2.readFile(fullPath, "utf-8");
|
|
399
|
+
finalContent = this.fileMerger.merge(relativePath, existingContent, content);
|
|
400
|
+
if (finalContent !== content) {
|
|
401
|
+
this.log(`\u{1F504} Merged file: ${relativePath}`);
|
|
402
|
+
}
|
|
403
|
+
} catch {
|
|
404
|
+
}
|
|
405
|
+
await fs2.writeFile(fullPath, finalContent, "utf-8");
|
|
238
406
|
this.filesCreated.push(fullPath);
|
|
239
407
|
this.log(`\u{1F4C4} Created file: ${relativePath}`);
|
|
240
408
|
}
|
|
@@ -242,6 +410,20 @@ var BaseGenerator = class {
|
|
|
242
410
|
* Generate package.json content.
|
|
243
411
|
*/
|
|
244
412
|
generatePackageJson(context) {
|
|
413
|
+
const profile = context.profile || "core";
|
|
414
|
+
const baseDependencies = {
|
|
415
|
+
"@gravito/core": "^1.0.0-beta.5",
|
|
416
|
+
"@gravito/atlas": "^1.0.0-beta.5",
|
|
417
|
+
"@gravito/plasma": "^1.0.0-beta.5",
|
|
418
|
+
"@gravito/stream": "^1.0.0-beta.5"
|
|
419
|
+
};
|
|
420
|
+
if (profile === "enterprise" || profile === "scale") {
|
|
421
|
+
baseDependencies["@gravito/quasar"] = "^1.0.0-beta.5";
|
|
422
|
+
baseDependencies["@gravito/horizon"] = "^1.0.0-beta.5";
|
|
423
|
+
}
|
|
424
|
+
if (context.withSpectrum) {
|
|
425
|
+
baseDependencies["@gravito/spectrum"] = "^1.0.0-beta.1";
|
|
426
|
+
}
|
|
245
427
|
const pkg = {
|
|
246
428
|
name: context.nameKebabCase,
|
|
247
429
|
version: "0.1.0",
|
|
@@ -251,42 +433,197 @@ var BaseGenerator = class {
|
|
|
251
433
|
build: "bun build ./src/bootstrap.ts --outdir ./dist --target bun",
|
|
252
434
|
start: "bun run dist/bootstrap.js",
|
|
253
435
|
test: "bun test",
|
|
254
|
-
typecheck: "tsc --noEmit"
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
436
|
+
typecheck: "tsc --noEmit",
|
|
437
|
+
check: "bun run typecheck && bun run test",
|
|
438
|
+
"check:deps": "bun run scripts/check-dependencies.ts",
|
|
439
|
+
validate: "bun run check && bun run check:deps",
|
|
440
|
+
precommit: "bun run validate",
|
|
441
|
+
"docker:build": `docker build -t ${context.nameKebabCase} .`,
|
|
442
|
+
"docker:run": `docker run -it -p 3000:3000 ${context.nameKebabCase}`
|
|
258
443
|
},
|
|
444
|
+
dependencies: baseDependencies,
|
|
259
445
|
devDependencies: {
|
|
260
|
-
"
|
|
446
|
+
"bun-types": "latest",
|
|
261
447
|
typescript: "^5.0.0"
|
|
262
448
|
}
|
|
263
449
|
};
|
|
264
450
|
return JSON.stringify(pkg, null, 2);
|
|
265
451
|
}
|
|
452
|
+
/**
|
|
453
|
+
* Generate Dockerfile content.
|
|
454
|
+
*/
|
|
455
|
+
generateDockerfile(context) {
|
|
456
|
+
const entrypoint = context.architecture === "ddd" ? "dist/main.js" : "dist/bootstrap.js";
|
|
457
|
+
return `FROM oven/bun:1.0 AS base
|
|
458
|
+
WORKDIR /usr/src/app
|
|
459
|
+
|
|
460
|
+
# Install dependencies
|
|
461
|
+
FROM base AS install
|
|
462
|
+
RUN mkdir -p /temp/dev
|
|
463
|
+
COPY package.json bun.lockb /temp/dev/
|
|
464
|
+
RUN cd /temp/dev && bun install --frozen-lockfile
|
|
465
|
+
|
|
466
|
+
# Build application
|
|
467
|
+
FROM base AS build
|
|
468
|
+
COPY --from=install /temp/dev/node_modules node_modules
|
|
469
|
+
COPY . .
|
|
470
|
+
ENV NODE_ENV=production
|
|
471
|
+
RUN bun run build
|
|
472
|
+
|
|
473
|
+
# Final production image
|
|
474
|
+
FROM base AS release
|
|
475
|
+
COPY --from=build /usr/src/app/${entrypoint} index.js
|
|
476
|
+
COPY --from=build /usr/src/app/package.json .
|
|
477
|
+
|
|
478
|
+
# Create a non-root user for security
|
|
479
|
+
USER bun
|
|
480
|
+
EXPOSE 3000/tcp
|
|
481
|
+
ENTRYPOINT [ "bun", "run", "index.js" ]
|
|
482
|
+
`;
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Generate .dockerignore content.
|
|
486
|
+
*/
|
|
487
|
+
generateDockerIgnore() {
|
|
488
|
+
return `node_modules
|
|
489
|
+
dist
|
|
490
|
+
.git
|
|
491
|
+
.env
|
|
492
|
+
*.log
|
|
493
|
+
.vscode
|
|
494
|
+
.idea
|
|
495
|
+
tests
|
|
496
|
+
`;
|
|
497
|
+
}
|
|
266
498
|
/**
|
|
267
499
|
* Generate .env.example content.
|
|
268
500
|
*/
|
|
269
501
|
generateEnvExample(context) {
|
|
270
|
-
|
|
271
|
-
|
|
502
|
+
const profile = context.profile || "core";
|
|
503
|
+
let envContent = `# ============================================================================
|
|
504
|
+
# Application Configuration
|
|
505
|
+
# ============================================================================
|
|
506
|
+
|
|
507
|
+
APP_NAME=${context.name}
|
|
272
508
|
APP_ENV=development
|
|
273
|
-
APP_KEY=
|
|
274
509
|
APP_DEBUG=true
|
|
275
510
|
APP_URL=http://localhost:3000
|
|
511
|
+
APP_KEY=
|
|
276
512
|
|
|
277
|
-
#
|
|
278
|
-
|
|
513
|
+
# ============================================================================
|
|
514
|
+
# Database Configuration
|
|
515
|
+
# ============================================================================
|
|
279
516
|
|
|
280
|
-
# Database
|
|
281
|
-
DB_CONNECTION
|
|
517
|
+
# Database Connection (sqlite, postgres, mysql)
|
|
518
|
+
DB_CONNECTION=${profile === "core" ? "sqlite" : "postgres"}
|
|
519
|
+
|
|
520
|
+
# SQLite Configuration (when DB_CONNECTION=sqlite)
|
|
282
521
|
DB_DATABASE=database/database.sqlite
|
|
283
522
|
|
|
284
|
-
#
|
|
285
|
-
|
|
523
|
+
# PostgreSQL Configuration (when DB_CONNECTION=postgres)
|
|
524
|
+
${profile !== "core" ? `DB_HOST=127.0.0.1
|
|
525
|
+
DB_PORT=5432
|
|
526
|
+
DB_DATABASE=${context.name}
|
|
527
|
+
DB_USERNAME=postgres
|
|
528
|
+
DB_PASSWORD=
|
|
529
|
+
DB_SSLMODE=prefer` : `# DB_HOST=127.0.0.1
|
|
530
|
+
# DB_PORT=5432
|
|
531
|
+
# DB_DATABASE=${context.name}
|
|
532
|
+
# DB_USERNAME=postgres
|
|
533
|
+
# DB_PASSWORD=
|
|
534
|
+
# DB_SSLMODE=prefer`}
|
|
535
|
+
|
|
536
|
+
# MySQL Configuration (when DB_CONNECTION=mysql)
|
|
537
|
+
# DB_HOST=127.0.0.1
|
|
538
|
+
# DB_PORT=3306
|
|
539
|
+
# DB_DATABASE=${context.name}
|
|
540
|
+
# DB_USERNAME=root
|
|
541
|
+
# DB_PASSWORD=
|
|
542
|
+
|
|
543
|
+
# ============================================================================
|
|
544
|
+
# Redis Configuration (@gravito/plasma)
|
|
545
|
+
# ============================================================================
|
|
546
|
+
|
|
547
|
+
# Default Redis Connection
|
|
548
|
+
REDIS_CONNECTION=default
|
|
549
|
+
REDIS_HOST=127.0.0.1
|
|
550
|
+
REDIS_PORT=6379
|
|
551
|
+
REDIS_PASSWORD=
|
|
552
|
+
REDIS_DB=0
|
|
553
|
+
|
|
554
|
+
# Redis Connection Options
|
|
555
|
+
REDIS_CONNECT_TIMEOUT=10000
|
|
556
|
+
REDIS_COMMAND_TIMEOUT=5000
|
|
557
|
+
REDIS_KEY_PREFIX=
|
|
558
|
+
REDIS_MAX_RETRIES=3
|
|
559
|
+
REDIS_RETRY_DELAY=1000
|
|
560
|
+
|
|
561
|
+
# Cache-specific Redis Connection (optional, falls back to default)
|
|
562
|
+
# REDIS_CACHE_HOST=127.0.0.1
|
|
563
|
+
# REDIS_CACHE_PORT=6379
|
|
564
|
+
# REDIS_CACHE_PASSWORD=
|
|
565
|
+
REDIS_CACHE_DB=1
|
|
566
|
+
|
|
567
|
+
# Queue-specific Redis Connection (optional, falls back to default)
|
|
568
|
+
# REDIS_QUEUE_HOST=127.0.0.1
|
|
569
|
+
# REDIS_QUEUE_PORT=6379
|
|
570
|
+
# REDIS_QUEUE_PASSWORD=
|
|
571
|
+
REDIS_QUEUE_DB=2
|
|
572
|
+
|
|
573
|
+
# ============================================================================
|
|
574
|
+
# Cache Configuration (@gravito/stasis)
|
|
575
|
+
# ============================================================================
|
|
576
|
+
|
|
577
|
+
# Cache Driver (memory, file, redis)
|
|
578
|
+
CACHE_DRIVER=${profile === "core" ? "memory" : "redis"}
|
|
579
|
+
|
|
580
|
+
# File Cache Path (when CACHE_DRIVER=file)
|
|
581
|
+
CACHE_PATH=storage/framework/cache
|
|
582
|
+
|
|
583
|
+
# Redis Cache Configuration (when CACHE_DRIVER=redis)
|
|
584
|
+
REDIS_CACHE_CONNECTION=cache
|
|
585
|
+
REDIS_CACHE_PREFIX=cache:
|
|
586
|
+
|
|
587
|
+
# ============================================================================
|
|
588
|
+
# Queue Configuration (@gravito/stream)
|
|
589
|
+
# ============================================================================
|
|
590
|
+
|
|
591
|
+
# Queue Connection (sync, memory, database, redis, kafka, sqs, rabbitmq)
|
|
592
|
+
QUEUE_CONNECTION=${profile === "core" ? "sync" : "redis"}
|
|
593
|
+
|
|
594
|
+
# Database Queue Configuration (when QUEUE_CONNECTION=database)
|
|
595
|
+
QUEUE_TABLE=jobs
|
|
596
|
+
|
|
597
|
+
# Redis Queue Configuration (when QUEUE_CONNECTION=redis)
|
|
598
|
+
REDIS_PREFIX=queue:
|
|
599
|
+
|
|
600
|
+
`;
|
|
601
|
+
if (profile === "enterprise" || profile === "scale") {
|
|
602
|
+
envContent += `# Kafka Queue Configuration (when QUEUE_CONNECTION=kafka)
|
|
603
|
+
# KAFKA_BROKERS=localhost:9092
|
|
604
|
+
# KAFKA_CONSUMER_GROUP_ID=gravito-workers
|
|
605
|
+
# KAFKA_CLIENT_ID=${context.name}
|
|
606
|
+
|
|
607
|
+
# AWS SQS Queue Configuration (when QUEUE_CONNECTION=sqs)
|
|
608
|
+
# AWS_REGION=us-east-1
|
|
609
|
+
# SQS_QUEUE_URL_PREFIX=
|
|
610
|
+
# SQS_VISIBILITY_TIMEOUT=30
|
|
611
|
+
# SQS_WAIT_TIME_SECONDS=20
|
|
612
|
+
|
|
613
|
+
# RabbitMQ Queue Configuration (when QUEUE_CONNECTION=rabbitmq)
|
|
614
|
+
# RABBITMQ_URL=amqp://localhost
|
|
615
|
+
# RABBITMQ_EXCHANGE=gravito.events
|
|
616
|
+
# RABBITMQ_EXCHANGE_TYPE=fanout
|
|
617
|
+
|
|
618
|
+
`;
|
|
619
|
+
}
|
|
620
|
+
envContent += `# ============================================================================
|
|
621
|
+
# Logging Configuration
|
|
622
|
+
# ============================================================================
|
|
286
623
|
|
|
287
|
-
# Logging
|
|
288
624
|
LOG_LEVEL=debug
|
|
289
625
|
`;
|
|
626
|
+
return envContent;
|
|
290
627
|
}
|
|
291
628
|
/**
|
|
292
629
|
* Generate .gitignore content.
|
|
@@ -338,6 +675,9 @@ coverage/
|
|
|
338
675
|
strict: true,
|
|
339
676
|
skipLibCheck: true,
|
|
340
677
|
declaration: true,
|
|
678
|
+
experimentalDecorators: true,
|
|
679
|
+
emitDecoratorMetadata: true,
|
|
680
|
+
types: ["bun-types"],
|
|
341
681
|
outDir: "./dist",
|
|
342
682
|
rootDir: "./src",
|
|
343
683
|
baseUrl: ".",
|
|
@@ -350,6 +690,430 @@ coverage/
|
|
|
350
690
|
};
|
|
351
691
|
return JSON.stringify(config, null, 2);
|
|
352
692
|
}
|
|
693
|
+
/**
|
|
694
|
+
* Generate check scripts for project validation.
|
|
695
|
+
*/
|
|
696
|
+
async generateCheckScripts(context) {
|
|
697
|
+
const scriptsDir = path2.resolve(context.targetDir, "scripts");
|
|
698
|
+
await fs2.mkdir(scriptsDir, { recursive: true });
|
|
699
|
+
await this.writeFile(
|
|
700
|
+
scriptsDir,
|
|
701
|
+
"check-dependencies.ts",
|
|
702
|
+
this.generateCheckDependenciesScript()
|
|
703
|
+
);
|
|
704
|
+
await this.writeFile(scriptsDir, "check.sh", this.generateCheckShellScript());
|
|
705
|
+
await this.writeFile(scriptsDir, "pre-commit.sh", this.generatePreCommitScript());
|
|
706
|
+
await this.writeFile(context.targetDir, "CHECK_SYSTEM.md", this.generateCheckSystemDoc(context));
|
|
707
|
+
}
|
|
708
|
+
/**
|
|
709
|
+
* Generate check-dependencies.ts script content.
|
|
710
|
+
*/
|
|
711
|
+
generateCheckDependenciesScript() {
|
|
712
|
+
return `/**
|
|
713
|
+
* \u76F8\u4F9D\u5957\u4EF6\u7248\u672C\u6AA2\u67E5\u8173\u672C
|
|
714
|
+
*
|
|
715
|
+
* \u6AA2\u67E5 package.json \u4E2D\u7684\u5957\u4EF6\u662F\u5426\u70BA\u6700\u65B0\u7A69\u5B9A\u7248\u672C
|
|
716
|
+
* \u4E26\u63D0\u4F9B\u66F4\u65B0\u5EFA\u8B70
|
|
717
|
+
*/
|
|
718
|
+
|
|
719
|
+
import { readFileSync } from 'fs'
|
|
720
|
+
import { join } from 'path'
|
|
721
|
+
|
|
722
|
+
interface PackageJson {
|
|
723
|
+
dependencies?: Record<string, string>
|
|
724
|
+
devDependencies?: Record<string, string>
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
interface PackageInfo {
|
|
728
|
+
name: string
|
|
729
|
+
current: string
|
|
730
|
+
latest: string
|
|
731
|
+
outdated: boolean
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
const colors = {
|
|
735
|
+
reset: '\\x1b[0m',
|
|
736
|
+
green: '\\x1b[32m',
|
|
737
|
+
yellow: '\\x1b[33m',
|
|
738
|
+
red: '\\x1b[31m',
|
|
739
|
+
blue: '\\x1b[36m',
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
function log(message: string, color: keyof typeof colors = 'reset') {
|
|
743
|
+
console.log(\`\${colors[color]}\${message}\${colors.reset}\`)
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
async function getLatestVersion(packageName: string): Promise<string | null> {
|
|
747
|
+
try {
|
|
748
|
+
const response = await fetch(\`https://registry.npmjs.org/\${packageName}/latest\`)
|
|
749
|
+
if (!response.ok) return null
|
|
750
|
+
const data = await response.json()
|
|
751
|
+
return data.version
|
|
752
|
+
} catch {
|
|
753
|
+
return null
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
function parseVersion(version: string): string {
|
|
758
|
+
// \u79FB\u9664 ^, ~, >= \u7B49\u524D\u7DB4
|
|
759
|
+
return version.replace(/^[\\^~>=<]/, '')
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
async function checkPackage(
|
|
763
|
+
name: string,
|
|
764
|
+
currentVersion: string
|
|
765
|
+
): Promise<PackageInfo | null> {
|
|
766
|
+
// \u8DF3\u904E\u672C\u5730\u9023\u7D50\u7684\u5957\u4EF6
|
|
767
|
+
if (currentVersion.startsWith('link:') || currentVersion.startsWith('workspace:')) {
|
|
768
|
+
return null
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
const current = parseVersion(currentVersion)
|
|
772
|
+
const latest = await getLatestVersion(name)
|
|
773
|
+
|
|
774
|
+
if (!latest) {
|
|
775
|
+
return null
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
return {
|
|
779
|
+
name,
|
|
780
|
+
current,
|
|
781
|
+
latest,
|
|
782
|
+
outdated: current !== latest,
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
async function main() {
|
|
787
|
+
log('\\n=== \u76F8\u4F9D\u5957\u4EF6\u7248\u672C\u6AA2\u67E5 ===\\n', 'blue')
|
|
788
|
+
|
|
789
|
+
const packageJsonPath = join(process.cwd(), 'package.json')
|
|
790
|
+
const packageJson: PackageJson = JSON.parse(
|
|
791
|
+
readFileSync(packageJsonPath, 'utf-8')
|
|
792
|
+
)
|
|
793
|
+
|
|
794
|
+
const allDependencies = {
|
|
795
|
+
...packageJson.dependencies,
|
|
796
|
+
...packageJson.devDependencies,
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
log(\`\u6AA2\u67E5 \${Object.keys(allDependencies).length} \u500B\u5957\u4EF6...\\n\`, 'yellow')
|
|
800
|
+
|
|
801
|
+
const results: PackageInfo[] = []
|
|
802
|
+
const outdated: PackageInfo[] = []
|
|
803
|
+
const upToDate: PackageInfo[] = []
|
|
804
|
+
|
|
805
|
+
// \u6AA2\u67E5\u6240\u6709\u5957\u4EF6
|
|
806
|
+
for (const [name, version] of Object.entries(allDependencies)) {
|
|
807
|
+
const info = await checkPackage(name, version)
|
|
808
|
+
if (info) {
|
|
809
|
+
results.push(info)
|
|
810
|
+
if (info.outdated) {
|
|
811
|
+
outdated.push(info)
|
|
812
|
+
} else {
|
|
813
|
+
upToDate.push(info)
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
// \u986F\u793A\u7D50\u679C
|
|
819
|
+
if (upToDate.length > 0) {
|
|
820
|
+
log(\`\\n\u2713 \u5DF2\u662F\u6700\u65B0\u7248\u672C (\${upToDate.length}):\`, 'green')
|
|
821
|
+
upToDate.forEach((pkg) => {
|
|
822
|
+
log(\` \${pkg.name}: \${pkg.current}\`, 'green')
|
|
823
|
+
})
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
if (outdated.length > 0) {
|
|
827
|
+
log(\`\\n\u26A0 \u9700\u8981\u66F4\u65B0 (\${outdated.length}):\`, 'yellow')
|
|
828
|
+
outdated.forEach((pkg) => {
|
|
829
|
+
log(\` \${pkg.name}: \${pkg.current} \u2192 \${pkg.latest}\`, 'yellow')
|
|
830
|
+
})
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
// \u7E3D\u7D50
|
|
834
|
+
log('\\n=== \u6AA2\u67E5\u7D50\u679C ===', 'blue')
|
|
835
|
+
log(\`\u7E3D\u8A08: \${results.length} \u500B\u5957\u4EF6\`, 'blue')
|
|
836
|
+
log(\`\u6700\u65B0: \${upToDate.length} \u500B\`, 'green')
|
|
837
|
+
log(\`\u9700\u66F4\u65B0: \${outdated.length} \u500B\`, outdated.length > 0 ? 'yellow' : 'green')
|
|
838
|
+
|
|
839
|
+
// \u5982\u679C\u6709\u9700\u8981\u66F4\u65B0\u7684\u5957\u4EF6\uFF0C\u8FD4\u56DE\u975E\u96F6\u9000\u51FA\u78BC
|
|
840
|
+
if (outdated.length > 0) {
|
|
841
|
+
log('\\n\u5EFA\u8B70\u57F7\u884C\u4EE5\u4E0B\u547D\u4EE4\u66F4\u65B0\u5957\u4EF6\uFF1A', 'yellow')
|
|
842
|
+
log(' bun update', 'yellow')
|
|
843
|
+
process.exit(1)
|
|
844
|
+
} else {
|
|
845
|
+
log('\\n\u2713 \u6240\u6709\u5957\u4EF6\u90FD\u662F\u6700\u65B0\u7248\u672C\uFF01', 'green')
|
|
846
|
+
process.exit(0)
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
main().catch((error) => {
|
|
851
|
+
log(\`\\n\u932F\u8AA4: \${error.message}\`, 'red')
|
|
852
|
+
process.exit(1)
|
|
853
|
+
})
|
|
854
|
+
`;
|
|
855
|
+
}
|
|
856
|
+
/**
|
|
857
|
+
* Generate check.sh script content.
|
|
858
|
+
*/
|
|
859
|
+
generateCheckShellScript() {
|
|
860
|
+
return `#!/bin/bash
|
|
861
|
+
|
|
862
|
+
# \u5C08\u6848\u6AA2\u67E5\u8173\u672C
|
|
863
|
+
# \u57F7\u884C\u6240\u6709\u5FC5\u8981\u7684\u6AA2\u67E5\uFF1A\u985E\u578B\u6AA2\u67E5\u3001\u6E2C\u8A66\u3001\u4F9D\u8CF4\u6AA2\u67E5\u7B49
|
|
864
|
+
|
|
865
|
+
set -e
|
|
866
|
+
|
|
867
|
+
# \u984F\u8272\u5B9A\u7FA9
|
|
868
|
+
GREEN='\\033[0;32m'
|
|
869
|
+
YELLOW='\\033[1;33m'
|
|
870
|
+
RED='\\033[0;31m'
|
|
871
|
+
BLUE='\\033[0;34m'
|
|
872
|
+
NC='\\033[0m' # No Color
|
|
873
|
+
|
|
874
|
+
echo -e "\${BLUE}=== \u5C08\u6848\u6AA2\u67E5 ===\${NC}\\n"
|
|
875
|
+
|
|
876
|
+
# \u6AA2\u67E5\u662F\u5426\u5728\u6B63\u78BA\u7684\u76EE\u9304
|
|
877
|
+
if [ ! -f "package.json" ]; then
|
|
878
|
+
echo -e "\${RED}\u932F\u8AA4: \u8ACB\u5728\u5C08\u6848\u6839\u76EE\u9304\u57F7\u884C\u6B64\u8173\u672C\${NC}"
|
|
879
|
+
exit 1
|
|
880
|
+
fi
|
|
881
|
+
|
|
882
|
+
# \u6AA2\u67E5 Bun \u662F\u5426\u5B89\u88DD
|
|
883
|
+
if ! command -v bun &> /dev/null; then
|
|
884
|
+
echo -e "\${RED}\u932F\u8AA4: \u672A\u627E\u5230 bun\uFF0C\u8ACB\u5148\u5B89\u88DD Bun\${NC}"
|
|
885
|
+
exit 1
|
|
886
|
+
fi
|
|
887
|
+
|
|
888
|
+
# 1. \u985E\u578B\u6AA2\u67E5
|
|
889
|
+
echo -e "\${YELLOW}[1/3] \u57F7\u884C\u985E\u578B\u6AA2\u67E5...\${NC}"
|
|
890
|
+
if bun run typecheck; then
|
|
891
|
+
echo -e "\${GREEN}\u2713 \u985E\u578B\u6AA2\u67E5\u901A\u904E\${NC}\\n"
|
|
892
|
+
else
|
|
893
|
+
echo -e "\${RED}\u2717 \u985E\u578B\u6AA2\u67E5\u5931\u6557\${NC}"
|
|
894
|
+
exit 1
|
|
895
|
+
fi
|
|
896
|
+
|
|
897
|
+
# 2. \u57F7\u884C\u6E2C\u8A66
|
|
898
|
+
echo -e "\${YELLOW}[2/3] \u57F7\u884C\u6E2C\u8A66...\${NC}"
|
|
899
|
+
if bun test; then
|
|
900
|
+
echo -e "\${GREEN}\u2713 \u6E2C\u8A66\u901A\u904E\${NC}\\n"
|
|
901
|
+
else
|
|
902
|
+
echo -e "\${RED}\u2717 \u6E2C\u8A66\u5931\u6557\${NC}"
|
|
903
|
+
exit 1
|
|
904
|
+
fi
|
|
905
|
+
|
|
906
|
+
# 3. \u6AA2\u67E5\u4F9D\u8CF4\u7248\u672C\uFF08\u53EF\u9078\uFF0C\u56E0\u70BA\u9700\u8981\u7DB2\u8DEF\u9023\u7DDA\uFF09
|
|
907
|
+
echo -e "\${YELLOW}[3/3] \u6AA2\u67E5\u4F9D\u8CF4\u7248\u672C...\${NC}"
|
|
908
|
+
if bun run check:deps; then
|
|
909
|
+
echo -e "\${GREEN}\u2713 \u4F9D\u8CF4\u6AA2\u67E5\u5B8C\u6210\${NC}\\n"
|
|
910
|
+
else
|
|
911
|
+
echo -e "\${YELLOW}\u26A0 \u4F9D\u8CF4\u6AA2\u67E5\u6709\u8B66\u544A\uFF08\u67D0\u4E9B\u5957\u4EF6\u53EF\u80FD\u9700\u8981\u66F4\u65B0\uFF09\${NC}\\n"
|
|
912
|
+
fi
|
|
913
|
+
|
|
914
|
+
echo -e "\${GREEN}=== \u6240\u6709\u6AA2\u67E5\u5B8C\u6210 ===\${NC}"
|
|
915
|
+
`;
|
|
916
|
+
}
|
|
917
|
+
/**
|
|
918
|
+
* Generate pre-commit.sh script content.
|
|
919
|
+
*/
|
|
920
|
+
generatePreCommitScript() {
|
|
921
|
+
return `#!/bin/bash
|
|
922
|
+
|
|
923
|
+
# Pre-commit Hook
|
|
924
|
+
# \u5728 git commit \u524D\u81EA\u52D5\u57F7\u884C\u6AA2\u67E5
|
|
925
|
+
#
|
|
926
|
+
# \u5B89\u88DD\u65B9\u5F0F\uFF1A
|
|
927
|
+
# ln -s ../../scripts/pre-commit.sh .git/hooks/pre-commit
|
|
928
|
+
# \u6216
|
|
929
|
+
# cp scripts/pre-commit.sh .git/hooks/pre-commit
|
|
930
|
+
# chmod +x .git/hooks/pre-commit
|
|
931
|
+
|
|
932
|
+
set -e
|
|
933
|
+
|
|
934
|
+
# \u984F\u8272\u5B9A\u7FA9
|
|
935
|
+
GREEN='\\033[0;32m'
|
|
936
|
+
YELLOW='\\033[1;33m'
|
|
937
|
+
RED='\\033[0;31m'
|
|
938
|
+
BLUE='\\033[0;34m'
|
|
939
|
+
NC='\\033[0m' # No Color
|
|
940
|
+
|
|
941
|
+
echo -e "\${BLUE}=== Pre-commit \u6AA2\u67E5 ===\${NC}\\n"
|
|
942
|
+
|
|
943
|
+
# \u5207\u63DB\u5230\u5C08\u6848\u6839\u76EE\u9304
|
|
944
|
+
cd "$(git rev-parse --show-toplevel)"
|
|
945
|
+
|
|
946
|
+
# \u6AA2\u67E5\u662F\u5426\u5728\u6B63\u78BA\u7684\u76EE\u9304
|
|
947
|
+
if [ ! -f "package.json" ]; then
|
|
948
|
+
echo -e "\${RED}\u932F\u8AA4: \u627E\u4E0D\u5230 package.json\${NC}"
|
|
949
|
+
exit 1
|
|
950
|
+
fi
|
|
951
|
+
|
|
952
|
+
# \u6AA2\u67E5 Bun \u662F\u5426\u5B89\u88DD
|
|
953
|
+
if ! command -v bun &> /dev/null; then
|
|
954
|
+
echo -e "\${RED}\u932F\u8AA4: \u672A\u627E\u5230 bun\uFF0C\u8ACB\u5148\u5B89\u88DD Bun\${NC}"
|
|
955
|
+
exit 1
|
|
956
|
+
fi
|
|
957
|
+
|
|
958
|
+
# 1. \u985E\u578B\u6AA2\u67E5\uFF08\u5FEB\u901F\u6AA2\u67E5\uFF09
|
|
959
|
+
echo -e "\${YELLOW}[1/2] \u57F7\u884C\u985E\u578B\u6AA2\u67E5...\${NC}"
|
|
960
|
+
if bun run typecheck; then
|
|
961
|
+
echo -e "\${GREEN}\u2713 \u985E\u578B\u6AA2\u67E5\u901A\u904E\${NC}\\n"
|
|
962
|
+
else
|
|
963
|
+
echo -e "\${RED}\u2717 \u985E\u578B\u6AA2\u67E5\u5931\u6557\${NC}"
|
|
964
|
+
echo -e "\${YELLOW}\u63D0\u793A: \u8ACB\u4FEE\u6B63\u985E\u578B\u932F\u8AA4\u5F8C\u518D\u63D0\u4EA4\${NC}"
|
|
965
|
+
exit 1
|
|
966
|
+
fi
|
|
967
|
+
|
|
968
|
+
# 2. \u57F7\u884C\u6E2C\u8A66\uFF08\u53EF\u9078\uFF0C\u5982\u679C\u6E2C\u8A66\u6642\u9593\u8F03\u9577\u53EF\u4EE5\u8A3B\u89E3\u6389\uFF09
|
|
969
|
+
echo -e "\${YELLOW}[2/2] \u57F7\u884C\u6E2C\u8A66...\${NC}"
|
|
970
|
+
if bun test; then
|
|
971
|
+
echo -e "\${GREEN}\u2713 \u6E2C\u8A66\u901A\u904E\${NC}\\n"
|
|
972
|
+
else
|
|
973
|
+
echo -e "\${RED}\u2717 \u6E2C\u8A66\u5931\u6557\${NC}"
|
|
974
|
+
echo -e "\${YELLOW}\u63D0\u793A: \u8ACB\u4FEE\u6B63\u6E2C\u8A66\u932F\u8AA4\u5F8C\u518D\u63D0\u4EA4\${NC}"
|
|
975
|
+
exit 1
|
|
976
|
+
fi
|
|
977
|
+
|
|
978
|
+
echo -e "\${GREEN}=== Pre-commit \u6AA2\u67E5\u901A\u904E ===\${NC}\\n"
|
|
979
|
+
`;
|
|
980
|
+
}
|
|
981
|
+
/**
|
|
982
|
+
* Generate CHECK_SYSTEM.md documentation.
|
|
983
|
+
*/
|
|
984
|
+
generateCheckSystemDoc(context) {
|
|
985
|
+
return `# \u5C08\u6848\u6AA2\u67E5\u7CFB\u7D71
|
|
986
|
+
|
|
987
|
+
\u672C\u5C08\u6848\u5DF2\u5EFA\u7ACB\u5B8C\u6574\u7684\u672C\u5730\u6AA2\u67E5\u6A5F\u5236\uFF0C\u7121\u9700\u4F9D\u8CF4 GitHub CI\u3002
|
|
988
|
+
|
|
989
|
+
## \u5FEB\u901F\u958B\u59CB
|
|
990
|
+
|
|
991
|
+
### \u57F7\u884C\u5B8C\u6574\u6AA2\u67E5
|
|
992
|
+
\`\`\`bash
|
|
993
|
+
bun run validate
|
|
994
|
+
\`\`\`
|
|
995
|
+
|
|
996
|
+
### \u57F7\u884C\u55AE\u9805\u6AA2\u67E5
|
|
997
|
+
\`\`\`bash
|
|
998
|
+
# \u985E\u578B\u6AA2\u67E5
|
|
999
|
+
bun run typecheck
|
|
1000
|
+
|
|
1001
|
+
# \u6E2C\u8A66
|
|
1002
|
+
bun run test
|
|
1003
|
+
|
|
1004
|
+
# \u4F9D\u8CF4\u7248\u672C\u6AA2\u67E5
|
|
1005
|
+
bun run check:deps
|
|
1006
|
+
\`\`\`
|
|
1007
|
+
|
|
1008
|
+
## \u53EF\u7528\u547D\u4EE4
|
|
1009
|
+
|
|
1010
|
+
### Package.json \u8173\u672C
|
|
1011
|
+
|
|
1012
|
+
| \u547D\u4EE4 | \u8AAA\u660E |
|
|
1013
|
+
|------|------|
|
|
1014
|
+
| \`bun run typecheck\` | TypeScript \u985E\u578B\u6AA2\u67E5 |
|
|
1015
|
+
| \`bun run test\` | \u57F7\u884C\u6240\u6709\u6E2C\u8A66 |
|
|
1016
|
+
| \`bun run check\` | \u985E\u578B\u6AA2\u67E5 + \u6E2C\u8A66 |
|
|
1017
|
+
| \`bun run check:deps\` | \u6AA2\u67E5\u4F9D\u8CF4\u7248\u672C |
|
|
1018
|
+
| \`bun run validate\` | \u5B8C\u6574\u9A57\u8B49\uFF08\u985E\u578B + \u6E2C\u8A66 + \u4F9D\u8CF4\uFF09 |
|
|
1019
|
+
| \`bun run precommit\` | \u7B49\u540C\u65BC \`validate\` |
|
|
1020
|
+
|
|
1021
|
+
### Shell \u8173\u672C
|
|
1022
|
+
|
|
1023
|
+
| \u8173\u672C | \u8AAA\u660E |
|
|
1024
|
+
|------|------|
|
|
1025
|
+
| \`./scripts/check.sh\` | \u5B8C\u6574\u5C08\u6848\u6AA2\u67E5\uFF08Shell \u7248\u672C\uFF09 |
|
|
1026
|
+
| \`./scripts/pre-commit.sh\` | Pre-commit hook \u8173\u672C |
|
|
1027
|
+
|
|
1028
|
+
## Pre-commit Hook\uFF08\u63A8\u85A6\uFF09
|
|
1029
|
+
|
|
1030
|
+
\u5B89\u88DD pre-commit hook \u5F8C\uFF0C\u6BCF\u6B21 \`git commit\` \u524D\u6703\u81EA\u52D5\u57F7\u884C\u6AA2\u67E5\uFF1A
|
|
1031
|
+
|
|
1032
|
+
\`\`\`bash
|
|
1033
|
+
# \u5B89\u88DD pre-commit hook
|
|
1034
|
+
ln -s ../../scripts/pre-commit.sh .git/hooks/pre-commit
|
|
1035
|
+
|
|
1036
|
+
# \u6216\u4F7F\u7528\u8907\u88FD\u65B9\u5F0F
|
|
1037
|
+
cp scripts/pre-commit.sh .git/hooks/pre-commit
|
|
1038
|
+
chmod +x .git/hooks/pre-commit
|
|
1039
|
+
\`\`\`
|
|
1040
|
+
|
|
1041
|
+
**\u529F\u80FD\uFF1A**
|
|
1042
|
+
- \u2705 \u81EA\u52D5\u57F7\u884C\u985E\u578B\u6AA2\u67E5
|
|
1043
|
+
- \u2705 \u81EA\u52D5\u57F7\u884C\u6E2C\u8A66
|
|
1044
|
+
- \u274C \u6AA2\u67E5\u5931\u6557\u6642\u963B\u6B62\u63D0\u4EA4
|
|
1045
|
+
|
|
1046
|
+
**\u8DF3\u904E\u6AA2\u67E5\uFF08\u4E0D\u63A8\u85A6\uFF09\uFF1A**
|
|
1047
|
+
\`\`\`bash
|
|
1048
|
+
git commit --no-verify -m "\u7DCA\u6025\u4FEE\u5FA9"
|
|
1049
|
+
\`\`\`
|
|
1050
|
+
|
|
1051
|
+
## \u6AA2\u67E5\u9805\u76EE
|
|
1052
|
+
|
|
1053
|
+
### 1. \u985E\u578B\u6AA2\u67E5
|
|
1054
|
+
- \u4F7F\u7528 \`tsc --noEmit\` \u6AA2\u67E5 TypeScript \u985E\u578B
|
|
1055
|
+
- \u78BA\u4FDD\u6C92\u6709\u985E\u578B\u932F\u8AA4
|
|
1056
|
+
|
|
1057
|
+
### 2. \u6E2C\u8A66
|
|
1058
|
+
- \u57F7\u884C\u6240\u6709\u55AE\u5143\u6E2C\u8A66\u548C\u6574\u5408\u6E2C\u8A66
|
|
1059
|
+
- \u78BA\u4FDD\u6E2C\u8A66\u901A\u904E
|
|
1060
|
+
|
|
1061
|
+
### 3. \u4F9D\u8CF4\u6AA2\u67E5\uFF08\u53EF\u9078\uFF09
|
|
1062
|
+
- \u6AA2\u67E5\u5957\u4EF6\u7248\u672C\u662F\u5426\u70BA\u6700\u65B0
|
|
1063
|
+
- \u63D0\u4F9B\u66F4\u65B0\u5EFA\u8B70
|
|
1064
|
+
- \u9700\u8981\u7DB2\u8DEF\u9023\u7DDA
|
|
1065
|
+
|
|
1066
|
+
## \u5DE5\u4F5C\u6D41\u7A0B\u5EFA\u8B70
|
|
1067
|
+
|
|
1068
|
+
### \u958B\u767C\u6642
|
|
1069
|
+
1. \u958B\u767C\u529F\u80FD
|
|
1070
|
+
2. \u63D0\u4EA4\u524D\u57F7\u884C \`bun run validate\`
|
|
1071
|
+
3. \u4FEE\u6B63\u554F\u984C
|
|
1072
|
+
4. \u63D0\u4EA4\u7A0B\u5F0F\u78BC
|
|
1073
|
+
|
|
1074
|
+
### \u4F7F\u7528 Pre-commit Hook\uFF08\u63A8\u85A6\uFF09
|
|
1075
|
+
1. \u5B89\u88DD pre-commit hook\uFF08\u53EA\u9700\u4E00\u6B21\uFF09
|
|
1076
|
+
2. \u6B63\u5E38\u958B\u767C\u548C\u63D0\u4EA4
|
|
1077
|
+
3. \u6AA2\u67E5\u6703\u81EA\u52D5\u57F7\u884C
|
|
1078
|
+
4. \u5982\u6709\u554F\u984C\uFF0C\u4FEE\u6B63\u5F8C\u91CD\u65B0\u63D0\u4EA4
|
|
1079
|
+
|
|
1080
|
+
## \u6A94\u6848\u7D50\u69CB
|
|
1081
|
+
|
|
1082
|
+
\`\`\`
|
|
1083
|
+
${context.nameKebabCase}/
|
|
1084
|
+
\u251C\u2500\u2500 package.json # \u6AA2\u67E5\u8173\u672C\u5B9A\u7FA9
|
|
1085
|
+
\u251C\u2500\u2500 scripts/
|
|
1086
|
+
\u2502 \u251C\u2500\u2500 check.sh # \u5B8C\u6574\u6AA2\u67E5\u8173\u672C\uFF08Shell\uFF09
|
|
1087
|
+
\u2502 \u251C\u2500\u2500 check-dependencies.ts # \u4F9D\u8CF4\u7248\u672C\u6AA2\u67E5
|
|
1088
|
+
\u2502 \u2514\u2500\u2500 pre-commit.sh # Pre-commit hook
|
|
1089
|
+
\u2514\u2500\u2500 CHECK_SYSTEM.md # \u672C\u6587\u4EF6
|
|
1090
|
+
\`\`\`
|
|
1091
|
+
|
|
1092
|
+
## \u6CE8\u610F\u4E8B\u9805
|
|
1093
|
+
|
|
1094
|
+
1. **\u4F9D\u8CF4\u6AA2\u67E5\u9700\u8981\u7DB2\u8DEF\u9023\u7DDA**\uFF1A\`check:deps\` \u9700\u8981\u9023\u63A5\u5230 npm registry
|
|
1095
|
+
2. **\u6E2C\u8A66\u6642\u9593**\uFF1A\u5982\u679C\u6E2C\u8A66\u6642\u9593\u8F03\u9577\uFF0C\u53EF\u4EE5\u7DE8\u8F2F \`pre-commit.sh\` \u8A3B\u89E3\u6389\u6E2C\u8A66\u90E8\u5206
|
|
1096
|
+
3. **\u985E\u578B\u932F\u8AA4**\uFF1A\u5C08\u6848\u4E2D\u53EF\u80FD\u9084\u6709\u4E00\u4E9B\u65E2\u6709\u7684\u985E\u578B\u932F\u8AA4\uFF0C\u5EFA\u8B70\u9010\u6B65\u4FEE\u6B63
|
|
1097
|
+
|
|
1098
|
+
## \u6545\u969C\u6392\u9664
|
|
1099
|
+
|
|
1100
|
+
### \u6AA2\u67E5\u5931\u6557
|
|
1101
|
+
1. \u67E5\u770B\u932F\u8AA4\u8A0A\u606F
|
|
1102
|
+
2. \u4FEE\u6B63\u554F\u984C
|
|
1103
|
+
3. \u91CD\u65B0\u57F7\u884C\u6AA2\u67E5
|
|
1104
|
+
|
|
1105
|
+
### \u8DF3\u904E\u6AA2\u67E5
|
|
1106
|
+
\u53EA\u6709\u5728\u7DCA\u6025\u60C5\u6CC1\u4E0B\u624D\u4F7F\u7528\uFF1A
|
|
1107
|
+
\`\`\`bash
|
|
1108
|
+
git commit --no-verify
|
|
1109
|
+
\`\`\`
|
|
1110
|
+
|
|
1111
|
+
### \u79FB\u9664 Pre-commit Hook
|
|
1112
|
+
\`\`\`bash
|
|
1113
|
+
rm .git/hooks/pre-commit
|
|
1114
|
+
\`\`\`
|
|
1115
|
+
`;
|
|
1116
|
+
}
|
|
353
1117
|
/**
|
|
354
1118
|
* Log a message if verbose mode is enabled.
|
|
355
1119
|
*/
|
|
@@ -407,6 +1171,11 @@ var CleanArchitectureGenerator = class extends BaseGenerator {
|
|
|
407
1171
|
{ type: "file", name: "logging.ts", content: this.generateLoggingConfig() }
|
|
408
1172
|
]
|
|
409
1173
|
},
|
|
1174
|
+
{
|
|
1175
|
+
type: "directory",
|
|
1176
|
+
name: "database",
|
|
1177
|
+
children: [{ type: "file", name: ".gitkeep", content: "" }]
|
|
1178
|
+
},
|
|
410
1179
|
{
|
|
411
1180
|
type: "directory",
|
|
412
1181
|
name: "src",
|
|
@@ -419,16 +1188,12 @@ var CleanArchitectureGenerator = class extends BaseGenerator {
|
|
|
419
1188
|
{
|
|
420
1189
|
type: "directory",
|
|
421
1190
|
name: "Entities",
|
|
422
|
-
children: [
|
|
423
|
-
{ type: "file", name: "Entity.ts", content: this.generateBaseEntity() },
|
|
424
|
-
{ type: "file", name: "User.ts", content: this.generateUserEntity() }
|
|
425
|
-
]
|
|
1191
|
+
children: [{ type: "file", name: "User.ts", content: this.generateUserEntity() }]
|
|
426
1192
|
},
|
|
427
1193
|
{
|
|
428
1194
|
type: "directory",
|
|
429
1195
|
name: "ValueObjects",
|
|
430
1196
|
children: [
|
|
431
|
-
{ type: "file", name: "ValueObject.ts", content: this.generateBaseValueObject() },
|
|
432
1197
|
{ type: "file", name: "Email.ts", content: this.generateEmailValueObject() }
|
|
433
1198
|
]
|
|
434
1199
|
},
|
|
@@ -446,13 +1211,7 @@ var CleanArchitectureGenerator = class extends BaseGenerator {
|
|
|
446
1211
|
{
|
|
447
1212
|
type: "directory",
|
|
448
1213
|
name: "Exceptions",
|
|
449
|
-
children: [
|
|
450
|
-
{
|
|
451
|
-
type: "file",
|
|
452
|
-
name: "DomainException.ts",
|
|
453
|
-
content: this.generateDomainException()
|
|
454
|
-
}
|
|
455
|
-
]
|
|
1214
|
+
children: [{ type: "file", name: ".gitkeep", content: "" }]
|
|
456
1215
|
}
|
|
457
1216
|
]
|
|
458
1217
|
},
|
|
@@ -535,6 +1294,11 @@ var CleanArchitectureGenerator = class extends BaseGenerator {
|
|
|
535
1294
|
type: "directory",
|
|
536
1295
|
name: "Providers",
|
|
537
1296
|
children: [
|
|
1297
|
+
{
|
|
1298
|
+
type: "file",
|
|
1299
|
+
name: "index.ts",
|
|
1300
|
+
content: this.generateProvidersIndex()
|
|
1301
|
+
},
|
|
538
1302
|
{
|
|
539
1303
|
type: "file",
|
|
540
1304
|
name: "AppServiceProvider.ts",
|
|
@@ -544,6 +1308,16 @@ var CleanArchitectureGenerator = class extends BaseGenerator {
|
|
|
544
1308
|
type: "file",
|
|
545
1309
|
name: "RepositoryServiceProvider.ts",
|
|
546
1310
|
content: this.generateRepositoryServiceProvider()
|
|
1311
|
+
},
|
|
1312
|
+
{
|
|
1313
|
+
type: "file",
|
|
1314
|
+
name: "MiddlewareProvider.ts",
|
|
1315
|
+
content: this.generateMiddlewareProvider()
|
|
1316
|
+
},
|
|
1317
|
+
{
|
|
1318
|
+
type: "file",
|
|
1319
|
+
name: "RouteProvider.ts",
|
|
1320
|
+
content: this.generateRouteProvider()
|
|
547
1321
|
}
|
|
548
1322
|
]
|
|
549
1323
|
}
|
|
@@ -673,42 +1447,6 @@ var CleanArchitectureGenerator = class extends BaseGenerator {
|
|
|
673
1447
|
console: { driver: 'console', level: process.env.LOG_LEVEL ?? 'debug' },
|
|
674
1448
|
},
|
|
675
1449
|
}
|
|
676
|
-
`;
|
|
677
|
-
}
|
|
678
|
-
// ─────────────────────────────────────────────────────────────
|
|
679
|
-
// Domain Layer
|
|
680
|
-
// ─────────────────────────────────────────────────────────────
|
|
681
|
-
generateBaseEntity() {
|
|
682
|
-
return `/**
|
|
683
|
-
* Base Entity
|
|
684
|
-
*
|
|
685
|
-
* All domain entities extend this base class.
|
|
686
|
-
* Entities have identity and lifecycle.
|
|
687
|
-
*
|
|
688
|
-
* IMPORTANT: This layer must have NO external dependencies.
|
|
689
|
-
*/
|
|
690
|
-
|
|
691
|
-
export abstract class Entity<T> {
|
|
692
|
-
protected readonly _id: T
|
|
693
|
-
|
|
694
|
-
constructor(id: T) {
|
|
695
|
-
this._id = id
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
get id(): T {
|
|
699
|
-
return this._id
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
equals(other: Entity<T>): boolean {
|
|
703
|
-
if (other === null || other === undefined) {
|
|
704
|
-
return false
|
|
705
|
-
}
|
|
706
|
-
if (!(other instanceof Entity)) {
|
|
707
|
-
return false
|
|
708
|
-
}
|
|
709
|
-
return this._id === other._id
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
1450
|
`;
|
|
713
1451
|
}
|
|
714
1452
|
generateUserEntity() {
|
|
@@ -719,7 +1457,7 @@ export abstract class Entity<T> {
|
|
|
719
1457
|
* Contains business logic related to users.
|
|
720
1458
|
*/
|
|
721
1459
|
|
|
722
|
-
import { Entity } from '
|
|
1460
|
+
import { Entity } from '@gravito/enterprise'
|
|
723
1461
|
import { Email } from '../ValueObjects/Email'
|
|
724
1462
|
|
|
725
1463
|
export interface UserProps {
|
|
@@ -769,30 +1507,6 @@ export class User extends Entity<string> {
|
|
|
769
1507
|
this.props.updatedAt = new Date()
|
|
770
1508
|
}
|
|
771
1509
|
}
|
|
772
|
-
`;
|
|
773
|
-
}
|
|
774
|
-
generateBaseValueObject() {
|
|
775
|
-
return `/**
|
|
776
|
-
* Base Value Object
|
|
777
|
-
*
|
|
778
|
-
* Value objects are immutable and compared by value.
|
|
779
|
-
* They have no identity of their own.
|
|
780
|
-
*/
|
|
781
|
-
|
|
782
|
-
export abstract class ValueObject<T> {
|
|
783
|
-
protected readonly props: T
|
|
784
|
-
|
|
785
|
-
constructor(props: T) {
|
|
786
|
-
this.props = Object.freeze(props)
|
|
787
|
-
}
|
|
788
|
-
|
|
789
|
-
equals(other: ValueObject<T>): boolean {
|
|
790
|
-
if (other === null || other === undefined) {
|
|
791
|
-
return false
|
|
792
|
-
}
|
|
793
|
-
return JSON.stringify(this.props) === JSON.stringify(other.props)
|
|
794
|
-
}
|
|
795
|
-
}
|
|
796
1510
|
`;
|
|
797
1511
|
}
|
|
798
1512
|
generateEmailValueObject() {
|
|
@@ -802,7 +1516,7 @@ export abstract class ValueObject<T> {
|
|
|
802
1516
|
* Encapsulates email validation and comparison.
|
|
803
1517
|
*/
|
|
804
1518
|
|
|
805
|
-
import { ValueObject } from '
|
|
1519
|
+
import { ValueObject } from '@gravito/enterprise'
|
|
806
1520
|
|
|
807
1521
|
interface EmailProps {
|
|
808
1522
|
value: string
|
|
@@ -843,43 +1557,11 @@ export class Email extends ValueObject<EmailProps> {
|
|
|
843
1557
|
* Implementations are in Infrastructure layer.
|
|
844
1558
|
*/
|
|
845
1559
|
|
|
1560
|
+
import type { Repository } from '@gravito/enterprise'
|
|
846
1561
|
import type { User } from '../Entities/User'
|
|
847
1562
|
|
|
848
|
-
export interface IUserRepository {
|
|
849
|
-
findById(id: string): Promise<User | null>
|
|
1563
|
+
export interface IUserRepository extends Repository<User, string> {
|
|
850
1564
|
findByEmail(email: string): Promise<User | null>
|
|
851
|
-
save(user: User): Promise<void>
|
|
852
|
-
delete(id: string): Promise<void>
|
|
853
|
-
findAll(): Promise<User[]>
|
|
854
|
-
}
|
|
855
|
-
`;
|
|
856
|
-
}
|
|
857
|
-
generateDomainException() {
|
|
858
|
-
return `/**
|
|
859
|
-
* Domain Exception
|
|
860
|
-
*
|
|
861
|
-
* Base exception for domain-level errors.
|
|
862
|
-
*/
|
|
863
|
-
|
|
864
|
-
export class DomainException extends Error {
|
|
865
|
-
constructor(message: string) {
|
|
866
|
-
super(message)
|
|
867
|
-
this.name = 'DomainException'
|
|
868
|
-
}
|
|
869
|
-
}
|
|
870
|
-
|
|
871
|
-
export class EntityNotFoundException extends DomainException {
|
|
872
|
-
constructor(entity: string, id: string) {
|
|
873
|
-
super(\`\${entity} with id \${id} not found\`)
|
|
874
|
-
this.name = 'EntityNotFoundException'
|
|
875
|
-
}
|
|
876
|
-
}
|
|
877
|
-
|
|
878
|
-
export class InvalidValueException extends DomainException {
|
|
879
|
-
constructor(message: string) {
|
|
880
|
-
super(message)
|
|
881
|
-
this.name = 'InvalidValueException'
|
|
882
|
-
}
|
|
883
1565
|
}
|
|
884
1566
|
`;
|
|
885
1567
|
}
|
|
@@ -893,6 +1575,7 @@ export class InvalidValueException extends DomainException {
|
|
|
893
1575
|
* Application service for creating new users.
|
|
894
1576
|
*/
|
|
895
1577
|
|
|
1578
|
+
import { UseCase } from '@gravito/enterprise'
|
|
896
1579
|
import { User } from '../../../Domain/Entities/User'
|
|
897
1580
|
import type { IUserRepository } from '../../../Domain/Interfaces/IUserRepository'
|
|
898
1581
|
import type { UserDTO } from '../../DTOs/UserDTO'
|
|
@@ -906,8 +1589,10 @@ export interface CreateUserOutput {
|
|
|
906
1589
|
user: UserDTO
|
|
907
1590
|
}
|
|
908
1591
|
|
|
909
|
-
export class CreateUserUseCase {
|
|
910
|
-
constructor(private userRepository: IUserRepository) {
|
|
1592
|
+
export class CreateUserUseCase extends UseCase<CreateUserInput, CreateUserOutput> {
|
|
1593
|
+
constructor(private userRepository: IUserRepository) {
|
|
1594
|
+
super()
|
|
1595
|
+
}
|
|
911
1596
|
|
|
912
1597
|
async execute(input: CreateUserInput): Promise<CreateUserOutput> {
|
|
913
1598
|
// Check if email already exists
|
|
@@ -944,8 +1629,8 @@ export class CreateUserUseCase {
|
|
|
944
1629
|
* Get User Use Case
|
|
945
1630
|
*/
|
|
946
1631
|
|
|
1632
|
+
import { UseCase } from '@gravito/enterprise'
|
|
947
1633
|
import type { IUserRepository } from '../../../Domain/Interfaces/IUserRepository'
|
|
948
|
-
import { EntityNotFoundException } from '../../../Domain/Exceptions/DomainException'
|
|
949
1634
|
import type { UserDTO } from '../../DTOs/UserDTO'
|
|
950
1635
|
|
|
951
1636
|
export interface GetUserInput {
|
|
@@ -956,14 +1641,16 @@ export interface GetUserOutput {
|
|
|
956
1641
|
user: UserDTO
|
|
957
1642
|
}
|
|
958
1643
|
|
|
959
|
-
export class GetUserUseCase {
|
|
960
|
-
constructor(private userRepository: IUserRepository) {
|
|
1644
|
+
export class GetUserUseCase extends UseCase<GetUserInput, GetUserOutput> {
|
|
1645
|
+
constructor(private userRepository: IUserRepository) {
|
|
1646
|
+
super()
|
|
1647
|
+
}
|
|
961
1648
|
|
|
962
1649
|
async execute(input: GetUserInput): Promise<GetUserOutput> {
|
|
963
1650
|
const user = await this.userRepository.findById(input.id)
|
|
964
1651
|
|
|
965
1652
|
if (!user) {
|
|
966
|
-
throw new
|
|
1653
|
+
throw new Error(\`User with id \${input.id} not found\`)
|
|
967
1654
|
}
|
|
968
1655
|
|
|
969
1656
|
return {
|
|
@@ -1064,6 +1751,10 @@ export class UserRepository implements IUserRepository {
|
|
|
1064
1751
|
async findAll(): Promise<User[]> {
|
|
1065
1752
|
return Array.from(users.values())
|
|
1066
1753
|
}
|
|
1754
|
+
|
|
1755
|
+
async exists(id: string): Promise<boolean> {
|
|
1756
|
+
return users.has(id)
|
|
1757
|
+
}
|
|
1067
1758
|
}
|
|
1068
1759
|
`;
|
|
1069
1760
|
}
|
|
@@ -1087,7 +1778,7 @@ export class MailService implements IMailService {
|
|
|
1087
1778
|
* App Service Provider
|
|
1088
1779
|
*/
|
|
1089
1780
|
|
|
1090
|
-
import { ServiceProvider, type Container, type PlanetCore } from 'gravito
|
|
1781
|
+
import { ServiceProvider, type Container, type PlanetCore } from '@gravito/core'
|
|
1091
1782
|
|
|
1092
1783
|
export class AppServiceProvider extends ServiceProvider {
|
|
1093
1784
|
register(_container: Container): void {
|
|
@@ -1107,7 +1798,7 @@ export class AppServiceProvider extends ServiceProvider {
|
|
|
1107
1798
|
* Binds repository interfaces to implementations.
|
|
1108
1799
|
*/
|
|
1109
1800
|
|
|
1110
|
-
import { ServiceProvider, type Container } from 'gravito
|
|
1801
|
+
import { ServiceProvider, type Container } from '@gravito/core'
|
|
1111
1802
|
import { UserRepository } from '../Persistence/Repositories/UserRepository'
|
|
1112
1803
|
import { MailService } from '../ExternalServices/MailService'
|
|
1113
1804
|
|
|
@@ -1120,6 +1811,65 @@ export class RepositoryServiceProvider extends ServiceProvider {
|
|
|
1120
1811
|
container.singleton('mailService', () => new MailService())
|
|
1121
1812
|
}
|
|
1122
1813
|
}
|
|
1814
|
+
`;
|
|
1815
|
+
}
|
|
1816
|
+
generateProvidersIndex() {
|
|
1817
|
+
return `/**
|
|
1818
|
+
* Application Service Providers
|
|
1819
|
+
*/
|
|
1820
|
+
|
|
1821
|
+
export { AppServiceProvider } from './AppServiceProvider'
|
|
1822
|
+
export { RepositoryServiceProvider } from './RepositoryServiceProvider'
|
|
1823
|
+
export { MiddlewareProvider } from './MiddlewareProvider'
|
|
1824
|
+
export { RouteProvider } from './RouteProvider'
|
|
1825
|
+
`;
|
|
1826
|
+
}
|
|
1827
|
+
generateMiddlewareProvider() {
|
|
1828
|
+
return `/**
|
|
1829
|
+
* Middleware Service Provider
|
|
1830
|
+
*/
|
|
1831
|
+
|
|
1832
|
+
import {
|
|
1833
|
+
ServiceProvider,
|
|
1834
|
+
type Container,
|
|
1835
|
+
type PlanetCore,
|
|
1836
|
+
bodySizeLimit,
|
|
1837
|
+
securityHeaders,
|
|
1838
|
+
} from '@gravito/core'
|
|
1839
|
+
|
|
1840
|
+
export class MiddlewareProvider extends ServiceProvider {
|
|
1841
|
+
register(_container: Container): void {}
|
|
1842
|
+
|
|
1843
|
+
boot(core: PlanetCore): void {
|
|
1844
|
+
const isDev = process.env.NODE_ENV !== 'production'
|
|
1845
|
+
|
|
1846
|
+
core.adapter.use('*', securityHeaders({
|
|
1847
|
+
contentSecurityPolicy: isDev ? false : undefined,
|
|
1848
|
+
}))
|
|
1849
|
+
|
|
1850
|
+
core.adapter.use('*', bodySizeLimit(10 * 1024 * 1024))
|
|
1851
|
+
|
|
1852
|
+
core.logger.info('\u{1F6E1}\uFE0F Middleware registered')
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
`;
|
|
1856
|
+
}
|
|
1857
|
+
generateRouteProvider() {
|
|
1858
|
+
return `/**
|
|
1859
|
+
* Route Service Provider
|
|
1860
|
+
*/
|
|
1861
|
+
|
|
1862
|
+
import { ServiceProvider, type Container, type PlanetCore } from '@gravito/core'
|
|
1863
|
+
import { registerApiRoutes } from '../../Interface/Http/Routes/api'
|
|
1864
|
+
|
|
1865
|
+
export class RouteProvider extends ServiceProvider {
|
|
1866
|
+
register(_container: Container): void {}
|
|
1867
|
+
|
|
1868
|
+
boot(core: PlanetCore): void {
|
|
1869
|
+
registerApiRoutes(core.router)
|
|
1870
|
+
core.logger.info('\u{1F6E4}\uFE0F Routes registered')
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1123
1873
|
`;
|
|
1124
1874
|
}
|
|
1125
1875
|
// ─────────────────────────────────────────────────────────────
|
|
@@ -1130,7 +1880,7 @@ export class RepositoryServiceProvider extends ServiceProvider {
|
|
|
1130
1880
|
* User Controller
|
|
1131
1881
|
*/
|
|
1132
1882
|
|
|
1133
|
-
import type { GravitoContext } from 'gravito
|
|
1883
|
+
import type { GravitoContext } from '@gravito/core'
|
|
1134
1884
|
import { CreateUserUseCase } from '../../../Application/UseCases/User/CreateUser'
|
|
1135
1885
|
import { GetUserUseCase } from '../../../Application/UseCases/User/GetUser'
|
|
1136
1886
|
import { UserRepository } from '../../../Infrastructure/Persistence/Repositories/UserRepository'
|
|
@@ -1204,28 +1954,55 @@ export class UserPresenter {
|
|
|
1204
1954
|
}
|
|
1205
1955
|
`;
|
|
1206
1956
|
}
|
|
1207
|
-
generateBootstrap(
|
|
1957
|
+
generateBootstrap(_context) {
|
|
1208
1958
|
return `/**
|
|
1209
1959
|
* Application Bootstrap
|
|
1960
|
+
*
|
|
1961
|
+
* The entry point for your Clean Architecture application.
|
|
1962
|
+
* Uses the ServiceProvider pattern for modular initialization.
|
|
1963
|
+
*
|
|
1964
|
+
* Lifecycle:
|
|
1965
|
+
* 1. Configure: Load app config and orbits
|
|
1966
|
+
* 2. Boot: Initialize PlanetCore
|
|
1967
|
+
* 3. Register Providers: Bind services to container
|
|
1968
|
+
* 4. Bootstrap: Boot all providers
|
|
1210
1969
|
*/
|
|
1211
1970
|
|
|
1212
|
-
import { PlanetCore } from 'gravito
|
|
1213
|
-
import {
|
|
1214
|
-
import
|
|
1215
|
-
import {
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1971
|
+
import { defineConfig, PlanetCore } from '@gravito/core'
|
|
1972
|
+
import { OrbitAtlas } from '@gravito/atlas'
|
|
1973
|
+
import appConfig from '../config/app'
|
|
1974
|
+
import {
|
|
1975
|
+
AppServiceProvider,
|
|
1976
|
+
RepositoryServiceProvider,
|
|
1977
|
+
MiddlewareProvider,
|
|
1978
|
+
RouteProvider,
|
|
1979
|
+
} from './Infrastructure/Providers'
|
|
1980
|
+
|
|
1981
|
+
export async function bootstrap() {
|
|
1982
|
+
// 1. Configure
|
|
1983
|
+
const config = defineConfig({
|
|
1984
|
+
config: appConfig,
|
|
1985
|
+
orbits: [new OrbitAtlas()],
|
|
1986
|
+
})
|
|
1987
|
+
|
|
1988
|
+
// 2. Boot Core
|
|
1989
|
+
const core = await PlanetCore.boot(config)
|
|
1990
|
+
core.registerGlobalErrorHandlers()
|
|
1991
|
+
|
|
1992
|
+
// 3. Register Providers
|
|
1993
|
+
core.register(new RepositoryServiceProvider())
|
|
1994
|
+
core.register(new AppServiceProvider())
|
|
1995
|
+
core.register(new MiddlewareProvider())
|
|
1996
|
+
core.register(new RouteProvider())
|
|
1997
|
+
|
|
1998
|
+
// 4. Bootstrap All Providers
|
|
1999
|
+
await core.bootstrap()
|
|
2000
|
+
|
|
2001
|
+
return core
|
|
2002
|
+
}
|
|
1228
2003
|
|
|
2004
|
+
// Application Entry Point
|
|
2005
|
+
const core = await bootstrap()
|
|
1229
2006
|
export default core.liftoff()
|
|
1230
2007
|
`;
|
|
1231
2008
|
}
|
|
@@ -1237,6 +2014,15 @@ export default core.liftoff()
|
|
|
1237
2014
|
This project follows **Clean Architecture** (by Robert C. Martin).
|
|
1238
2015
|
The key principle is the **Dependency Rule**: dependencies point inward.
|
|
1239
2016
|
|
|
2017
|
+
## Service Providers
|
|
2018
|
+
|
|
2019
|
+
Service providers are the central place to configure your application. They follow the ServiceProvider pattern with \`register()\` and \`boot()\` lifecycle methods.
|
|
2020
|
+
|
|
2021
|
+
### Provider Lifecycle
|
|
2022
|
+
|
|
2023
|
+
1. **register()**: Bind services to the container (sync or async).
|
|
2024
|
+
2. **boot()**: Called after ALL providers have registered. Safe to use other services.
|
|
2025
|
+
|
|
1240
2026
|
## Layer Structure
|
|
1241
2027
|
|
|
1242
2028
|
\`\`\`
|
|
@@ -1297,6 +2083,36 @@ src/
|
|
|
1297
2083
|
Created with \u2764\uFE0F using Gravito Framework
|
|
1298
2084
|
`;
|
|
1299
2085
|
}
|
|
2086
|
+
generatePackageJson(context) {
|
|
2087
|
+
const pkg = {
|
|
2088
|
+
name: context.nameKebabCase,
|
|
2089
|
+
version: "0.1.0",
|
|
2090
|
+
type: "module",
|
|
2091
|
+
scripts: {
|
|
2092
|
+
dev: "bun run --watch src/bootstrap.ts",
|
|
2093
|
+
build: "bun build ./src/bootstrap.ts --outdir ./dist --target bun",
|
|
2094
|
+
start: "bun run dist/bootstrap.js",
|
|
2095
|
+
test: "bun test",
|
|
2096
|
+
typecheck: "tsc --noEmit",
|
|
2097
|
+
check: "bun run typecheck && bun run test",
|
|
2098
|
+
"check:deps": "bun run scripts/check-dependencies.ts",
|
|
2099
|
+
validate: "bun run check && bun run check:deps",
|
|
2100
|
+
precommit: "bun run validate",
|
|
2101
|
+
"docker:build": `docker build -t ${context.nameKebabCase} .`,
|
|
2102
|
+
"docker:run": `docker run -it -p 3000:3000 ${context.nameKebabCase}`
|
|
2103
|
+
},
|
|
2104
|
+
dependencies: {
|
|
2105
|
+
"@gravito/core": "workspace:*",
|
|
2106
|
+
"@gravito/enterprise": "workspace:*",
|
|
2107
|
+
...context.withSpectrum ? { "@gravito/spectrum": "workspace:*" } : {}
|
|
2108
|
+
},
|
|
2109
|
+
devDependencies: {
|
|
2110
|
+
"bun-types": "latest",
|
|
2111
|
+
typescript: "^5.0.0"
|
|
2112
|
+
}
|
|
2113
|
+
};
|
|
2114
|
+
return JSON.stringify(pkg, null, 2);
|
|
2115
|
+
}
|
|
1300
2116
|
};
|
|
1301
2117
|
|
|
1302
2118
|
// src/generators/DddGenerator.ts
|
|
@@ -1424,22 +2240,6 @@ var DddGenerator = class extends BaseGenerator {
|
|
|
1424
2240
|
{ type: "file", name: "Money.ts", content: this.generateMoneyValueObject() },
|
|
1425
2241
|
{ type: "file", name: "Email.ts", content: this.generateEmailValueObject() }
|
|
1426
2242
|
]
|
|
1427
|
-
},
|
|
1428
|
-
{
|
|
1429
|
-
type: "directory",
|
|
1430
|
-
name: "Events",
|
|
1431
|
-
children: [
|
|
1432
|
-
{ type: "file", name: "DomainEvent.ts", content: this.generateDomainEvent() }
|
|
1433
|
-
]
|
|
1434
|
-
},
|
|
1435
|
-
{
|
|
1436
|
-
type: "directory",
|
|
1437
|
-
name: "Primitives",
|
|
1438
|
-
children: [
|
|
1439
|
-
{ type: "file", name: "AggregateRoot.ts", content: this.generateAggregateRoot() },
|
|
1440
|
-
{ type: "file", name: "Entity.ts", content: this.generateEntity() },
|
|
1441
|
-
{ type: "file", name: "ValueObject.ts", content: this.generateValueObject() }
|
|
1442
|
-
]
|
|
1443
2243
|
}
|
|
1444
2244
|
]
|
|
1445
2245
|
},
|
|
@@ -1621,34 +2421,46 @@ var DddGenerator = class extends BaseGenerator {
|
|
|
1621
2421
|
// ─────────────────────────────────────────────────────────────
|
|
1622
2422
|
// Bootstrap File Generators
|
|
1623
2423
|
// ─────────────────────────────────────────────────────────────
|
|
1624
|
-
generateBootstrapApp(
|
|
2424
|
+
generateBootstrapApp(_context) {
|
|
1625
2425
|
return `/**
|
|
1626
2426
|
* Application Bootstrap
|
|
1627
2427
|
*
|
|
1628
|
-
* Central configuration and initialization
|
|
2428
|
+
* Central configuration and initialization using the ServiceProvider pattern.
|
|
2429
|
+
*
|
|
2430
|
+
* Lifecycle:
|
|
2431
|
+
* 1. Configure: Load app config and orbits
|
|
2432
|
+
* 2. Boot: Initialize PlanetCore
|
|
2433
|
+
* 3. Register Providers: Bind services to container
|
|
2434
|
+
* 4. Bootstrap: Boot all providers
|
|
1629
2435
|
*/
|
|
1630
2436
|
|
|
1631
|
-
import { PlanetCore } from 'gravito
|
|
2437
|
+
import { defineConfig, PlanetCore } from '@gravito/core'
|
|
2438
|
+
import { OrbitAtlas } from '@gravito/atlas'
|
|
2439
|
+
import appConfig from '../../config/app'
|
|
1632
2440
|
import { registerProviders } from './providers'
|
|
1633
2441
|
import { registerRoutes } from './routes'
|
|
1634
2442
|
|
|
1635
2443
|
export async function createApp(): Promise<PlanetCore> {
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
2444
|
+
// 1. Configure
|
|
2445
|
+
const config = defineConfig({
|
|
2446
|
+
config: appConfig,
|
|
2447
|
+
orbits: [new OrbitAtlas()],
|
|
2448
|
+
})
|
|
1641
2449
|
|
|
1642
|
-
|
|
1643
|
-
|
|
2450
|
+
// 2. Boot Core
|
|
2451
|
+
const core = await PlanetCore.boot(config)
|
|
2452
|
+
core.registerGlobalErrorHandlers()
|
|
1644
2453
|
|
|
1645
|
-
|
|
1646
|
-
|
|
2454
|
+
// 3. Register Providers
|
|
2455
|
+
await registerProviders(core)
|
|
1647
2456
|
|
|
1648
|
-
|
|
1649
|
-
|
|
2457
|
+
// 4. Bootstrap All Providers
|
|
2458
|
+
await core.bootstrap()
|
|
2459
|
+
|
|
2460
|
+
// Register routes after bootstrap
|
|
2461
|
+
registerRoutes(core.router)
|
|
1650
2462
|
|
|
1651
|
-
|
|
2463
|
+
return core
|
|
1652
2464
|
}
|
|
1653
2465
|
`;
|
|
1654
2466
|
}
|
|
@@ -1656,19 +2468,48 @@ export async function createApp(): Promise<PlanetCore> {
|
|
|
1656
2468
|
return `/**
|
|
1657
2469
|
* Service Providers Registry
|
|
1658
2470
|
*
|
|
1659
|
-
* Register all
|
|
2471
|
+
* Register all service providers here.
|
|
2472
|
+
* Include both global and module-specific providers.
|
|
1660
2473
|
*/
|
|
1661
2474
|
|
|
1662
|
-
import
|
|
2475
|
+
import {
|
|
2476
|
+
ServiceProvider,
|
|
2477
|
+
type Container,
|
|
2478
|
+
type PlanetCore,
|
|
2479
|
+
bodySizeLimit,
|
|
2480
|
+
securityHeaders,
|
|
2481
|
+
} from '@gravito/core'
|
|
1663
2482
|
import { OrderingServiceProvider } from '../Modules/Ordering/Infrastructure/Providers/OrderingServiceProvider'
|
|
1664
2483
|
import { CatalogServiceProvider } from '../Modules/Catalog/Infrastructure/Providers/CatalogServiceProvider'
|
|
1665
2484
|
|
|
2485
|
+
/**
|
|
2486
|
+
* Middleware Provider - Global middleware registration
|
|
2487
|
+
*/
|
|
2488
|
+
export class MiddlewareProvider extends ServiceProvider {
|
|
2489
|
+
register(_container: Container): void {}
|
|
2490
|
+
|
|
2491
|
+
boot(core: PlanetCore): void {
|
|
2492
|
+
const isDev = process.env.NODE_ENV !== 'production'
|
|
2493
|
+
|
|
2494
|
+
core.adapter.use('*', securityHeaders({
|
|
2495
|
+
contentSecurityPolicy: isDev ? false : undefined,
|
|
2496
|
+
}))
|
|
2497
|
+
|
|
2498
|
+
core.adapter.use('*', bodySizeLimit(10 * 1024 * 1024))
|
|
2499
|
+
|
|
2500
|
+
core.logger.info('\u{1F6E1}\uFE0F Global middleware registered')
|
|
2501
|
+
}
|
|
2502
|
+
}
|
|
2503
|
+
|
|
1666
2504
|
export async function registerProviders(core: PlanetCore): Promise<void> {
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
2505
|
+
// Global Providers
|
|
2506
|
+
core.register(new MiddlewareProvider())
|
|
2507
|
+
|
|
2508
|
+
// Module Providers
|
|
2509
|
+
core.register(new OrderingServiceProvider())
|
|
2510
|
+
core.register(new CatalogServiceProvider())
|
|
1670
2511
|
|
|
1671
|
-
|
|
2512
|
+
// Add more providers as needed
|
|
1672
2513
|
}
|
|
1673
2514
|
`;
|
|
1674
2515
|
}
|
|
@@ -1754,7 +2595,7 @@ export default {
|
|
|
1754
2595
|
* ${name} Service Provider
|
|
1755
2596
|
*/
|
|
1756
2597
|
|
|
1757
|
-
import { ServiceProvider, type Container, type PlanetCore } from 'gravito
|
|
2598
|
+
import { ServiceProvider, type Container, type PlanetCore } from '@gravito/core'
|
|
1758
2599
|
import { ${name}Repository } from '../Persistence/${name}Repository'
|
|
1759
2600
|
|
|
1760
2601
|
export class ${name}ServiceProvider extends ServiceProvider {
|
|
@@ -1781,13 +2622,20 @@ export class ${name}ServiceProvider extends ServiceProvider {
|
|
|
1781
2622
|
build: "bun build ./src/main.ts --outdir ./dist --target bun",
|
|
1782
2623
|
start: "bun run dist/main.js",
|
|
1783
2624
|
test: "bun test",
|
|
1784
|
-
typecheck: "tsc --noEmit"
|
|
2625
|
+
typecheck: "tsc --noEmit",
|
|
2626
|
+
check: "bun run typecheck && bun run test",
|
|
2627
|
+
"check:deps": "bun run scripts/check-dependencies.ts",
|
|
2628
|
+
validate: "bun run check && bun run check:deps",
|
|
2629
|
+
precommit: "bun run validate",
|
|
2630
|
+
"docker:build": `docker build -t ${context.nameKebabCase} .`,
|
|
2631
|
+
"docker:run": `docker run -it -p 3000:3000 ${context.nameKebabCase}`
|
|
1785
2632
|
},
|
|
1786
2633
|
dependencies: {
|
|
1787
|
-
"gravito
|
|
2634
|
+
"@gravito/core": "^1.0.0-beta.5",
|
|
2635
|
+
"@gravito/enterprise": "workspace:*"
|
|
1788
2636
|
},
|
|
1789
2637
|
devDependencies: {
|
|
1790
|
-
"
|
|
2638
|
+
"bun-types": "latest",
|
|
1791
2639
|
typescript: "^5.0.0"
|
|
1792
2640
|
}
|
|
1793
2641
|
};
|
|
@@ -1838,11 +2686,15 @@ export class ${name}ServiceProvider extends ServiceProvider {
|
|
|
1838
2686
|
* Shared identifier across all contexts.
|
|
1839
2687
|
*/
|
|
1840
2688
|
|
|
1841
|
-
|
|
1842
|
-
|
|
2689
|
+
import { ValueObject } from '@gravito/enterprise'
|
|
2690
|
+
|
|
2691
|
+
interface IdProps {
|
|
2692
|
+
value: string
|
|
2693
|
+
}
|
|
1843
2694
|
|
|
2695
|
+
export class Id extends ValueObject<IdProps> {
|
|
1844
2696
|
private constructor(value: string) {
|
|
1845
|
-
|
|
2697
|
+
super({ value })
|
|
1846
2698
|
}
|
|
1847
2699
|
|
|
1848
2700
|
static create(): Id {
|
|
@@ -1855,15 +2707,11 @@ export class Id {
|
|
|
1855
2707
|
}
|
|
1856
2708
|
|
|
1857
2709
|
get value(): string {
|
|
1858
|
-
return this.
|
|
1859
|
-
}
|
|
1860
|
-
|
|
1861
|
-
equals(other: Id): boolean {
|
|
1862
|
-
return this._value === other._value
|
|
2710
|
+
return this.props.value
|
|
1863
2711
|
}
|
|
1864
2712
|
|
|
1865
2713
|
toString(): string {
|
|
1866
|
-
return this.
|
|
2714
|
+
return this.props.value
|
|
1867
2715
|
}
|
|
1868
2716
|
}
|
|
1869
2717
|
`;
|
|
@@ -1873,12 +2721,25 @@ export class Id {
|
|
|
1873
2721
|
* Money Value Object
|
|
1874
2722
|
*/
|
|
1875
2723
|
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
2724
|
+
import { ValueObject } from '@gravito/enterprise'
|
|
2725
|
+
|
|
2726
|
+
interface MoneyProps {
|
|
2727
|
+
amount: number
|
|
2728
|
+
currency: string
|
|
2729
|
+
}
|
|
2730
|
+
|
|
2731
|
+
export class Money extends ValueObject<MoneyProps> {
|
|
2732
|
+
constructor(amount: number, currency: string = 'USD') {
|
|
1881
2733
|
if (amount < 0) throw new Error('Amount cannot be negative')
|
|
2734
|
+
super({ amount, currency })
|
|
2735
|
+
}
|
|
2736
|
+
|
|
2737
|
+
get amount(): number {
|
|
2738
|
+
return this.props.amount
|
|
2739
|
+
}
|
|
2740
|
+
|
|
2741
|
+
get currency(): string {
|
|
2742
|
+
return this.props.currency
|
|
1882
2743
|
}
|
|
1883
2744
|
|
|
1884
2745
|
add(other: Money): Money {
|
|
@@ -1896,10 +2757,6 @@ export class Money {
|
|
|
1896
2757
|
throw new Error('Cannot operate on different currencies')
|
|
1897
2758
|
}
|
|
1898
2759
|
}
|
|
1899
|
-
|
|
1900
|
-
equals(other: Money): boolean {
|
|
1901
|
-
return this.amount === other.amount && this.currency === other.currency
|
|
1902
|
-
}
|
|
1903
2760
|
}
|
|
1904
2761
|
`;
|
|
1905
2762
|
}
|
|
@@ -1908,11 +2765,15 @@ export class Money {
|
|
|
1908
2765
|
* Email Value Object
|
|
1909
2766
|
*/
|
|
1910
2767
|
|
|
1911
|
-
|
|
1912
|
-
|
|
2768
|
+
import { ValueObject } from '@gravito/enterprise'
|
|
2769
|
+
|
|
2770
|
+
interface EmailProps {
|
|
2771
|
+
value: string
|
|
2772
|
+
}
|
|
1913
2773
|
|
|
2774
|
+
export class Email extends ValueObject<EmailProps> {
|
|
1914
2775
|
private constructor(value: string) {
|
|
1915
|
-
|
|
2776
|
+
super({ value: value.toLowerCase().trim() })
|
|
1916
2777
|
}
|
|
1917
2778
|
|
|
1918
2779
|
static create(email: string): Email {
|
|
@@ -1927,97 +2788,7 @@ export class Email {
|
|
|
1927
2788
|
}
|
|
1928
2789
|
|
|
1929
2790
|
get value(): string {
|
|
1930
|
-
return this.
|
|
1931
|
-
}
|
|
1932
|
-
}
|
|
1933
|
-
`;
|
|
1934
|
-
}
|
|
1935
|
-
generateDomainEvent() {
|
|
1936
|
-
return `/**
|
|
1937
|
-
* Domain Event Base
|
|
1938
|
-
*/
|
|
1939
|
-
|
|
1940
|
-
export abstract class DomainEvent {
|
|
1941
|
-
readonly occurredOn: Date
|
|
1942
|
-
readonly eventId: string
|
|
1943
|
-
|
|
1944
|
-
constructor() {
|
|
1945
|
-
this.occurredOn = new Date()
|
|
1946
|
-
this.eventId = crypto.randomUUID()
|
|
1947
|
-
}
|
|
1948
|
-
|
|
1949
|
-
abstract get eventName(): string
|
|
1950
|
-
abstract get aggregateId(): string
|
|
1951
|
-
}
|
|
1952
|
-
`;
|
|
1953
|
-
}
|
|
1954
|
-
generateAggregateRoot() {
|
|
1955
|
-
return `/**
|
|
1956
|
-
* Aggregate Root Base
|
|
1957
|
-
*/
|
|
1958
|
-
|
|
1959
|
-
import type { DomainEvent } from '../Events/DomainEvent'
|
|
1960
|
-
import type { Id } from '../ValueObjects/Id'
|
|
1961
|
-
|
|
1962
|
-
export abstract class AggregateRoot<T extends Id = Id> {
|
|
1963
|
-
private _domainEvents: DomainEvent[] = []
|
|
1964
|
-
|
|
1965
|
-
protected constructor(protected readonly _id: T) {}
|
|
1966
|
-
|
|
1967
|
-
get id(): T {
|
|
1968
|
-
return this._id
|
|
1969
|
-
}
|
|
1970
|
-
|
|
1971
|
-
get domainEvents(): DomainEvent[] {
|
|
1972
|
-
return [...this._domainEvents]
|
|
1973
|
-
}
|
|
1974
|
-
|
|
1975
|
-
protected addDomainEvent(event: DomainEvent): void {
|
|
1976
|
-
this._domainEvents.push(event)
|
|
1977
|
-
}
|
|
1978
|
-
|
|
1979
|
-
clearDomainEvents(): DomainEvent[] {
|
|
1980
|
-
const events = [...this._domainEvents]
|
|
1981
|
-
this._domainEvents = []
|
|
1982
|
-
return events
|
|
1983
|
-
}
|
|
1984
|
-
}
|
|
1985
|
-
`;
|
|
1986
|
-
}
|
|
1987
|
-
generateEntity() {
|
|
1988
|
-
return `/**
|
|
1989
|
-
* Entity Base
|
|
1990
|
-
*/
|
|
1991
|
-
|
|
1992
|
-
import type { Id } from '../ValueObjects/Id'
|
|
1993
|
-
|
|
1994
|
-
export abstract class Entity<T extends Id = Id> {
|
|
1995
|
-
protected constructor(protected readonly _id: T) {}
|
|
1996
|
-
|
|
1997
|
-
get id(): T {
|
|
1998
|
-
return this._id
|
|
1999
|
-
}
|
|
2000
|
-
|
|
2001
|
-
equals(other: Entity<T>): boolean {
|
|
2002
|
-
return this._id.equals(other._id)
|
|
2003
|
-
}
|
|
2004
|
-
}
|
|
2005
|
-
`;
|
|
2006
|
-
}
|
|
2007
|
-
generateValueObject() {
|
|
2008
|
-
return `/**
|
|
2009
|
-
* Value Object Base
|
|
2010
|
-
*/
|
|
2011
|
-
|
|
2012
|
-
export abstract class ValueObject<T> {
|
|
2013
|
-
protected readonly props: T
|
|
2014
|
-
|
|
2015
|
-
constructor(props: T) {
|
|
2016
|
-
this.props = Object.freeze(props)
|
|
2017
|
-
}
|
|
2018
|
-
|
|
2019
|
-
equals(other: ValueObject<T>): boolean {
|
|
2020
|
-
return JSON.stringify(this.props) === JSON.stringify(other.props)
|
|
2791
|
+
return this.props.value
|
|
2021
2792
|
}
|
|
2022
2793
|
}
|
|
2023
2794
|
`;
|
|
@@ -2027,7 +2798,7 @@ export abstract class ValueObject<T> {
|
|
|
2027
2798
|
* Event Dispatcher
|
|
2028
2799
|
*/
|
|
2029
2800
|
|
|
2030
|
-
import type { DomainEvent } from '
|
|
2801
|
+
import type { DomainEvent } from '@gravito/enterprise'
|
|
2031
2802
|
|
|
2032
2803
|
type EventHandler = (event: DomainEvent) => void | Promise<void>
|
|
2033
2804
|
|
|
@@ -2063,7 +2834,7 @@ export class EventDispatcher {
|
|
|
2063
2834
|
* ${name} Aggregate Root
|
|
2064
2835
|
*/
|
|
2065
2836
|
|
|
2066
|
-
import { AggregateRoot } from '
|
|
2837
|
+
import { AggregateRoot } from '@gravito/enterprise'
|
|
2067
2838
|
import { Id } from '../../../../../Shared/Domain/ValueObjects/Id'
|
|
2068
2839
|
import { ${name}Created } from '../../Events/${name}Created'
|
|
2069
2840
|
import { ${name}Status } from './${name}Status'
|
|
@@ -2074,7 +2845,7 @@ export interface ${name}Props {
|
|
|
2074
2845
|
createdAt: Date
|
|
2075
2846
|
}
|
|
2076
2847
|
|
|
2077
|
-
export class ${name} extends AggregateRoot {
|
|
2848
|
+
export class ${name} extends AggregateRoot<Id> {
|
|
2078
2849
|
private props: ${name}Props
|
|
2079
2850
|
|
|
2080
2851
|
private constructor(id: Id, props: ${name}Props) {
|
|
@@ -2119,14 +2890,14 @@ export enum ${name}Status {
|
|
|
2119
2890
|
* ${name} Created Event
|
|
2120
2891
|
*/
|
|
2121
2892
|
|
|
2122
|
-
import { DomainEvent } from '
|
|
2893
|
+
import { DomainEvent } from '@gravito/enterprise'
|
|
2123
2894
|
|
|
2124
2895
|
export class ${name}Created extends DomainEvent {
|
|
2125
2896
|
constructor(public readonly ${name.toLowerCase()}Id: string) {
|
|
2126
2897
|
super()
|
|
2127
2898
|
}
|
|
2128
2899
|
|
|
2129
|
-
get eventName(): string {
|
|
2900
|
+
override get eventName(): string {
|
|
2130
2901
|
return '${name.toLowerCase()}.created'
|
|
2131
2902
|
}
|
|
2132
2903
|
|
|
@@ -2141,12 +2912,12 @@ export class ${name}Created extends DomainEvent {
|
|
|
2141
2912
|
* ${name} Repository Interface
|
|
2142
2913
|
*/
|
|
2143
2914
|
|
|
2915
|
+
import { Repository } from '@gravito/enterprise'
|
|
2144
2916
|
import type { ${name} } from '../Aggregates/${name}/${name}'
|
|
2917
|
+
import { Id } from '../../../../../Shared/Domain/ValueObjects/Id'
|
|
2145
2918
|
|
|
2146
|
-
export interface I${name}Repository {
|
|
2147
|
-
|
|
2148
|
-
save(aggregate: ${name}): Promise<void>
|
|
2149
|
-
delete(id: string): Promise<void>
|
|
2919
|
+
export interface I${name}Repository extends Repository<${name}, Id> {
|
|
2920
|
+
// Add specific methods for this repository if needed
|
|
2150
2921
|
}
|
|
2151
2922
|
`;
|
|
2152
2923
|
}
|
|
@@ -2155,11 +2926,15 @@ export interface I${name}Repository {
|
|
|
2155
2926
|
* Create ${name} Command
|
|
2156
2927
|
*/
|
|
2157
2928
|
|
|
2158
|
-
|
|
2929
|
+
import { Command } from '@gravito/enterprise'
|
|
2930
|
+
|
|
2931
|
+
export class Create${name}Command extends Command {
|
|
2159
2932
|
constructor(
|
|
2160
2933
|
// Add command properties
|
|
2161
2934
|
public readonly id?: string
|
|
2162
|
-
) {
|
|
2935
|
+
) {
|
|
2936
|
+
super()
|
|
2937
|
+
}
|
|
2163
2938
|
}
|
|
2164
2939
|
`;
|
|
2165
2940
|
}
|
|
@@ -2168,12 +2943,13 @@ export class Create${name}Command {
|
|
|
2168
2943
|
* Create ${name} Handler
|
|
2169
2944
|
*/
|
|
2170
2945
|
|
|
2946
|
+
import { CommandHandler } from '@gravito/enterprise'
|
|
2171
2947
|
import type { I${name}Repository } from '../../../Domain/Repositories/I${name}Repository'
|
|
2172
2948
|
import { ${name} } from '../../../Domain/Aggregates/${name}/${name}'
|
|
2173
2949
|
import { Id } from '../../../../../Shared/Domain/ValueObjects/Id'
|
|
2174
2950
|
import type { Create${name}Command } from './Create${name}Command'
|
|
2175
2951
|
|
|
2176
|
-
export class Create${name}Handler {
|
|
2952
|
+
export class Create${name}Handler implements CommandHandler<Create${name}Command, string> {
|
|
2177
2953
|
constructor(private repository: I${name}Repository) {}
|
|
2178
2954
|
|
|
2179
2955
|
async handle(command: Create${name}Command): Promise<string> {
|
|
@@ -2192,8 +2968,12 @@ export class Create${name}Handler {
|
|
|
2192
2968
|
* Get ${name} By Id Query
|
|
2193
2969
|
*/
|
|
2194
2970
|
|
|
2195
|
-
|
|
2196
|
-
|
|
2971
|
+
import { Query } from '@gravito/enterprise'
|
|
2972
|
+
|
|
2973
|
+
export class Get${name}ByIdQuery extends Query {
|
|
2974
|
+
constructor(public readonly id: string) {
|
|
2975
|
+
super()
|
|
2976
|
+
}
|
|
2197
2977
|
}
|
|
2198
2978
|
`;
|
|
2199
2979
|
}
|
|
@@ -2202,15 +2982,16 @@ export class Get${name}ByIdQuery {
|
|
|
2202
2982
|
* Get ${name} By Id Handler
|
|
2203
2983
|
*/
|
|
2204
2984
|
|
|
2985
|
+
import { QueryHandler } from '@gravito/enterprise'
|
|
2205
2986
|
import type { I${name}Repository } from '../../../Domain/Repositories/I${name}Repository'
|
|
2206
2987
|
import type { ${name}DTO } from '../../DTOs/${name}DTO'
|
|
2207
2988
|
import type { Get${name}ByIdQuery } from './Get${name}ByIdQuery'
|
|
2208
2989
|
|
|
2209
|
-
export class Get${name}ByIdHandler {
|
|
2990
|
+
export class Get${name}ByIdHandler implements QueryHandler<Get${name}ByIdQuery, ${name}DTO | null> {
|
|
2210
2991
|
constructor(private repository: I${name}Repository) {}
|
|
2211
2992
|
|
|
2212
2993
|
async handle(query: Get${name}ByIdQuery): Promise<${name}DTO | null> {
|
|
2213
|
-
const aggregate = await this.repository.findById(query.id)
|
|
2994
|
+
const aggregate = await this.repository.findById(query.id as any) // Simplified for demo
|
|
2214
2995
|
if (!aggregate) return null
|
|
2215
2996
|
|
|
2216
2997
|
return {
|
|
@@ -2242,20 +3023,29 @@ export interface ${name}DTO {
|
|
|
2242
3023
|
|
|
2243
3024
|
import type { ${name} } from '../../Domain/Aggregates/${name}/${name}'
|
|
2244
3025
|
import type { I${name}Repository } from '../../Domain/Repositories/I${name}Repository'
|
|
3026
|
+
import type { Id } from '../../../../../Shared/Domain/ValueObjects/Id'
|
|
2245
3027
|
|
|
2246
3028
|
const store = new Map<string, ${name}>()
|
|
2247
3029
|
|
|
2248
3030
|
export class ${name}Repository implements I${name}Repository {
|
|
2249
|
-
async findById(id:
|
|
2250
|
-
return store.get(id) ?? null
|
|
3031
|
+
async findById(id: Id): Promise<${name} | null> {
|
|
3032
|
+
return store.get(id.value) ?? null
|
|
2251
3033
|
}
|
|
2252
3034
|
|
|
2253
3035
|
async save(aggregate: ${name}): Promise<void> {
|
|
2254
3036
|
store.set(aggregate.id.value, aggregate)
|
|
2255
3037
|
}
|
|
2256
3038
|
|
|
2257
|
-
async delete(id:
|
|
2258
|
-
store.delete(id)
|
|
3039
|
+
async delete(id: Id): Promise<void> {
|
|
3040
|
+
store.delete(id.value)
|
|
3041
|
+
}
|
|
3042
|
+
|
|
3043
|
+
async findAll(): Promise<${name}[]> {
|
|
3044
|
+
return Array.from(store.values())
|
|
3045
|
+
}
|
|
3046
|
+
|
|
3047
|
+
async exists(id: Id): Promise<boolean> {
|
|
3048
|
+
return store.has(id.value)
|
|
2259
3049
|
}
|
|
2260
3050
|
}
|
|
2261
3051
|
`;
|
|
@@ -2277,6 +3067,16 @@ export function report(error: unknown): void {
|
|
|
2277
3067
|
|
|
2278
3068
|
This project follows **Domain-Driven Design (DDD)** with strategic and tactical patterns.
|
|
2279
3069
|
|
|
3070
|
+
## Service Providers
|
|
3071
|
+
|
|
3072
|
+
Service providers are the central place to configure your application and modules. They follow the ServiceProvider pattern with \`register()\` and \`boot()\` lifecycle methods.
|
|
3073
|
+
|
|
3074
|
+
### Internal Bootstrapping
|
|
3075
|
+
|
|
3076
|
+
1. **Bootstrap/app.ts**: Orchestrates the 4-step lifecycle (Configure, Boot, Register, Bootstrap).
|
|
3077
|
+
2. **Bootstrap/providers.ts**: Central registry for all global and module-specific providers.
|
|
3078
|
+
3. **Infrastructure/Providers/[Module]ServiceProvider.ts**: Module-specific service registration.
|
|
3079
|
+
|
|
2280
3080
|
## Bounded Contexts
|
|
2281
3081
|
|
|
2282
3082
|
\`\`\`
|
|
@@ -2414,6 +3214,11 @@ var EnterpriseMvcGenerator = class extends BaseGenerator {
|
|
|
2414
3214
|
type: "directory",
|
|
2415
3215
|
name: "Providers",
|
|
2416
3216
|
children: [
|
|
3217
|
+
{
|
|
3218
|
+
type: "file",
|
|
3219
|
+
name: "index.ts",
|
|
3220
|
+
content: this.generateProvidersIndex()
|
|
3221
|
+
},
|
|
2417
3222
|
{
|
|
2418
3223
|
type: "file",
|
|
2419
3224
|
name: "AppServiceProvider.ts",
|
|
@@ -2421,8 +3226,18 @@ var EnterpriseMvcGenerator = class extends BaseGenerator {
|
|
|
2421
3226
|
},
|
|
2422
3227
|
{
|
|
2423
3228
|
type: "file",
|
|
2424
|
-
name: "
|
|
2425
|
-
content: this.
|
|
3229
|
+
name: "DatabaseProvider.ts",
|
|
3230
|
+
content: this.generateDatabaseProvider()
|
|
3231
|
+
},
|
|
3232
|
+
{
|
|
3233
|
+
type: "file",
|
|
3234
|
+
name: "MiddlewareProvider.ts",
|
|
3235
|
+
content: this.generateMiddlewareProvider()
|
|
3236
|
+
},
|
|
3237
|
+
{
|
|
3238
|
+
type: "file",
|
|
3239
|
+
name: "RouteProvider.ts",
|
|
3240
|
+
content: this.generateRouteProvider()
|
|
2426
3241
|
}
|
|
2427
3242
|
]
|
|
2428
3243
|
},
|
|
@@ -2698,7 +3513,7 @@ export default {
|
|
|
2698
3513
|
* can be assigned to specific routes.
|
|
2699
3514
|
*/
|
|
2700
3515
|
|
|
2701
|
-
import type { GravitoMiddleware } from 'gravito
|
|
3516
|
+
import type { GravitoMiddleware } from '@gravito/core'
|
|
2702
3517
|
|
|
2703
3518
|
/**
|
|
2704
3519
|
* Global middleware stack.
|
|
@@ -2787,7 +3602,7 @@ export abstract class Controller {
|
|
|
2787
3602
|
* Home Controller
|
|
2788
3603
|
*/
|
|
2789
3604
|
|
|
2790
|
-
import type { GravitoContext } from 'gravito
|
|
3605
|
+
import type { GravitoContext } from '@gravito/core'
|
|
2791
3606
|
import { Controller } from './Controller'
|
|
2792
3607
|
|
|
2793
3608
|
export class HomeController extends Controller {
|
|
@@ -2821,7 +3636,7 @@ export class HomeController extends Controller {
|
|
|
2821
3636
|
* Protects routes that require authentication.
|
|
2822
3637
|
*/
|
|
2823
3638
|
|
|
2824
|
-
import type { GravitoContext, GravitoNext } from 'gravito
|
|
3639
|
+
import type { GravitoContext, GravitoNext } from '@gravito/core'
|
|
2825
3640
|
|
|
2826
3641
|
export async function Authenticate(c: GravitoContext, next: GravitoNext) {
|
|
2827
3642
|
// TODO: Implement authentication check
|
|
@@ -2831,6 +3646,7 @@ export async function Authenticate(c: GravitoContext, next: GravitoNext) {
|
|
|
2831
3646
|
// }
|
|
2832
3647
|
|
|
2833
3648
|
await next()
|
|
3649
|
+
return undefined
|
|
2834
3650
|
}
|
|
2835
3651
|
`;
|
|
2836
3652
|
}
|
|
@@ -2842,7 +3658,7 @@ export async function Authenticate(c: GravitoContext, next: GravitoNext) {
|
|
|
2842
3658
|
* Register and bootstrap application services here.
|
|
2843
3659
|
*/
|
|
2844
3660
|
|
|
2845
|
-
import { ServiceProvider, type Container, type PlanetCore } from 'gravito
|
|
3661
|
+
import { ServiceProvider, type Container, type PlanetCore } from '@gravito/core'
|
|
2846
3662
|
|
|
2847
3663
|
export class AppServiceProvider extends ServiceProvider {
|
|
2848
3664
|
/**
|
|
@@ -2870,7 +3686,7 @@ export class AppServiceProvider extends ServiceProvider {
|
|
|
2870
3686
|
* Configures and registers application routes.
|
|
2871
3687
|
*/
|
|
2872
3688
|
|
|
2873
|
-
import { ServiceProvider, type Container, type PlanetCore } from 'gravito
|
|
3689
|
+
import { ServiceProvider, type Container, type PlanetCore } from '@gravito/core'
|
|
2874
3690
|
import { registerRoutes } from '../routes'
|
|
2875
3691
|
|
|
2876
3692
|
export class RouteServiceProvider extends ServiceProvider {
|
|
@@ -2890,80 +3706,250 @@ export class RouteServiceProvider extends ServiceProvider {
|
|
|
2890
3706
|
}
|
|
2891
3707
|
`;
|
|
2892
3708
|
}
|
|
2893
|
-
|
|
3709
|
+
// ─────────────────────────────────────────────────────────────
|
|
3710
|
+
// Modern Provider Generators (ServiceProvider Pattern)
|
|
3711
|
+
// ─────────────────────────────────────────────────────────────
|
|
3712
|
+
generateProvidersIndex() {
|
|
2894
3713
|
return `/**
|
|
2895
|
-
*
|
|
3714
|
+
* Application Service Providers
|
|
2896
3715
|
*
|
|
2897
|
-
*
|
|
2898
|
-
*
|
|
3716
|
+
* Export all providers for easy importing in bootstrap.
|
|
3717
|
+
* Providers are registered in the order they are listed.
|
|
2899
3718
|
*/
|
|
2900
3719
|
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
export function report(error: unknown, context: ErrorHandlerContext): void {
|
|
2907
|
-
// Log to external service (Sentry, etc.)
|
|
2908
|
-
if (!context.isProduction) {
|
|
2909
|
-
console.error('[Exception Handler]', error)
|
|
3720
|
+
export { AppServiceProvider } from './AppServiceProvider'
|
|
3721
|
+
export { DatabaseProvider } from './DatabaseProvider'
|
|
3722
|
+
export { MiddlewareProvider } from './MiddlewareProvider'
|
|
3723
|
+
export { RouteProvider } from './RouteProvider'
|
|
3724
|
+
`;
|
|
2910
3725
|
}
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
*
|
|
3726
|
+
generateDatabaseProvider() {
|
|
3727
|
+
return `/**
|
|
3728
|
+
* Database Service Provider
|
|
3729
|
+
*
|
|
3730
|
+
* Handles database initialization and migrations.
|
|
3731
|
+
*
|
|
3732
|
+
* Lifecycle:
|
|
3733
|
+
* - register(): Bind database config to container
|
|
3734
|
+
* - boot(): Run migrations and seeders
|
|
2915
3735
|
*/
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
3736
|
+
|
|
3737
|
+
import { ServiceProvider, type Container, type PlanetCore } from '@gravito/core'
|
|
3738
|
+
import databaseConfig from '../../config/database'
|
|
3739
|
+
|
|
3740
|
+
export class DatabaseProvider extends ServiceProvider {
|
|
3741
|
+
/**
|
|
3742
|
+
* Register database configuration.
|
|
3743
|
+
*/
|
|
3744
|
+
register(_container: Container): void {
|
|
3745
|
+
this.mergeConfig(this.core!.config, 'database', databaseConfig)
|
|
2923
3746
|
}
|
|
2924
3747
|
|
|
2925
|
-
|
|
3748
|
+
/**
|
|
3749
|
+
* Initialize database connections.
|
|
3750
|
+
*/
|
|
3751
|
+
async boot(core: PlanetCore): Promise<void> {
|
|
3752
|
+
// Database initialization will be handled by Atlas orbit
|
|
3753
|
+
core.logger.info('\u{1F4E6} Database provider booted')
|
|
3754
|
+
}
|
|
2926
3755
|
}
|
|
2927
|
-
|
|
2928
|
-
/**
|
|
2929
|
-
* A list of exception types that should not be reported.
|
|
2930
|
-
*/
|
|
2931
|
-
export const dontReport: string[] = [
|
|
2932
|
-
'ValidationException',
|
|
2933
|
-
'NotFoundException',
|
|
2934
|
-
]
|
|
2935
3756
|
`;
|
|
2936
3757
|
}
|
|
2937
|
-
|
|
3758
|
+
generateMiddlewareProvider() {
|
|
2938
3759
|
return `/**
|
|
2939
|
-
*
|
|
3760
|
+
* Middleware Service Provider
|
|
2940
3761
|
*
|
|
2941
|
-
*
|
|
2942
|
-
*
|
|
3762
|
+
* Registers global middleware stack.
|
|
3763
|
+
* Provides a centralized location for middleware configuration.
|
|
3764
|
+
*
|
|
3765
|
+
* Lifecycle:
|
|
3766
|
+
* - register(): N/A (no container bindings)
|
|
3767
|
+
* - boot(): Register global middleware
|
|
2943
3768
|
*/
|
|
2944
3769
|
|
|
2945
|
-
import {
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
3770
|
+
import {
|
|
3771
|
+
ServiceProvider,
|
|
3772
|
+
type Container,
|
|
3773
|
+
type PlanetCore,
|
|
3774
|
+
bodySizeLimit,
|
|
3775
|
+
securityHeaders,
|
|
3776
|
+
} from '@gravito/core'
|
|
2951
3777
|
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
3778
|
+
export class MiddlewareProvider extends ServiceProvider {
|
|
3779
|
+
/**
|
|
3780
|
+
* No container bindings needed.
|
|
3781
|
+
*/
|
|
3782
|
+
register(_container: Container): void {
|
|
3783
|
+
// Middleware doesn't require container bindings
|
|
3784
|
+
}
|
|
2958
3785
|
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
3786
|
+
/**
|
|
3787
|
+
* Register global middleware stack.
|
|
3788
|
+
*/
|
|
3789
|
+
boot(core: PlanetCore): void {
|
|
3790
|
+
const isDev = process.env.NODE_ENV !== 'production'
|
|
3791
|
+
|
|
3792
|
+
// Security Headers
|
|
3793
|
+
core.adapter.use('*', securityHeaders({
|
|
3794
|
+
contentSecurityPolicy: isDev ? false : undefined,
|
|
3795
|
+
}))
|
|
3796
|
+
|
|
3797
|
+
// Body Parser Limits
|
|
3798
|
+
core.adapter.use('*', bodySizeLimit(10 * 1024 * 1024)) // 10MB limit
|
|
3799
|
+
|
|
3800
|
+
core.logger.info('\u{1F6E1}\uFE0F Middleware registered')
|
|
3801
|
+
}
|
|
3802
|
+
}
|
|
3803
|
+
`;
|
|
3804
|
+
}
|
|
3805
|
+
generateRouteProvider() {
|
|
3806
|
+
return `/**
|
|
3807
|
+
* Route Service Provider
|
|
3808
|
+
*
|
|
3809
|
+
* Registers application routes.
|
|
3810
|
+
* Routes are registered in the boot phase after all services are available.
|
|
3811
|
+
*
|
|
3812
|
+
* Lifecycle:
|
|
3813
|
+
* - register(): N/A
|
|
3814
|
+
* - boot(): Register routes
|
|
3815
|
+
*/
|
|
3816
|
+
|
|
3817
|
+
import { ServiceProvider, type Container, type PlanetCore } from '@gravito/core'
|
|
3818
|
+
import { registerRoutes } from '../routes'
|
|
3819
|
+
|
|
3820
|
+
export class RouteProvider extends ServiceProvider {
|
|
3821
|
+
/**
|
|
3822
|
+
* No container bindings needed.
|
|
3823
|
+
*/
|
|
3824
|
+
register(_container: Container): void {
|
|
3825
|
+
// Routes don't require container bindings
|
|
3826
|
+
}
|
|
3827
|
+
|
|
3828
|
+
/**
|
|
3829
|
+
* Register application routes.
|
|
3830
|
+
*/
|
|
3831
|
+
boot(core: PlanetCore): void {
|
|
3832
|
+
registerRoutes(core.router)
|
|
3833
|
+
core.logger.info('\u{1F6E4}\uFE0F Routes registered')
|
|
3834
|
+
}
|
|
3835
|
+
}
|
|
3836
|
+
`;
|
|
3837
|
+
}
|
|
3838
|
+
generateExceptionHandler() {
|
|
3839
|
+
return `/**
|
|
3840
|
+
* Exception Handler
|
|
3841
|
+
*
|
|
3842
|
+
* Handles all exceptions thrown by the application.
|
|
3843
|
+
* Customize error responses and logging here.
|
|
3844
|
+
*/
|
|
3845
|
+
|
|
3846
|
+
import type { ErrorHandlerContext } from '@gravito/core'
|
|
3847
|
+
|
|
3848
|
+
/**
|
|
3849
|
+
* Report an exception (logging, monitoring, etc.)
|
|
3850
|
+
*/
|
|
3851
|
+
export function report(error: unknown, context: ErrorHandlerContext): void {
|
|
3852
|
+
// Log to external service (Sentry, etc.)
|
|
3853
|
+
if (!context.isProduction) {
|
|
3854
|
+
console.error('[Exception Handler]', error)
|
|
3855
|
+
}
|
|
3856
|
+
}
|
|
3857
|
+
|
|
3858
|
+
/**
|
|
3859
|
+
* Determine if the exception should be reported.
|
|
3860
|
+
*/
|
|
3861
|
+
export function shouldReport(error: unknown): boolean {
|
|
3862
|
+
// Don't report 4xx errors
|
|
3863
|
+
if (error instanceof Error && 'status' in error) {
|
|
3864
|
+
const status = (error as any).status
|
|
3865
|
+
if (status >= 400 && status < 500) {
|
|
3866
|
+
return false
|
|
3867
|
+
}
|
|
3868
|
+
}
|
|
3869
|
+
|
|
3870
|
+
return true
|
|
3871
|
+
}
|
|
3872
|
+
|
|
3873
|
+
/**
|
|
3874
|
+
* A list of exception types that should not be reported.
|
|
3875
|
+
*/
|
|
3876
|
+
export const dontReport: string[] = [
|
|
3877
|
+
'ValidationException',
|
|
3878
|
+
'NotFoundException',
|
|
3879
|
+
]
|
|
3880
|
+
`;
|
|
3881
|
+
}
|
|
3882
|
+
generateBootstrap(context) {
|
|
3883
|
+
const spectrumImport = context.withSpectrum ? "import { SpectrumOrbit } from '@gravito/spectrum'\n" : "";
|
|
3884
|
+
const spectrumOrbit = context.withSpectrum ? " new SpectrumOrbit()," : "";
|
|
3885
|
+
return `/**
|
|
3886
|
+
* Application Bootstrap
|
|
3887
|
+
*
|
|
3888
|
+
* The entry point for your Gravito application.
|
|
3889
|
+
* Uses the ServiceProvider pattern for modular, maintainable initialization.
|
|
3890
|
+
*
|
|
3891
|
+
* Lifecycle:
|
|
3892
|
+
* 1. Configure: Load app config and orbits
|
|
3893
|
+
* 2. Boot: Initialize PlanetCore
|
|
3894
|
+
* 3. Register Providers: Bind services to container
|
|
3895
|
+
* 4. Bootstrap: Boot all providers
|
|
3896
|
+
*
|
|
3897
|
+
* @module bootstrap
|
|
3898
|
+
*/
|
|
2962
3899
|
|
|
2963
|
-
|
|
2964
|
-
|
|
3900
|
+
import { defineConfig, PlanetCore } from '@gravito/core'
|
|
3901
|
+
import { OrbitAtlas } from '@gravito/atlas'
|
|
3902
|
+
import appConfig from '../config/app'
|
|
3903
|
+
${spectrumImport}import {
|
|
3904
|
+
AppServiceProvider,
|
|
3905
|
+
DatabaseProvider,
|
|
3906
|
+
MiddlewareProvider,
|
|
3907
|
+
RouteProvider,
|
|
3908
|
+
} from './Providers'
|
|
3909
|
+
|
|
3910
|
+
/**
|
|
3911
|
+
* Bootstrap the application with service providers.
|
|
3912
|
+
*
|
|
3913
|
+
* @returns The booted PlanetCore instance
|
|
3914
|
+
*/
|
|
3915
|
+
export async function bootstrap() {
|
|
3916
|
+
// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
3917
|
+
// 1. Configure
|
|
3918
|
+
// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
3919
|
+
const config = defineConfig({
|
|
3920
|
+
config: appConfig,
|
|
3921
|
+
orbits: [
|
|
3922
|
+
new OrbitAtlas(),
|
|
3923
|
+
${spectrumOrbit}
|
|
3924
|
+
],
|
|
3925
|
+
})
|
|
3926
|
+
|
|
3927
|
+
// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
3928
|
+
// 2. Boot Core
|
|
3929
|
+
// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
3930
|
+
const core = await PlanetCore.boot(config)
|
|
3931
|
+
core.registerGlobalErrorHandlers()
|
|
3932
|
+
|
|
3933
|
+
// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
3934
|
+
// 3. Register Providers
|
|
3935
|
+
// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
3936
|
+
core.register(new AppServiceProvider())
|
|
3937
|
+
core.register(new DatabaseProvider())
|
|
3938
|
+
core.register(new MiddlewareProvider())
|
|
3939
|
+
core.register(new RouteProvider())
|
|
3940
|
+
|
|
3941
|
+
// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
3942
|
+
// 4. Bootstrap All Providers
|
|
3943
|
+
// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
3944
|
+
await core.bootstrap()
|
|
3945
|
+
|
|
3946
|
+
return core
|
|
3947
|
+
}
|
|
2965
3948
|
|
|
2966
|
-
//
|
|
3949
|
+
// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
3950
|
+
// Application Entry Point
|
|
3951
|
+
// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
3952
|
+
const core = await bootstrap()
|
|
2967
3953
|
export default core.liftoff()
|
|
2968
3954
|
`;
|
|
2969
3955
|
}
|
|
@@ -3126,51 +4112,1017 @@ Created with \u2764\uFE0F using Gravito Framework
|
|
|
3126
4112
|
}
|
|
3127
4113
|
};
|
|
3128
4114
|
|
|
3129
|
-
// src/
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
verbose;
|
|
3134
|
-
constructor(options = {}) {
|
|
3135
|
-
this.templatesDir = options.templatesDir ?? path3.resolve(__dirname, "../templates");
|
|
3136
|
-
this.verbose = options.verbose ?? false;
|
|
4115
|
+
// src/generators/SatelliteGenerator.ts
|
|
4116
|
+
var SatelliteGenerator = class extends BaseGenerator {
|
|
4117
|
+
get architectureType() {
|
|
4118
|
+
return "satellite";
|
|
3137
4119
|
}
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
4120
|
+
get displayName() {
|
|
4121
|
+
return "Gravito Satellite";
|
|
4122
|
+
}
|
|
4123
|
+
get description() {
|
|
4124
|
+
return "A modular plugin for Gravito following DDD and Clean Architecture";
|
|
4125
|
+
}
|
|
4126
|
+
getDirectoryStructure(context) {
|
|
4127
|
+
const name = context.namePascalCase;
|
|
3142
4128
|
return [
|
|
3143
4129
|
{
|
|
3144
|
-
type: "
|
|
3145
|
-
name: "
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
4130
|
+
type: "directory",
|
|
4131
|
+
name: "src",
|
|
4132
|
+
children: [
|
|
4133
|
+
// Domain Layer
|
|
4134
|
+
{
|
|
4135
|
+
type: "directory",
|
|
4136
|
+
name: "Domain",
|
|
4137
|
+
children: [
|
|
4138
|
+
{
|
|
4139
|
+
type: "directory",
|
|
4140
|
+
name: "Entities",
|
|
4141
|
+
children: [
|
|
4142
|
+
{ type: "file", name: `${name}.ts`, content: this.generateEntity(name) }
|
|
4143
|
+
]
|
|
4144
|
+
},
|
|
4145
|
+
{
|
|
4146
|
+
type: "directory",
|
|
4147
|
+
name: "Contracts",
|
|
4148
|
+
children: [
|
|
4149
|
+
{
|
|
4150
|
+
type: "file",
|
|
4151
|
+
name: `I${name}Repository.ts`,
|
|
4152
|
+
content: this.generateRepositoryInterface(name)
|
|
4153
|
+
}
|
|
4154
|
+
]
|
|
4155
|
+
},
|
|
4156
|
+
{ type: "directory", name: "ValueObjects", children: [] },
|
|
4157
|
+
{ type: "directory", name: "Events", children: [] }
|
|
4158
|
+
]
|
|
4159
|
+
},
|
|
4160
|
+
// Application Layer
|
|
4161
|
+
{
|
|
4162
|
+
type: "directory",
|
|
4163
|
+
name: "Application",
|
|
4164
|
+
children: [
|
|
4165
|
+
{
|
|
4166
|
+
type: "directory",
|
|
4167
|
+
name: "UseCases",
|
|
4168
|
+
children: [
|
|
4169
|
+
{
|
|
4170
|
+
type: "file",
|
|
4171
|
+
name: `Create${name}.ts`,
|
|
4172
|
+
content: this.generateUseCase(name)
|
|
4173
|
+
}
|
|
4174
|
+
]
|
|
4175
|
+
},
|
|
4176
|
+
{ type: "directory", name: "DTOs", children: [] }
|
|
4177
|
+
]
|
|
4178
|
+
},
|
|
4179
|
+
// Infrastructure Layer
|
|
4180
|
+
{
|
|
4181
|
+
type: "directory",
|
|
4182
|
+
name: "Infrastructure",
|
|
4183
|
+
children: [
|
|
4184
|
+
{
|
|
4185
|
+
type: "directory",
|
|
4186
|
+
name: "Persistence",
|
|
4187
|
+
children: [
|
|
4188
|
+
{
|
|
4189
|
+
type: "file",
|
|
4190
|
+
name: `Atlas${name}Repository.ts`,
|
|
4191
|
+
content: this.generateAtlasRepository(name)
|
|
4192
|
+
},
|
|
4193
|
+
{ type: "directory", name: "Migrations", children: [] }
|
|
4194
|
+
]
|
|
4195
|
+
}
|
|
4196
|
+
]
|
|
4197
|
+
},
|
|
4198
|
+
// Entry Point
|
|
4199
|
+
{ type: "file", name: "index.ts", content: this.generateEntryPoint(name) },
|
|
4200
|
+
{
|
|
4201
|
+
type: "file",
|
|
4202
|
+
name: "env.d.ts",
|
|
4203
|
+
content: "interface ImportMeta {\n readonly dir: string\n readonly path: string\n}\n"
|
|
4204
|
+
},
|
|
4205
|
+
{ type: "file", name: "manifest.json", content: this.generateManifest(context) }
|
|
4206
|
+
]
|
|
3152
4207
|
},
|
|
3153
4208
|
{
|
|
3154
|
-
type: "
|
|
3155
|
-
name: "
|
|
3156
|
-
|
|
4209
|
+
type: "directory",
|
|
4210
|
+
name: "tests",
|
|
4211
|
+
children: [
|
|
4212
|
+
{
|
|
4213
|
+
type: "file",
|
|
4214
|
+
name: "unit.test.ts",
|
|
4215
|
+
content: `import { describe, it, expect } from "bun:test";
|
|
4216
|
+
|
|
4217
|
+
describe("${name}", () => {
|
|
4218
|
+
it("should work", () => {
|
|
4219
|
+
expect(true).toBe(true);
|
|
4220
|
+
});
|
|
4221
|
+
});`
|
|
4222
|
+
}
|
|
4223
|
+
]
|
|
3157
4224
|
}
|
|
3158
4225
|
];
|
|
3159
4226
|
}
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
4227
|
+
// ─────────────────────────────────────────────────────────────
|
|
4228
|
+
// Domain Templates
|
|
4229
|
+
// ─────────────────────────────────────────────────────────────
|
|
4230
|
+
generateEntity(name) {
|
|
4231
|
+
return `import { Entity } from '@gravito/enterprise'
|
|
4232
|
+
|
|
4233
|
+
export interface ${name}Props {
|
|
4234
|
+
name: string
|
|
4235
|
+
createdAt: Date
|
|
4236
|
+
}
|
|
4237
|
+
|
|
4238
|
+
export class ${name} extends Entity<string> {
|
|
4239
|
+
constructor(id: string, private props: ${name}Props) {
|
|
4240
|
+
super(id)
|
|
4241
|
+
}
|
|
4242
|
+
|
|
4243
|
+
static create(id: string, name: string): ${name} {
|
|
4244
|
+
return new ${name}(id, {
|
|
4245
|
+
name,
|
|
4246
|
+
createdAt: new Date()
|
|
4247
|
+
})
|
|
4248
|
+
}
|
|
4249
|
+
|
|
4250
|
+
get name() { return this.props.name }
|
|
4251
|
+
}
|
|
4252
|
+
`;
|
|
4253
|
+
}
|
|
4254
|
+
generateRepositoryInterface(name) {
|
|
4255
|
+
return `import { Repository } from '@gravito/enterprise'
|
|
4256
|
+
import { ${name} } from '../Entities/${name}'
|
|
4257
|
+
|
|
4258
|
+
export interface I${name}Repository extends Repository<${name}, string> {
|
|
4259
|
+
// Add custom methods here
|
|
4260
|
+
}
|
|
4261
|
+
`;
|
|
4262
|
+
}
|
|
4263
|
+
// ─────────────────────────────────────────────────────────────
|
|
4264
|
+
// Application Templates
|
|
4265
|
+
// ─────────────────────────────────────────────────────────────
|
|
4266
|
+
generateUseCase(name) {
|
|
4267
|
+
return `import { UseCase } from '@gravito/enterprise'
|
|
4268
|
+
import { I${name}Repository } from '../../Domain/Contracts/I${name}Repository'
|
|
4269
|
+
import { ${name} } from '../../Domain/Entities/${name}'
|
|
4270
|
+
|
|
4271
|
+
export interface Create${name}Input {
|
|
4272
|
+
name: string
|
|
4273
|
+
}
|
|
4274
|
+
|
|
4275
|
+
export class Create${name} extends UseCase<Create${name}Input, string> {
|
|
4276
|
+
constructor(private repository: I${name}Repository) {
|
|
4277
|
+
super()
|
|
4278
|
+
}
|
|
4279
|
+
|
|
4280
|
+
async execute(input: Create${name}Input): Promise<string> {
|
|
4281
|
+
const id = crypto.randomUUID()
|
|
4282
|
+
const entity = ${name}.create(id, input.name)
|
|
4283
|
+
|
|
4284
|
+
await this.repository.save(entity)
|
|
4285
|
+
|
|
4286
|
+
return id
|
|
4287
|
+
}
|
|
4288
|
+
}
|
|
4289
|
+
`;
|
|
4290
|
+
}
|
|
4291
|
+
// ─────────────────────────────────────────────────────────────
|
|
4292
|
+
// Infrastructure Templates (Dogfooding Atlas)
|
|
4293
|
+
// ─────────────────────────────────────────────────────────────
|
|
4294
|
+
generateAtlasRepository(name) {
|
|
4295
|
+
return `import { I${name}Repository } from '../../Domain/Contracts/I${name}Repository'
|
|
4296
|
+
import { ${name} } from '../../Domain/Entities/${name}'
|
|
4297
|
+
import { DB } from '@gravito/atlas'
|
|
4298
|
+
|
|
4299
|
+
export class Atlas${name}Repository implements I${name}Repository {
|
|
4300
|
+
async save(entity: ${name}): Promise<void> {
|
|
4301
|
+
// Dogfooding: Use @gravito/atlas for persistence
|
|
4302
|
+
console.log('[Atlas] Saving entity:', entity.id)
|
|
4303
|
+
// await DB.table('${name.toLowerCase()}s').insert({ ... })
|
|
4304
|
+
}
|
|
4305
|
+
|
|
4306
|
+
async findById(id: string): Promise<${name} | null> {
|
|
4307
|
+
return null
|
|
4308
|
+
}
|
|
4309
|
+
|
|
4310
|
+
async findAll(): Promise<${name}[]> {
|
|
4311
|
+
return []
|
|
4312
|
+
}
|
|
4313
|
+
|
|
4314
|
+
async delete(id: string): Promise<void> {}
|
|
4315
|
+
|
|
4316
|
+
async exists(id: string): Promise<boolean> {
|
|
4317
|
+
return false
|
|
4318
|
+
}
|
|
4319
|
+
}
|
|
4320
|
+
`;
|
|
4321
|
+
}
|
|
4322
|
+
// ─────────────────────────────────────────────────────────────
|
|
4323
|
+
// Entry Point & Manifest
|
|
4324
|
+
// ─────────────────────────────────────────────────────────────
|
|
4325
|
+
generateEntryPoint(name) {
|
|
4326
|
+
return `import { ServiceProvider, type Container } from '@gravito/core'
|
|
4327
|
+
import { Atlas${name}Repository } from './Infrastructure/Persistence/Atlas${name}Repository'
|
|
4328
|
+
|
|
4329
|
+
export class ${name}ServiceProvider extends ServiceProvider {
|
|
4330
|
+
register(container: Container): void {
|
|
4331
|
+
// Bind Repository
|
|
4332
|
+
container.singleton('${name.toLowerCase()}.repo', () => new Atlas${name}Repository())
|
|
4333
|
+
|
|
4334
|
+
// Bind UseCases
|
|
4335
|
+
container.singleton('${name.toLowerCase()}.create', () => {
|
|
4336
|
+
return new (require('./Application/UseCases/Create${name}').Create${name})(
|
|
4337
|
+
container.make('${name.toLowerCase()}.repo')
|
|
4338
|
+
)
|
|
4339
|
+
})
|
|
4340
|
+
}
|
|
4341
|
+
|
|
4342
|
+
boot(): void {
|
|
4343
|
+
this.core?.logger.info('\u{1F6F0}\uFE0F Satellite ${name} is operational')
|
|
4344
|
+
}
|
|
4345
|
+
}
|
|
4346
|
+
`;
|
|
4347
|
+
}
|
|
4348
|
+
generateManifest(context) {
|
|
4349
|
+
return JSON.stringify(
|
|
4350
|
+
{
|
|
4351
|
+
name: context.name,
|
|
4352
|
+
id: context.nameKebabCase,
|
|
4353
|
+
version: "0.1.0",
|
|
4354
|
+
description: context.description || "A Gravito Satellite",
|
|
4355
|
+
capabilities: [`create-${context.nameKebabCase}`],
|
|
4356
|
+
requirements: [
|
|
4357
|
+
"cache"
|
|
4358
|
+
// Example requirement
|
|
4359
|
+
],
|
|
4360
|
+
hooks: [`${context.nameKebabCase}:created`]
|
|
4361
|
+
},
|
|
4362
|
+
null,
|
|
4363
|
+
2
|
|
4364
|
+
);
|
|
4365
|
+
}
|
|
4366
|
+
generatePackageJson(context) {
|
|
4367
|
+
const isInternal = context.isInternal || false;
|
|
4368
|
+
const depVersion = isInternal ? "workspace:*" : "^1.0.0-beta.1";
|
|
4369
|
+
const pkg = {
|
|
4370
|
+
name: isInternal ? `@gravito/satellite-${context.nameKebabCase}` : `gravito-satellite-${context.nameKebabCase}`,
|
|
4371
|
+
version: "0.1.0",
|
|
4372
|
+
type: "module",
|
|
4373
|
+
main: "dist/index.js",
|
|
4374
|
+
module: "dist/index.mjs",
|
|
4375
|
+
types: "dist/index.d.ts",
|
|
4376
|
+
scripts: {
|
|
4377
|
+
build: "tsup src/index.ts --format cjs,esm --dts",
|
|
4378
|
+
test: "bun test",
|
|
4379
|
+
typecheck: "tsc --noEmit",
|
|
4380
|
+
check: "bun run typecheck && bun run test",
|
|
4381
|
+
validate: "bun run check"
|
|
4382
|
+
},
|
|
4383
|
+
dependencies: {
|
|
4384
|
+
"@gravito/core": depVersion,
|
|
4385
|
+
"@gravito/enterprise": depVersion,
|
|
4386
|
+
"@gravito/atlas": depVersion,
|
|
4387
|
+
"@gravito/stasis": depVersion
|
|
4388
|
+
},
|
|
4389
|
+
devDependencies: {
|
|
4390
|
+
tsup: "^8.0.0",
|
|
4391
|
+
typescript: "^5.0.0"
|
|
4392
|
+
}
|
|
4393
|
+
};
|
|
4394
|
+
return JSON.stringify(pkg, null, 2);
|
|
4395
|
+
}
|
|
4396
|
+
generateArchitectureDoc(context) {
|
|
4397
|
+
return `# ${context.name} Satellite Architecture
|
|
4398
|
+
|
|
4399
|
+
This satellite follows the Gravito Satellite Specification v1.0.
|
|
4400
|
+
|
|
4401
|
+
## Design
|
|
4402
|
+
- **DDD**: Domain logic is separated from framework concerns.
|
|
4403
|
+
- **Dogfooding**: Uses official Gravito modules (@gravito/atlas, @gravito/stasis).
|
|
4404
|
+
- **Decoupled**: Inter-satellite communication happens via Contracts and Events.
|
|
4405
|
+
|
|
4406
|
+
## Layers
|
|
4407
|
+
- **Domain**: Pure business rules.
|
|
4408
|
+
- **Application**: Orchestration of domain tasks.
|
|
4409
|
+
- **Infrastructure**: Implementation of persistence and external services.
|
|
4410
|
+
- **Interface**: HTTP and Event entry points.
|
|
4411
|
+
`;
|
|
4412
|
+
}
|
|
4413
|
+
};
|
|
4414
|
+
|
|
4415
|
+
// src/LockGenerator.ts
|
|
4416
|
+
var LockGenerator = class {
|
|
4417
|
+
generate(profileName, config, templateName = "basic", templateVersion = "1.0.0") {
|
|
4418
|
+
const lock = {
|
|
4419
|
+
profile: {
|
|
4420
|
+
name: profileName,
|
|
4421
|
+
version: "1.0.0"
|
|
4422
|
+
// This could be dynamic based on profile system version
|
|
4423
|
+
},
|
|
4424
|
+
features: config.features,
|
|
4425
|
+
drivers: config.drivers,
|
|
4426
|
+
manifest: {
|
|
4427
|
+
template: templateName,
|
|
4428
|
+
version: templateVersion
|
|
4429
|
+
},
|
|
4430
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4431
|
+
};
|
|
4432
|
+
return JSON.stringify(lock, null, 2);
|
|
4433
|
+
}
|
|
4434
|
+
};
|
|
4435
|
+
|
|
4436
|
+
// src/ProfileResolver.ts
|
|
4437
|
+
var ProfileResolver = class _ProfileResolver {
|
|
4438
|
+
static DEFAULTS = {
|
|
4439
|
+
core: {
|
|
4440
|
+
drivers: {
|
|
4441
|
+
database: "sqlite",
|
|
4442
|
+
cache: "memory",
|
|
4443
|
+
queue: "sync",
|
|
4444
|
+
storage: "local",
|
|
4445
|
+
session: "file"
|
|
4446
|
+
},
|
|
4447
|
+
features: []
|
|
4448
|
+
},
|
|
4449
|
+
scale: {
|
|
4450
|
+
drivers: {
|
|
4451
|
+
database: "postgresql",
|
|
4452
|
+
cache: "redis",
|
|
4453
|
+
queue: "redis",
|
|
4454
|
+
storage: "s3",
|
|
4455
|
+
session: "redis"
|
|
4456
|
+
},
|
|
4457
|
+
features: ["stream", "nebula"]
|
|
4458
|
+
},
|
|
4459
|
+
enterprise: {
|
|
4460
|
+
drivers: {
|
|
4461
|
+
database: "postgresql",
|
|
4462
|
+
cache: "redis",
|
|
4463
|
+
queue: "redis",
|
|
4464
|
+
storage: "s3",
|
|
4465
|
+
session: "redis"
|
|
4466
|
+
},
|
|
4467
|
+
features: ["stream", "nebula", "monitor", "sentinel", "fortify"]
|
|
4468
|
+
}
|
|
4469
|
+
};
|
|
4470
|
+
resolve(profile = "core", withFeatures = []) {
|
|
4471
|
+
const base = _ProfileResolver.DEFAULTS[profile] || _ProfileResolver.DEFAULTS.core;
|
|
4472
|
+
const config = {
|
|
4473
|
+
drivers: { ...base.drivers },
|
|
4474
|
+
features: [...base.features]
|
|
4475
|
+
};
|
|
4476
|
+
for (const feature of withFeatures) {
|
|
4477
|
+
this.applyFeature(config, feature);
|
|
4478
|
+
}
|
|
4479
|
+
return config;
|
|
4480
|
+
}
|
|
4481
|
+
applyFeature(config, feature) {
|
|
4482
|
+
switch (feature) {
|
|
4483
|
+
case "redis":
|
|
4484
|
+
config.drivers.cache = "redis";
|
|
4485
|
+
config.drivers.queue = "redis";
|
|
4486
|
+
config.drivers.session = "redis";
|
|
4487
|
+
break;
|
|
4488
|
+
case "postgres":
|
|
4489
|
+
case "postgresql":
|
|
4490
|
+
config.drivers.database = "postgresql";
|
|
4491
|
+
break;
|
|
4492
|
+
case "mysql":
|
|
4493
|
+
config.drivers.database = "mysql";
|
|
4494
|
+
break;
|
|
4495
|
+
case "s3":
|
|
4496
|
+
config.drivers.storage = "s3";
|
|
4497
|
+
break;
|
|
4498
|
+
case "r2":
|
|
4499
|
+
config.drivers.storage = "r2";
|
|
4500
|
+
break;
|
|
4501
|
+
case "queue":
|
|
4502
|
+
if (config.drivers.queue === "sync") {
|
|
4503
|
+
config.drivers.queue = "redis";
|
|
4504
|
+
}
|
|
4505
|
+
break;
|
|
4506
|
+
default:
|
|
4507
|
+
if (!config.features.includes(feature)) {
|
|
4508
|
+
config.features.push(feature);
|
|
4509
|
+
}
|
|
4510
|
+
}
|
|
4511
|
+
}
|
|
4512
|
+
/**
|
|
4513
|
+
* Validator for profile names
|
|
4514
|
+
*/
|
|
4515
|
+
isValidProfile(profile) {
|
|
4516
|
+
return profile in _ProfileResolver.DEFAULTS;
|
|
4517
|
+
}
|
|
4518
|
+
/**
|
|
4519
|
+
* Validator for feature names
|
|
4520
|
+
*/
|
|
4521
|
+
isValidFeature(feature) {
|
|
4522
|
+
const validFeatures = [
|
|
4523
|
+
"redis",
|
|
4524
|
+
"postgres",
|
|
4525
|
+
"postgresql",
|
|
4526
|
+
"mysql",
|
|
4527
|
+
"s3",
|
|
4528
|
+
"r2",
|
|
4529
|
+
"queue",
|
|
4530
|
+
"stream",
|
|
4531
|
+
"nebula",
|
|
4532
|
+
"monitor",
|
|
4533
|
+
"sentinel",
|
|
4534
|
+
"fortify"
|
|
4535
|
+
];
|
|
4536
|
+
return validFeatures.includes(feature);
|
|
4537
|
+
}
|
|
4538
|
+
};
|
|
4539
|
+
|
|
4540
|
+
// src/Scaffold.ts
|
|
4541
|
+
import path3 from "path";
|
|
4542
|
+
|
|
4543
|
+
// src/generators/ActionDomainGenerator.ts
|
|
4544
|
+
var ActionDomainGenerator = class extends BaseGenerator {
|
|
4545
|
+
get architectureType() {
|
|
4546
|
+
return "action-domain";
|
|
4547
|
+
}
|
|
4548
|
+
get displayName() {
|
|
4549
|
+
return "Action Domain";
|
|
4550
|
+
}
|
|
4551
|
+
get description() {
|
|
4552
|
+
return "Single-responsibility Action pattern for clear business logic separation";
|
|
4553
|
+
}
|
|
4554
|
+
getDirectoryStructure(context) {
|
|
4555
|
+
return [
|
|
4556
|
+
{
|
|
4557
|
+
type: "directory",
|
|
4558
|
+
name: "config",
|
|
4559
|
+
children: [
|
|
4560
|
+
{ type: "file", name: "app.ts", content: this.generateAppConfig(context) },
|
|
4561
|
+
{ type: "file", name: "database.ts", content: this.generateDatabaseConfig() }
|
|
4562
|
+
]
|
|
4563
|
+
},
|
|
4564
|
+
{
|
|
4565
|
+
type: "directory",
|
|
4566
|
+
name: "database",
|
|
4567
|
+
children: [{ type: "file", name: ".gitkeep", content: "" }]
|
|
4568
|
+
},
|
|
4569
|
+
{
|
|
4570
|
+
type: "directory",
|
|
4571
|
+
name: "src",
|
|
4572
|
+
children: [
|
|
4573
|
+
{
|
|
4574
|
+
type: "directory",
|
|
4575
|
+
name: "actions",
|
|
4576
|
+
children: [
|
|
4577
|
+
{ type: "file", name: "Action.ts", content: this.generateActionBase() },
|
|
4578
|
+
{
|
|
4579
|
+
type: "directory",
|
|
4580
|
+
name: "server",
|
|
4581
|
+
children: [
|
|
4582
|
+
{
|
|
4583
|
+
type: "file",
|
|
4584
|
+
name: "GetServerStatusAction.ts",
|
|
4585
|
+
content: this.generateGetServerStatusAction()
|
|
4586
|
+
}
|
|
4587
|
+
]
|
|
4588
|
+
}
|
|
4589
|
+
]
|
|
4590
|
+
},
|
|
4591
|
+
{
|
|
4592
|
+
type: "directory",
|
|
4593
|
+
name: "controllers",
|
|
4594
|
+
children: [
|
|
4595
|
+
{
|
|
4596
|
+
type: "directory",
|
|
4597
|
+
name: "api",
|
|
4598
|
+
children: [
|
|
4599
|
+
{
|
|
4600
|
+
type: "directory",
|
|
4601
|
+
name: "v1",
|
|
4602
|
+
children: [
|
|
4603
|
+
{
|
|
4604
|
+
type: "file",
|
|
4605
|
+
name: "ServerController.ts",
|
|
4606
|
+
content: this.generateServerController()
|
|
4607
|
+
}
|
|
4608
|
+
]
|
|
4609
|
+
}
|
|
4610
|
+
]
|
|
4611
|
+
}
|
|
4612
|
+
]
|
|
4613
|
+
},
|
|
4614
|
+
{
|
|
4615
|
+
type: "directory",
|
|
4616
|
+
name: "types",
|
|
4617
|
+
children: [
|
|
4618
|
+
{
|
|
4619
|
+
type: "directory",
|
|
4620
|
+
name: "requests",
|
|
4621
|
+
children: [
|
|
4622
|
+
{
|
|
4623
|
+
type: "directory",
|
|
4624
|
+
name: "api",
|
|
4625
|
+
children: [
|
|
4626
|
+
{
|
|
4627
|
+
type: "directory",
|
|
4628
|
+
name: "v1",
|
|
4629
|
+
children: [{ type: "file", name: ".gitkeep", content: "" }]
|
|
4630
|
+
}
|
|
4631
|
+
]
|
|
4632
|
+
}
|
|
4633
|
+
]
|
|
4634
|
+
},
|
|
4635
|
+
{
|
|
4636
|
+
type: "directory",
|
|
4637
|
+
name: "responses",
|
|
4638
|
+
children: [
|
|
4639
|
+
{
|
|
4640
|
+
type: "directory",
|
|
4641
|
+
name: "api",
|
|
4642
|
+
children: [
|
|
4643
|
+
{
|
|
4644
|
+
type: "directory",
|
|
4645
|
+
name: "v1",
|
|
4646
|
+
children: [
|
|
4647
|
+
{
|
|
4648
|
+
type: "file",
|
|
4649
|
+
name: "ServerStatusResponse.ts",
|
|
4650
|
+
content: this.generateServerStatusResponse()
|
|
4651
|
+
}
|
|
4652
|
+
]
|
|
4653
|
+
}
|
|
4654
|
+
]
|
|
4655
|
+
}
|
|
4656
|
+
]
|
|
4657
|
+
}
|
|
4658
|
+
]
|
|
4659
|
+
},
|
|
4660
|
+
{
|
|
4661
|
+
type: "directory",
|
|
4662
|
+
name: "models",
|
|
4663
|
+
children: [{ type: "file", name: "User.ts", content: this.generateUserModel() }]
|
|
4664
|
+
},
|
|
4665
|
+
{
|
|
4666
|
+
type: "directory",
|
|
4667
|
+
name: "repositories",
|
|
4668
|
+
children: [{ type: "file", name: ".gitkeep", content: "" }]
|
|
4669
|
+
},
|
|
4670
|
+
{
|
|
4671
|
+
type: "directory",
|
|
4672
|
+
name: "routes",
|
|
4673
|
+
children: [{ type: "file", name: "api.ts", content: this.generateApiRoutes() }]
|
|
4674
|
+
},
|
|
4675
|
+
{
|
|
4676
|
+
type: "directory",
|
|
4677
|
+
name: "providers",
|
|
4678
|
+
children: [
|
|
4679
|
+
{
|
|
4680
|
+
type: "file",
|
|
4681
|
+
name: "index.ts",
|
|
4682
|
+
content: this.generateProvidersIndex()
|
|
4683
|
+
},
|
|
4684
|
+
{
|
|
4685
|
+
type: "file",
|
|
4686
|
+
name: "AppServiceProvider.ts",
|
|
4687
|
+
content: this.generateAppServiceProvider(context)
|
|
4688
|
+
},
|
|
4689
|
+
{
|
|
4690
|
+
type: "file",
|
|
4691
|
+
name: "MiddlewareProvider.ts",
|
|
4692
|
+
content: this.generateMiddlewareProvider()
|
|
4693
|
+
},
|
|
4694
|
+
{
|
|
4695
|
+
type: "file",
|
|
4696
|
+
name: "RouteProvider.ts",
|
|
4697
|
+
content: this.generateRouteProvider()
|
|
4698
|
+
}
|
|
4699
|
+
]
|
|
4700
|
+
},
|
|
4701
|
+
{ type: "file", name: "bootstrap.ts", content: this.generateBootstrap(context) }
|
|
4702
|
+
]
|
|
4703
|
+
},
|
|
4704
|
+
{
|
|
4705
|
+
type: "directory",
|
|
4706
|
+
name: "tests",
|
|
4707
|
+
children: [{ type: "file", name: ".gitkeep", content: "" }]
|
|
4708
|
+
}
|
|
4709
|
+
];
|
|
4710
|
+
}
|
|
4711
|
+
// ─────────────────────────────────────────────────────────────
|
|
4712
|
+
// Config Generators
|
|
4713
|
+
// ─────────────────────────────────────────────────────────────
|
|
4714
|
+
generateAppConfig(context) {
|
|
4715
|
+
return `export default {
|
|
4716
|
+
name: process.env.APP_NAME ?? '${context.name}',
|
|
4717
|
+
env: process.env.APP_ENV ?? 'development',
|
|
4718
|
+
debug: process.env.APP_DEBUG === 'true',
|
|
4719
|
+
url: process.env.APP_URL ?? 'http://localhost:3000',
|
|
4720
|
+
key: process.env.APP_KEY,
|
|
4721
|
+
}
|
|
4722
|
+
`;
|
|
4723
|
+
}
|
|
4724
|
+
generateDatabaseConfig() {
|
|
4725
|
+
return `export default {
|
|
4726
|
+
default: process.env.DB_CONNECTION ?? 'sqlite',
|
|
4727
|
+
connections: {
|
|
4728
|
+
sqlite: {
|
|
4729
|
+
driver: 'sqlite',
|
|
4730
|
+
database: process.env.DB_DATABASE ?? 'database/database.sqlite',
|
|
4731
|
+
},
|
|
4732
|
+
},
|
|
4733
|
+
}
|
|
4734
|
+
`;
|
|
4735
|
+
}
|
|
4736
|
+
// ─────────────────────────────────────────────────────────────
|
|
4737
|
+
// Model Generators
|
|
4738
|
+
// ─────────────────────────────────────────────────────────────
|
|
4739
|
+
generateUserModel() {
|
|
4740
|
+
return `/**
|
|
4741
|
+
* User Model
|
|
4742
|
+
*/
|
|
4743
|
+
|
|
4744
|
+
import { Model, column } from '@gravito/atlas'
|
|
4745
|
+
|
|
4746
|
+
export class User extends Model {
|
|
4747
|
+
static table = 'users'
|
|
4748
|
+
|
|
4749
|
+
@column({ isPrimary: true })
|
|
4750
|
+
id!: number
|
|
4751
|
+
|
|
4752
|
+
@column()
|
|
4753
|
+
name!: string
|
|
4754
|
+
|
|
4755
|
+
@column()
|
|
4756
|
+
email!: string
|
|
4757
|
+
|
|
4758
|
+
@column()
|
|
4759
|
+
created_at!: Date
|
|
4760
|
+
|
|
4761
|
+
@column()
|
|
4762
|
+
updated_at!: Date
|
|
4763
|
+
}
|
|
4764
|
+
`;
|
|
4765
|
+
}
|
|
4766
|
+
// ─────────────────────────────────────────────────────────────
|
|
4767
|
+
// Action Generators
|
|
4768
|
+
// ─────────────────────────────────────────────────────────────
|
|
4769
|
+
generateActionBase() {
|
|
4770
|
+
return `/**
|
|
4771
|
+
* Action Base Class
|
|
4772
|
+
*
|
|
4773
|
+
* All business logic actions should extend this class.
|
|
4774
|
+
* It enforces a consistent execution method.
|
|
4775
|
+
*/
|
|
4776
|
+
|
|
4777
|
+
export abstract class Action<TInput = unknown, TOutput = unknown> {
|
|
4778
|
+
/**
|
|
4779
|
+
* Execute the business logic.
|
|
4780
|
+
*/
|
|
4781
|
+
abstract execute(input: TInput): Promise<TOutput> | TOutput
|
|
4782
|
+
}
|
|
4783
|
+
`;
|
|
4784
|
+
}
|
|
4785
|
+
generateGetServerStatusAction() {
|
|
4786
|
+
return `/**
|
|
4787
|
+
* Get Server Status Action
|
|
4788
|
+
*/
|
|
4789
|
+
|
|
4790
|
+
import { Action } from '../Action'
|
|
4791
|
+
import type { ServerStatusResponse } from '../../types/responses/api/v1/ServerStatusResponse'
|
|
4792
|
+
|
|
4793
|
+
export class GetServerStatusAction extends Action<void, ServerStatusResponse> {
|
|
4794
|
+
execute(): Promise<ServerStatusResponse> {
|
|
4795
|
+
return Promise.resolve({
|
|
4796
|
+
status: 'active',
|
|
4797
|
+
timestamp: new Date().toISOString(),
|
|
4798
|
+
service: 'Gravito Hub'
|
|
4799
|
+
})
|
|
4800
|
+
}
|
|
4801
|
+
}
|
|
4802
|
+
`;
|
|
4803
|
+
}
|
|
4804
|
+
// ─────────────────────────────────────────────────────────────
|
|
4805
|
+
// Controller Generators
|
|
4806
|
+
// ─────────────────────────────────────────────────────────────
|
|
4807
|
+
generateServerController() {
|
|
4808
|
+
return `/**
|
|
4809
|
+
* Server Controller
|
|
4810
|
+
*/
|
|
4811
|
+
|
|
4812
|
+
import type { GravitoContext } from '@gravito/core'
|
|
4813
|
+
import { GetServerStatusAction } from '../../../actions/server/GetServerStatusAction'
|
|
4814
|
+
|
|
4815
|
+
export class ServerController {
|
|
4816
|
+
/**
|
|
4817
|
+
* GET /v1/server/status
|
|
4818
|
+
*/
|
|
4819
|
+
async status(c: GravitoContext) {
|
|
4820
|
+
const action = new GetServerStatusAction()
|
|
4821
|
+
const result = await action.execute()
|
|
4822
|
+
|
|
4823
|
+
return c.json({
|
|
4824
|
+
success: true,
|
|
4825
|
+
data: result
|
|
4826
|
+
})
|
|
4827
|
+
}
|
|
4828
|
+
}
|
|
4829
|
+
`;
|
|
4830
|
+
}
|
|
4831
|
+
// ─────────────────────────────────────────────────────────────
|
|
4832
|
+
// Type Generators
|
|
4833
|
+
// ─────────────────────────────────────────────────────────────
|
|
4834
|
+
generateServerStatusResponse() {
|
|
4835
|
+
return `/**
|
|
4836
|
+
* Server Status Response Type
|
|
4837
|
+
*/
|
|
4838
|
+
|
|
4839
|
+
export interface ServerStatusResponse {
|
|
4840
|
+
status: string
|
|
4841
|
+
timestamp: string
|
|
4842
|
+
service: string
|
|
4843
|
+
}
|
|
4844
|
+
`;
|
|
4845
|
+
}
|
|
4846
|
+
// ─────────────────────────────────────────────────────────────
|
|
4847
|
+
// Routes & Bootstrap
|
|
4848
|
+
// ─────────────────────────────────────────────────────────────
|
|
4849
|
+
generateApiRoutes() {
|
|
4850
|
+
return `/**
|
|
4851
|
+
* API Routes Registration
|
|
4852
|
+
*/
|
|
4853
|
+
|
|
4854
|
+
import type { Router } from '@gravito/core'
|
|
4855
|
+
import { ServerController } from '../controllers/api/v1/ServerController'
|
|
4856
|
+
|
|
4857
|
+
export function registerApiRoutes(router: Router) {
|
|
4858
|
+
const server = new ServerController()
|
|
4859
|
+
|
|
4860
|
+
router.prefix('/v1').group((group) => {
|
|
4861
|
+
// Server Domain
|
|
4862
|
+
group.get('/server/status', (c) => server.status(c))
|
|
4863
|
+
})
|
|
4864
|
+
}
|
|
4865
|
+
`;
|
|
4866
|
+
}
|
|
4867
|
+
generateAppServiceProvider(context) {
|
|
4868
|
+
return `/**
|
|
4869
|
+
* App Service Provider
|
|
4870
|
+
*/
|
|
4871
|
+
|
|
4872
|
+
import { ServiceProvider, type Container, type PlanetCore } from '@gravito/core'
|
|
4873
|
+
|
|
4874
|
+
export class AppServiceProvider extends ServiceProvider {
|
|
4875
|
+
register(_container: Container): void {
|
|
4876
|
+
// Register global services here
|
|
4877
|
+
}
|
|
4878
|
+
|
|
4879
|
+
boot(core: PlanetCore): void {
|
|
4880
|
+
core.logger.info('${context.name} (Action Domain) booted!')
|
|
4881
|
+
}
|
|
4882
|
+
}
|
|
4883
|
+
`;
|
|
4884
|
+
}
|
|
4885
|
+
generateProvidersIndex() {
|
|
4886
|
+
return `/**
|
|
4887
|
+
* Application Service Providers
|
|
4888
|
+
*/
|
|
4889
|
+
|
|
4890
|
+
export { AppServiceProvider } from './AppServiceProvider'
|
|
4891
|
+
export { MiddlewareProvider } from './MiddlewareProvider'
|
|
4892
|
+
export { RouteProvider } from './RouteProvider'
|
|
4893
|
+
`;
|
|
4894
|
+
}
|
|
4895
|
+
generateMiddlewareProvider() {
|
|
4896
|
+
return `/**
|
|
4897
|
+
* Middleware Service Provider
|
|
4898
|
+
*/
|
|
4899
|
+
|
|
4900
|
+
import {
|
|
4901
|
+
ServiceProvider,
|
|
4902
|
+
type Container,
|
|
4903
|
+
type PlanetCore,
|
|
4904
|
+
bodySizeLimit,
|
|
4905
|
+
securityHeaders,
|
|
4906
|
+
} from '@gravito/core'
|
|
4907
|
+
|
|
4908
|
+
export class MiddlewareProvider extends ServiceProvider {
|
|
4909
|
+
register(_container: Container): void {}
|
|
4910
|
+
|
|
4911
|
+
boot(core: PlanetCore): void {
|
|
4912
|
+
core.adapter.use('*', securityHeaders())
|
|
4913
|
+
core.adapter.use('*', bodySizeLimit(1024 * 1024))
|
|
4914
|
+
core.logger.info('\u{1F6E1}\uFE0F Middleware registered')
|
|
4915
|
+
}
|
|
4916
|
+
}
|
|
4917
|
+
`;
|
|
4918
|
+
}
|
|
4919
|
+
generateRouteProvider() {
|
|
4920
|
+
return `/**
|
|
4921
|
+
* Route Service Provider
|
|
4922
|
+
*/
|
|
4923
|
+
|
|
4924
|
+
import { ServiceProvider, type Container, type PlanetCore } from '@gravito/core'
|
|
4925
|
+
import { registerApiRoutes } from '../routes/api'
|
|
4926
|
+
|
|
4927
|
+
export class RouteProvider extends ServiceProvider {
|
|
4928
|
+
register(_container: Container): void {}
|
|
4929
|
+
|
|
4930
|
+
boot(core: PlanetCore): void {
|
|
4931
|
+
registerApiRoutes(core.router)
|
|
4932
|
+
core.logger.info('\u{1F6E4}\uFE0F Routes registered')
|
|
4933
|
+
}
|
|
4934
|
+
}
|
|
4935
|
+
`;
|
|
4936
|
+
}
|
|
4937
|
+
generateBootstrap(_context) {
|
|
4938
|
+
return `/**
|
|
4939
|
+
* Application Bootstrap
|
|
4940
|
+
*
|
|
4941
|
+
* Uses the ServiceProvider pattern for modular initialization.
|
|
4942
|
+
*/
|
|
4943
|
+
|
|
4944
|
+
import { defineConfig, PlanetCore } from '@gravito/core'
|
|
4945
|
+
import { OrbitAtlas } from '@gravito/atlas'
|
|
4946
|
+
import appConfig from '../config/app'
|
|
4947
|
+
import {
|
|
4948
|
+
AppServiceProvider,
|
|
4949
|
+
MiddlewareProvider,
|
|
4950
|
+
RouteProvider,
|
|
4951
|
+
} from './providers'
|
|
4952
|
+
|
|
4953
|
+
export async function bootstrap() {
|
|
4954
|
+
const config = defineConfig({
|
|
4955
|
+
config: appConfig,
|
|
4956
|
+
orbits: [new OrbitAtlas()],
|
|
4957
|
+
})
|
|
4958
|
+
|
|
4959
|
+
const core = await PlanetCore.boot(config)
|
|
4960
|
+
core.registerGlobalErrorHandlers()
|
|
4961
|
+
|
|
4962
|
+
core.register(new AppServiceProvider())
|
|
4963
|
+
core.register(new MiddlewareProvider())
|
|
4964
|
+
core.register(new RouteProvider())
|
|
4965
|
+
|
|
4966
|
+
await core.bootstrap()
|
|
4967
|
+
|
|
4968
|
+
return core
|
|
4969
|
+
}
|
|
4970
|
+
|
|
4971
|
+
const core = await bootstrap()
|
|
4972
|
+
export default core.liftoff()
|
|
4973
|
+
`;
|
|
4974
|
+
}
|
|
4975
|
+
generateArchitectureDoc(context) {
|
|
4976
|
+
return `# ${context.name} - Action Domain Architecture
|
|
4977
|
+
|
|
4978
|
+
## Overview
|
|
4979
|
+
|
|
4980
|
+
This project uses the **Action Domain** pattern, designed for high-clarity API implementations.
|
|
4981
|
+
|
|
4982
|
+
## Service Providers
|
|
4983
|
+
|
|
4984
|
+
Service providers are the central place to configure your application. They follow the ServiceProvider pattern with \`register()\` and \`boot()\` lifecycle methods.
|
|
4985
|
+
|
|
4986
|
+
## Directory Structure
|
|
4987
|
+
|
|
4988
|
+
\`\`\`
|
|
4989
|
+
src/
|
|
4990
|
+
\u251C\u2500\u2500 actions/ # Single Responsibility Business Logic
|
|
4991
|
+
\u2502 \u251C\u2500\u2500 Action.ts # Base Action class
|
|
4992
|
+
\u2502 \u2514\u2500\u2500 [Domain]/ # Domain-specific actions
|
|
4993
|
+
\u251C\u2500\u2500 controllers/ # HTTP Request Handlers
|
|
4994
|
+
\u2502 \u2514\u2500\u2500 api/v1/ # API Controllers
|
|
4995
|
+
\u251C\u2500\u2500 types/ # TypeScript Definitions
|
|
4996
|
+
\u251C\u2500\u2500 repositories/ # Data Access Layer
|
|
4997
|
+
\u251C\u2500\u2500 routes/ # Route Definitions
|
|
4998
|
+
\u251C\u2500\u2500 providers/ # Service Providers
|
|
4999
|
+
\u2514\u2500\u2500 config/ # Configuration
|
|
5000
|
+
\`\`\`
|
|
5001
|
+
|
|
5002
|
+
## Core Concepts
|
|
5003
|
+
|
|
5004
|
+
### Actions
|
|
5005
|
+
Every business operation is an "Action". An action:
|
|
5006
|
+
- Does ONE thing.
|
|
5007
|
+
- Takes specific input.
|
|
5008
|
+
- Returns specific output.
|
|
5009
|
+
- Is framework-agnostic (ideally).
|
|
5010
|
+
|
|
5011
|
+
### Controllers
|
|
5012
|
+
Controllers are thin. They:
|
|
5013
|
+
1. Parse the request.
|
|
5014
|
+
2. Instantiate/Call the Action.
|
|
5015
|
+
3. Return the response.
|
|
5016
|
+
|
|
5017
|
+
Created with \u2764\uFE0F using Gravito Framework
|
|
5018
|
+
`;
|
|
5019
|
+
}
|
|
5020
|
+
generatePackageJson(context) {
|
|
5021
|
+
const pkg = {
|
|
5022
|
+
name: context.nameKebabCase,
|
|
5023
|
+
version: "0.1.0",
|
|
5024
|
+
type: "module",
|
|
5025
|
+
scripts: {
|
|
5026
|
+
dev: "bun run --watch src/bootstrap.ts",
|
|
5027
|
+
build: "bun build ./src/bootstrap.ts --outdir ./dist --target bun",
|
|
5028
|
+
start: "bun run dist/bootstrap.js",
|
|
5029
|
+
test: "bun test",
|
|
5030
|
+
typecheck: "tsc --noEmit",
|
|
5031
|
+
check: "bun run typecheck && bun run test",
|
|
5032
|
+
"check:deps": "bun run scripts/check-dependencies.ts",
|
|
5033
|
+
validate: "bun run check && bun run check:deps",
|
|
5034
|
+
precommit: "bun run validate"
|
|
5035
|
+
},
|
|
5036
|
+
dependencies: {
|
|
5037
|
+
"@gravito/core": "workspace:*",
|
|
5038
|
+
"@gravito/enterprise": "workspace:*",
|
|
5039
|
+
"@gravito/atlas": "workspace:*"
|
|
5040
|
+
// Usually needed for repositories
|
|
5041
|
+
},
|
|
5042
|
+
devDependencies: {
|
|
5043
|
+
"bun-types": "latest",
|
|
5044
|
+
typescript: "^5.0.0"
|
|
5045
|
+
}
|
|
5046
|
+
};
|
|
5047
|
+
return JSON.stringify(pkg, null, 2);
|
|
5048
|
+
}
|
|
5049
|
+
};
|
|
5050
|
+
|
|
5051
|
+
// src/Scaffold.ts
|
|
5052
|
+
var Scaffold = class {
|
|
5053
|
+
templatesDir;
|
|
5054
|
+
verbose;
|
|
5055
|
+
constructor(options = {}) {
|
|
5056
|
+
this.templatesDir = options.templatesDir ?? path3.resolve(__dirname, "../templates");
|
|
5057
|
+
this.verbose = options.verbose ?? false;
|
|
5058
|
+
}
|
|
5059
|
+
/**
|
|
5060
|
+
* Get all available architecture types.
|
|
5061
|
+
*/
|
|
5062
|
+
getArchitectureTypes() {
|
|
5063
|
+
return [
|
|
5064
|
+
{
|
|
5065
|
+
type: "enterprise-mvc",
|
|
5066
|
+
name: "Enterprise MVC",
|
|
5067
|
+
description: "Laravel-inspired MVC with Services and Repositories"
|
|
5068
|
+
},
|
|
5069
|
+
{
|
|
5070
|
+
type: "clean",
|
|
5071
|
+
name: "Clean Architecture",
|
|
5072
|
+
description: "Uncle Bob's Clean Architecture with strict dependency rules"
|
|
5073
|
+
},
|
|
5074
|
+
{
|
|
5075
|
+
type: "ddd",
|
|
5076
|
+
name: "Domain-Driven Design",
|
|
5077
|
+
description: "Full DDD with Bounded Contexts and CQRS"
|
|
5078
|
+
},
|
|
5079
|
+
{
|
|
5080
|
+
type: "action-domain",
|
|
5081
|
+
name: "Action Domain",
|
|
5082
|
+
description: "Single Action Controllers pattern for high-clarity APIs"
|
|
5083
|
+
},
|
|
5084
|
+
{
|
|
5085
|
+
type: "satellite",
|
|
5086
|
+
name: "Gravito Satellite",
|
|
5087
|
+
description: "Plug-and-play module for the Gravito ecosystem"
|
|
5088
|
+
}
|
|
5089
|
+
];
|
|
5090
|
+
}
|
|
5091
|
+
/**
|
|
5092
|
+
* Create a new project scaffold.
|
|
5093
|
+
*/
|
|
5094
|
+
async create(options) {
|
|
5095
|
+
const generator = this.createGenerator(options.architecture);
|
|
5096
|
+
const fs3 = await import("fs/promises");
|
|
5097
|
+
const profileResolver = new ProfileResolver();
|
|
5098
|
+
const profileConfig = profileResolver.resolve(options.profile, options.features);
|
|
5099
|
+
const context = BaseGenerator.createContext(
|
|
5100
|
+
options.name,
|
|
5101
|
+
options.targetDir,
|
|
5102
|
+
options.architecture,
|
|
5103
|
+
options.packageManager ?? "bun",
|
|
5104
|
+
{
|
|
5105
|
+
...options.context,
|
|
5106
|
+
withSpectrum: options.withSpectrum ?? false,
|
|
5107
|
+
isInternal: options.isInternal ?? false,
|
|
5108
|
+
profile: options.profile ?? "core",
|
|
5109
|
+
features: options.features ?? [],
|
|
5110
|
+
profileConfig
|
|
5111
|
+
}
|
|
3171
5112
|
);
|
|
3172
5113
|
try {
|
|
3173
5114
|
const filesCreated = await generator.generate(context);
|
|
5115
|
+
const lockGenerator = new LockGenerator();
|
|
5116
|
+
const lockContent = lockGenerator.generate(
|
|
5117
|
+
options.profile ?? "core",
|
|
5118
|
+
profileConfig,
|
|
5119
|
+
"basic",
|
|
5120
|
+
// Default template for now, should come from options if applicable
|
|
5121
|
+
"1.0.0"
|
|
5122
|
+
);
|
|
5123
|
+
const lockPath = path3.resolve(options.targetDir, "gravito.lock.json");
|
|
5124
|
+
await fs3.writeFile(lockPath, lockContent, "utf-8");
|
|
5125
|
+
filesCreated.push(lockPath);
|
|
3174
5126
|
return {
|
|
3175
5127
|
success: true,
|
|
3176
5128
|
targetDir: options.targetDir,
|
|
@@ -3200,6 +5152,10 @@ var Scaffold = class {
|
|
|
3200
5152
|
return new CleanArchitectureGenerator(config);
|
|
3201
5153
|
case "ddd":
|
|
3202
5154
|
return new DddGenerator(config);
|
|
5155
|
+
case "action-domain":
|
|
5156
|
+
return new ActionDomainGenerator(config);
|
|
5157
|
+
case "satellite":
|
|
5158
|
+
return new SatelliteGenerator(config);
|
|
3203
5159
|
default:
|
|
3204
5160
|
throw new Error(`Unknown architecture type: ${type}`);
|
|
3205
5161
|
}
|
|
@@ -3222,6 +5178,11 @@ export {
|
|
|
3222
5178
|
CleanArchitectureGenerator,
|
|
3223
5179
|
DddGenerator,
|
|
3224
5180
|
EnterpriseMvcGenerator,
|
|
5181
|
+
EnvironmentDetector,
|
|
5182
|
+
FileMerger,
|
|
5183
|
+
LockGenerator,
|
|
5184
|
+
ProfileResolver,
|
|
5185
|
+
SatelliteGenerator,
|
|
3225
5186
|
Scaffold,
|
|
3226
5187
|
StubGenerator
|
|
3227
5188
|
};
|