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