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