@gravito/scaffold 2.1.0 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -98,12 +98,52 @@ ${overlay}`;
98
98
  };
99
99
 
100
100
  // src/generators/BaseGenerator.ts
101
- import fs2 from "fs/promises";
102
- import path2 from "path";
101
+ import fs4 from "fs/promises";
102
+ import path4 from "path";
103
103
 
104
- // src/generators/StubGenerator.ts
104
+ // src/utils/FileUtilities.ts
105
105
  import fs from "fs/promises";
106
106
  import path from "path";
107
+ var FileUtilities = class _FileUtilities {
108
+ static async walk(dir) {
109
+ const files = await fs.readdir(dir);
110
+ const paths = [];
111
+ for (const file of files) {
112
+ const filePath = path.join(dir, file);
113
+ const stat = await fs.stat(filePath);
114
+ if (stat.isDirectory()) {
115
+ paths.push(...await _FileUtilities.walk(filePath));
116
+ } else {
117
+ paths.push(filePath);
118
+ }
119
+ }
120
+ return paths;
121
+ }
122
+ static async writeFile(basePath, relativePath, content, fileMerger, log) {
123
+ const fullPath = path.resolve(basePath, relativePath);
124
+ await fs.mkdir(path.dirname(fullPath), { recursive: true });
125
+ let finalContent = content;
126
+ try {
127
+ const existingContent = await fs.readFile(fullPath, "utf-8");
128
+ finalContent = fileMerger.merge(relativePath, existingContent, content);
129
+ if (finalContent !== content) {
130
+ log?.(`\u{1F504} Merged file: ${relativePath}`);
131
+ }
132
+ } catch {
133
+ }
134
+ await fs.writeFile(fullPath, finalContent, "utf-8");
135
+ log?.(`\u{1F4C4} Created file: ${relativePath}`);
136
+ return fullPath;
137
+ }
138
+ };
139
+
140
+ // src/utils/TemplateManager.ts
141
+ import fs3 from "fs/promises";
142
+ import path3 from "path";
143
+
144
+ // src/generators/StubGenerator.ts
145
+ import fs2 from "fs/promises";
146
+ import path2 from "path";
107
147
  import Handlebars from "handlebars";
108
148
  var StubGenerator = class {
109
149
  config;
@@ -192,16 +232,16 @@ var StubGenerator = class {
192
232
  * @returns Path to the generated file
193
233
  */
194
234
  async generate(stubName, outputPath, variables = {}) {
195
- const stubPath = path.resolve(this.config.stubsDir, stubName);
196
- const template = await fs.readFile(stubPath, "utf-8");
235
+ const stubPath = path2.resolve(this.config.stubsDir, stubName);
236
+ const template = await fs2.readFile(stubPath, "utf-8");
197
237
  const compiled = this.handlebars.compile(template);
198
238
  const content = compiled({
199
239
  ...this.config.defaultVariables,
200
240
  ...variables
201
241
  });
202
- const fullOutputPath = path.resolve(this.config.outputDir, outputPath);
203
- await fs.mkdir(path.dirname(fullOutputPath), { recursive: true });
204
- await fs.writeFile(fullOutputPath, content, "utf-8");
242
+ const fullOutputPath = path2.resolve(this.config.outputDir, outputPath);
243
+ await fs2.mkdir(path2.dirname(fullOutputPath), { recursive: true });
244
+ await fs2.writeFile(fullOutputPath, content, "utf-8");
205
245
  return fullOutputPath;
206
246
  }
207
247
  /**
@@ -253,41 +293,57 @@ var StubGenerator = class {
253
293
  }
254
294
  };
255
295
 
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);
296
+ // src/utils/TemplateManager.ts
297
+ var TemplateManager = class {
298
+ stubGenerator;
299
+ constructor(templatesDir) {
300
+ this.stubGenerator = new StubGenerator({
301
+ stubsDir: templatesDir,
302
+ outputDir: ""
303
+ });
304
+ }
305
+ render(template, context) {
306
+ return this.stubGenerator.render(template, context);
307
+ }
308
+ async applyOverlay(sourceDir, targetDir, context, fileMerger, log) {
309
+ const createdFiles = [];
310
+ try {
311
+ await fs3.access(sourceDir);
312
+ } catch {
313
+ return [];
314
+ }
315
+ const files = await FileUtilities.walk(sourceDir);
316
+ for (const filePath of files) {
317
+ const relativePath = path3.relative(sourceDir, filePath);
318
+ let content = await fs3.readFile(filePath, "utf-8");
319
+ try {
320
+ content = this.render(content, context);
321
+ } catch {
322
+ }
323
+ const fullPath = await FileUtilities.writeFile(
324
+ targetDir,
325
+ relativePath,
326
+ content,
327
+ fileMerger,
328
+ log
329
+ );
330
+ createdFiles.push(fullPath);
267
331
  }
332
+ return createdFiles;
268
333
  }
269
- return paths;
270
- }
334
+ };
335
+
336
+ // src/generators/BaseGenerator.ts
271
337
  var BaseGenerator = class {
272
338
  config;
273
- stubGenerator;
339
+ templateManager;
274
340
  fileMerger;
275
341
  filesCreated = [];
276
342
  constructor(config) {
277
343
  this.config = config;
278
- this.stubGenerator = new StubGenerator({
279
- stubsDir: config.templatesDir,
280
- outputDir: ""
281
- // Set per-generation
282
- });
344
+ this.templateManager = new TemplateManager(config.templatesDir);
283
345
  this.fileMerger = new FileMerger();
284
346
  }
285
- /**
286
- * Generate the project scaffold.
287
- *
288
- * @param context - Generator context
289
- * @returns Array of created file paths
290
- */
291
347
  async generate(context) {
292
348
  this.filesCreated = [];
293
349
  const structure = this.getDirectoryStructure(context);
@@ -297,50 +353,73 @@ var BaseGenerator = class {
297
353
  await this.applyFeatureOverlays(context);
298
354
  return this.filesCreated;
299
355
  }
300
- /**
301
- * Create directory structure recursively.
302
- */
303
356
  async createStructure(basePath, nodes, context) {
304
357
  for (const node of nodes) {
305
- const fullPath = path2.resolve(basePath, node.name);
358
+ const fullPath = path4.resolve(basePath, node.name);
306
359
  if (node.type === "directory") {
307
- await fs2.mkdir(fullPath, { recursive: true });
360
+ await fs4.mkdir(fullPath, { recursive: true });
308
361
  this.log(`\u{1F4C1} Created directory: ${node.name}`);
309
362
  if (node.children) {
310
363
  await this.createStructure(fullPath, node.children, context);
311
364
  }
312
365
  } else {
313
- await fs2.mkdir(path2.dirname(fullPath), { recursive: true });
366
+ await fs4.mkdir(path4.dirname(fullPath), { recursive: true });
367
+ let content = "";
314
368
  if (node.template) {
315
- const templatePath = path2.resolve(this.config.templatesDir, node.template);
316
369
  try {
317
- const template = await fs2.readFile(templatePath, "utf-8");
318
- const content = this.stubGenerator.render(template, context);
319
- await fs2.writeFile(fullPath, content, "utf-8");
370
+ const templatePath = path4.resolve(this.config.templatesDir, node.template);
371
+ const template = await fs4.readFile(templatePath, "utf-8");
372
+ content = this.templateManager.render(template, context);
320
373
  } catch {
321
- await fs2.writeFile(fullPath, node.content ?? "", "utf-8");
374
+ content = node.content ?? "";
322
375
  }
323
- } else if (node.content) {
324
- await fs2.writeFile(fullPath, node.content, "utf-8");
325
376
  } else {
326
- await fs2.writeFile(fullPath, "", "utf-8");
377
+ content = node.content ?? "";
327
378
  }
328
- this.filesCreated.push(fullPath);
329
- this.log(`\u{1F4C4} Created file: ${node.name}`);
379
+ const relativePath = path4.relative(context.targetDir, fullPath);
380
+ const writtenPath = await FileUtilities.writeFile(
381
+ context.targetDir,
382
+ relativePath,
383
+ content,
384
+ this.fileMerger,
385
+ (msg) => this.log(msg)
386
+ );
387
+ this.filesCreated.push(writtenPath);
330
388
  }
331
389
  }
332
390
  }
333
- /**
334
- * Generate common files (package.json, .env, etc.)
335
- */
336
391
  async generateCommonFiles(context) {
392
+ const commonDir = path4.resolve(this.config.templatesDir, "common");
393
+ const extendedContext = {
394
+ ...context,
395
+ entrypoint: context.architecture === "ddd" ? "dist/main.js" : "dist/bootstrap.js",
396
+ dbConnection: context.profile === "core" ? "sqlite" : "postgres"
397
+ };
398
+ await this.generateFileFromTemplate(
399
+ commonDir,
400
+ "env.example.hbs",
401
+ ".env.example",
402
+ extendedContext
403
+ );
404
+ await this.generateFileFromTemplate(commonDir, "env.example.hbs", ".env", extendedContext);
405
+ await this.generateFileFromTemplate(commonDir, "gitignore.hbs", ".gitignore", extendedContext);
406
+ await this.generateFileFromTemplate(
407
+ commonDir,
408
+ "tsconfig.json.hbs",
409
+ "tsconfig.json",
410
+ extendedContext
411
+ );
412
+ await this.generateFileFromTemplate(commonDir, "Dockerfile.hbs", "Dockerfile", extendedContext);
337
413
  await this.writeFile(context.targetDir, "package.json", this.generatePackageJson(context));
338
- await this.writeFile(context.targetDir, ".env.example", this.generateEnvExample(context));
339
- await this.writeFile(context.targetDir, ".env", this.generateEnvExample(context));
340
- await this.writeFile(context.targetDir, ".gitignore", this.generateGitignore());
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());
414
+ await this.writeFile(
415
+ context.targetDir,
416
+ ".dockerignore",
417
+ `node_modules
418
+ dist
419
+ .git
420
+ .env
421
+ `
422
+ );
344
423
  await this.writeFile(
345
424
  context.targetDir,
346
425
  "ARCHITECTURE.md",
@@ -349,104 +428,74 @@ var BaseGenerator = class {
349
428
  await this.generateCheckScripts(context);
350
429
  await this.generateSkills(context);
351
430
  }
352
- /**
353
- * Copy AI Skills to the project
354
- */
355
- async generateSkills(context) {
356
- const skillsDir = path2.resolve(this.config.templatesDir, "skills");
357
- const targetSkillsDir = path2.join(".skills");
431
+ async generateFileFromTemplate(tplDir, tplName, targetName, context) {
358
432
  try {
359
- await fs2.access(skillsDir);
360
- } catch {
361
- return;
362
- }
363
- const files = await walk(skillsDir);
364
- for (const filePath of files) {
365
- const relativePath = path2.relative(skillsDir, filePath);
366
- const targetPath = path2.join(targetSkillsDir, relativePath);
367
- let content = await fs2.readFile(filePath, "utf-8");
368
- try {
369
- content = this.stubGenerator.render(content, context);
370
- } catch {
371
- }
372
- await this.writeFile(context.targetDir, targetPath, content);
433
+ const template = await fs4.readFile(path4.join(tplDir, tplName), "utf-8");
434
+ const content = this.templateManager.render(template, context);
435
+ await this.writeFile(context.targetDir, targetName, content);
436
+ } catch (e) {
437
+ this.log(`\u26A0\uFE0F Failed to generate ${targetName}: ${e}`);
373
438
  }
374
439
  }
375
- /**
376
- * Apply profile-specific overlays
377
- */
440
+ async generateSkills(context) {
441
+ const skillsDir = path4.resolve(this.config.templatesDir, "skills");
442
+ const created = await this.templateManager.applyOverlay(
443
+ skillsDir,
444
+ path4.join(context.targetDir, ".skills"),
445
+ context,
446
+ this.fileMerger,
447
+ (msg) => this.log(msg)
448
+ );
449
+ this.filesCreated.push(...created);
450
+ }
378
451
  async applyOverlays(context) {
379
452
  const profile = context.profile;
380
- if (!profile) return;
381
- const overlayDir = path2.resolve(this.config.templatesDir, "overlays", profile);
382
- await this.copyOverlayDirectory(overlayDir, context);
453
+ if (profile) {
454
+ const overlayDir = path4.resolve(this.config.templatesDir, "overlays", profile);
455
+ await this.copyOverlayDirectory(overlayDir, context);
456
+ }
383
457
  }
384
- /**
385
- * Apply feature-specific overlays
386
- */
387
458
  async applyFeatureOverlays(context) {
388
459
  const features = context.features || [];
389
460
  for (const feature of features) {
390
- const overlayDir = path2.resolve(this.config.templatesDir, "features", feature);
461
+ const overlayDir = path4.resolve(this.config.templatesDir, "features", feature);
391
462
  await this.copyOverlayDirectory(overlayDir, context);
392
463
  }
393
464
  }
394
- /**
395
- * Helper to copy/merge an overlay directory into the target
396
- */
397
465
  async copyOverlayDirectory(sourceDir, context) {
398
- try {
399
- await fs2.access(sourceDir);
400
- } catch {
401
- return;
402
- }
403
- const files = await walk(sourceDir);
404
- for (const filePath of files) {
405
- const relativePath = path2.relative(sourceDir, filePath);
406
- let content = await fs2.readFile(filePath, "utf-8");
407
- try {
408
- content = this.stubGenerator.render(content, context);
409
- } catch {
410
- }
411
- await this.writeFile(context.targetDir, relativePath, content);
412
- }
466
+ const created = await this.templateManager.applyOverlay(
467
+ sourceDir,
468
+ context.targetDir,
469
+ context,
470
+ this.fileMerger,
471
+ (msg) => this.log(msg)
472
+ );
473
+ this.filesCreated.push(...created);
413
474
  }
414
- /**
415
- * Write a file and track it.
416
- */
417
475
  async writeFile(basePath, relativePath, content) {
418
- const fullPath = path2.resolve(basePath, relativePath);
419
- await fs2.mkdir(path2.dirname(fullPath), { recursive: true });
420
- let finalContent = content;
421
- try {
422
- const existingContent = await fs2.readFile(fullPath, "utf-8");
423
- finalContent = this.fileMerger.merge(relativePath, existingContent, content);
424
- if (finalContent !== content) {
425
- this.log(`\u{1F504} Merged file: ${relativePath}`);
426
- }
427
- } catch {
428
- }
429
- await fs2.writeFile(fullPath, finalContent, "utf-8");
430
- this.filesCreated.push(fullPath);
431
- this.log(`\u{1F4C4} Created file: ${relativePath}`);
476
+ const writtenPath = await FileUtilities.writeFile(
477
+ basePath,
478
+ relativePath,
479
+ content,
480
+ this.fileMerger,
481
+ (msg) => this.log(msg)
482
+ );
483
+ this.filesCreated.push(writtenPath);
432
484
  }
433
- /**
434
- * Generate package.json content.
435
- */
436
485
  generatePackageJson(context) {
437
486
  const profile = context.profile || "core";
438
- const baseDependencies = {
487
+ const deps = {
439
488
  "@gravito/core": "^1.0.0-beta.5",
440
489
  "@gravito/atlas": "^1.0.0-beta.5",
441
490
  "@gravito/plasma": "^1.0.0-beta.5",
442
491
  "@gravito/stream": "^1.0.0-beta.5"
443
492
  };
444
493
  if (profile === "enterprise" || profile === "scale") {
445
- baseDependencies["@gravito/quasar"] = "^1.0.0-beta.5";
446
- baseDependencies["@gravito/horizon"] = "^1.0.0-beta.5";
494
+ deps["@gravito/quasar"] = "^1.0.0-beta.5";
495
+ deps["@gravito/horizon"] = "^1.0.0-beta.5";
447
496
  }
448
497
  if (context.withSpectrum) {
449
- baseDependencies["@gravito/spectrum"] = "^1.0.0-beta.1";
498
+ deps["@gravito/spectrum"] = "^1.0.0-beta.5";
450
499
  }
451
500
  const pkg = {
452
501
  name: context.nameKebabCase,
@@ -457,717 +506,426 @@ var BaseGenerator = class {
457
506
  build: "bun build ./src/bootstrap.ts --outdir ./dist --target bun",
458
507
  start: "bun run dist/bootstrap.js",
459
508
  test: "bun test",
460
- typecheck: "tsc --noEmit",
461
- check: "bun run typecheck && bun run test",
462
- "check:deps": "bun run scripts/check-dependencies.ts",
463
- validate: "bun run check && bun run check:deps",
464
- precommit: "bun run validate",
465
- "docker:build": `docker build -t ${context.nameKebabCase} .`,
466
- "docker:run": `docker run -it -p 3000:3000 ${context.nameKebabCase}`
509
+ typecheck: "bun tsc --noEmit",
510
+ validate: "bun run typecheck && bun run test"
467
511
  },
468
- dependencies: baseDependencies,
469
- devDependencies: {
470
- "bun-types": "latest",
471
- typescript: "^5.0.0"
472
- }
512
+ dependencies: deps,
513
+ devDependencies: { "bun-types": "latest", typescript: "^5.9.3" }
473
514
  };
474
515
  return JSON.stringify(pkg, null, 2);
475
516
  }
517
+ async generateCheckScripts(context) {
518
+ const scriptsDir = path4.resolve(context.targetDir, "scripts");
519
+ await fs4.mkdir(scriptsDir, { recursive: true });
520
+ const templatesDir = path4.resolve(this.config.templatesDir, "scripts");
521
+ await this.generateFileFromTemplate(
522
+ templatesDir,
523
+ "check-dependencies.ts.hbs",
524
+ "scripts/check-dependencies.ts",
525
+ context
526
+ );
527
+ await this.generateFileFromTemplate(templatesDir, "check.sh.hbs", "scripts/check.sh", context);
528
+ await this.generateFileFromTemplate(
529
+ templatesDir,
530
+ "pre-commit.sh.hbs",
531
+ "scripts/pre-commit.sh",
532
+ context
533
+ );
534
+ await this.writeFile(
535
+ context.targetDir,
536
+ "CHECK_SYSTEM.md",
537
+ "# Project Check System\n\nRun `bun run validate` to check everything.\n"
538
+ );
539
+ }
540
+ log(message) {
541
+ if (this.config.verbose) {
542
+ console.log(message);
543
+ }
544
+ }
545
+ static createContext(name, targetDir, architecture, packageManager = "bun", extra = {}) {
546
+ const toPascalCase = (str) => str.replace(/([a-z])([A-Z])/g, "$1 $2").split(/[-_ ]+/).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
547
+ const toCamelCase = (str) => {
548
+ const pascal = toPascalCase(str);
549
+ return pascal.charAt(0).toLowerCase() + pascal.slice(1);
550
+ };
551
+ const toSnakeCase = (str) => str.replace(/([a-z])([A-Z])/g, "$1_$2").split(/[-_ ]+/).map((word) => word.toLowerCase()).join("_");
552
+ const toKebabCase = (str) => str.replace(/([a-z])([A-Z])/g, "$1-$2").split(/[-_ ]+/).map((word) => word.toLowerCase()).join("-");
553
+ return {
554
+ name,
555
+ namePascalCase: toPascalCase(name),
556
+ nameCamelCase: toCamelCase(name),
557
+ nameSnakeCase: toSnakeCase(name),
558
+ nameKebabCase: toKebabCase(name),
559
+ targetDir,
560
+ architecture,
561
+ packageManager,
562
+ year: (/* @__PURE__ */ new Date()).getFullYear().toString(),
563
+ date: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
564
+ ...extra
565
+ };
566
+ }
567
+ };
568
+
569
+ // src/utils/ConfigGenerator.ts
570
+ var ConfigGenerator = class {
476
571
  /**
477
- * Generate Dockerfile content.
572
+ * Generate app configuration (simple version for Clean Architecture)
478
573
  */
479
- generateDockerfile(context) {
480
- const entrypoint = context.architecture === "ddd" ? "dist/main.js" : "dist/bootstrap.js";
481
- return `FROM oven/bun:1.0 AS base
482
- WORKDIR /usr/src/app
483
-
484
- # Install dependencies
485
- FROM base AS install
486
- RUN mkdir -p /temp/dev
487
- COPY package.json bun.lockb /temp/dev/
488
- RUN cd /temp/dev && bun install --frozen-lockfile
489
-
490
- # Build application
491
- FROM base AS build
492
- COPY --from=install /temp/dev/node_modules node_modules
493
- COPY . .
494
- ENV NODE_ENV=production
495
- RUN bun run build
496
-
497
- # Final production image
498
- FROM base AS release
499
- COPY --from=build /usr/src/app/${entrypoint} index.js
500
- COPY --from=build /usr/src/app/package.json .
501
-
502
- # Create a non-root user for security
503
- USER bun
504
- EXPOSE 3000/tcp
505
- ENTRYPOINT [ "bun", "run", "index.js" ]
574
+ static generateSimpleAppConfig(context) {
575
+ return `export default {
576
+ name: process.env.APP_NAME ?? '${context.name}',
577
+ env: process.env.APP_ENV ?? 'development',
578
+ debug: process.env.APP_DEBUG === 'true',
579
+ url: process.env.APP_URL ?? 'http://localhost:3000',
580
+ key: process.env.APP_KEY,
581
+ }
506
582
  `;
507
583
  }
508
584
  /**
509
- * Generate .dockerignore content.
585
+ * Generate app configuration (detailed version for Enterprise MVC)
510
586
  */
511
- generateDockerIgnore() {
512
- return `node_modules
513
- dist
514
- .git
515
- .env
516
- *.log
517
- .vscode
518
- .idea
519
- tests
520
- `;
521
- }
587
+ static generateDetailedAppConfig(context) {
588
+ return `/**
589
+ * Application Configuration
590
+ */
591
+ export default {
522
592
  /**
523
- * Generate .env.example content.
593
+ * Application name
524
594
  */
525
- generateEnvExample(context) {
526
- const profile = context.profile || "core";
527
- let envContent = `# ============================================================================
528
- # Application Configuration
529
- # ============================================================================
530
-
531
- APP_NAME=${context.name}
532
- APP_ENV=development
533
- APP_DEBUG=true
534
- APP_URL=http://localhost:3000
535
- APP_KEY=
536
-
537
- # ============================================================================
538
- # Database Configuration
539
- # ============================================================================
540
-
541
- # Database Connection (sqlite, postgres, mysql)
542
- DB_CONNECTION=${profile === "core" ? "sqlite" : "postgres"}
543
-
544
- # SQLite Configuration (when DB_CONNECTION=sqlite)
545
- DB_DATABASE=database/database.sqlite
546
-
547
- # PostgreSQL Configuration (when DB_CONNECTION=postgres)
548
- ${profile !== "core" ? `DB_HOST=127.0.0.1
549
- DB_PORT=5432
550
- DB_DATABASE=${context.name}
551
- DB_USERNAME=postgres
552
- DB_PASSWORD=
553
- DB_SSLMODE=prefer` : `# DB_HOST=127.0.0.1
554
- # DB_PORT=5432
555
- # DB_DATABASE=${context.name}
556
- # DB_USERNAME=postgres
557
- # DB_PASSWORD=
558
- # DB_SSLMODE=prefer`}
559
-
560
- # MySQL Configuration (when DB_CONNECTION=mysql)
561
- # DB_HOST=127.0.0.1
562
- # DB_PORT=3306
563
- # DB_DATABASE=${context.name}
564
- # DB_USERNAME=root
565
- # DB_PASSWORD=
566
-
567
- # ============================================================================
568
- # Redis Configuration (@gravito/plasma)
569
- # ============================================================================
570
-
571
- # Default Redis Connection
572
- REDIS_CONNECTION=default
573
- REDIS_HOST=127.0.0.1
574
- REDIS_PORT=6379
575
- REDIS_PASSWORD=
576
- REDIS_DB=0
577
-
578
- # Redis Connection Options
579
- REDIS_CONNECT_TIMEOUT=10000
580
- REDIS_COMMAND_TIMEOUT=5000
581
- REDIS_KEY_PREFIX=
582
- REDIS_MAX_RETRIES=3
583
- REDIS_RETRY_DELAY=1000
584
-
585
- # Cache-specific Redis Connection (optional, falls back to default)
586
- # REDIS_CACHE_HOST=127.0.0.1
587
- # REDIS_CACHE_PORT=6379
588
- # REDIS_CACHE_PASSWORD=
589
- REDIS_CACHE_DB=1
590
-
591
- # Queue-specific Redis Connection (optional, falls back to default)
592
- # REDIS_QUEUE_HOST=127.0.0.1
593
- # REDIS_QUEUE_PORT=6379
594
- # REDIS_QUEUE_PASSWORD=
595
- REDIS_QUEUE_DB=2
596
-
597
- # ============================================================================
598
- # Cache Configuration (@gravito/stasis)
599
- # ============================================================================
600
-
601
- # Cache Driver (memory, file, redis)
602
- CACHE_DRIVER=${profile === "core" ? "memory" : "redis"}
603
-
604
- # File Cache Path (when CACHE_DRIVER=file)
605
- CACHE_PATH=storage/framework/cache
606
-
607
- # Redis Cache Configuration (when CACHE_DRIVER=redis)
608
- REDIS_CACHE_CONNECTION=cache
609
- REDIS_CACHE_PREFIX=cache:
610
-
611
- # ============================================================================
612
- # Queue Configuration (@gravito/stream)
613
- # ============================================================================
614
-
615
- # Queue Connection (sync, memory, database, redis, kafka, sqs, rabbitmq)
616
- QUEUE_CONNECTION=${profile === "core" ? "sync" : "redis"}
617
-
618
- # Database Queue Configuration (when QUEUE_CONNECTION=database)
619
- QUEUE_TABLE=jobs
620
-
621
- # Redis Queue Configuration (when QUEUE_CONNECTION=redis)
622
- REDIS_PREFIX=queue:
595
+ name: process.env.APP_NAME ?? '${context.name}',
623
596
 
624
- `;
625
- if (profile === "enterprise" || profile === "scale") {
626
- envContent += `# Kafka Queue Configuration (when QUEUE_CONNECTION=kafka)
627
- # KAFKA_BROKERS=localhost:9092
628
- # KAFKA_CONSUMER_GROUP_ID=gravito-workers
629
- # KAFKA_CLIENT_ID=${context.name}
630
-
631
- # AWS SQS Queue Configuration (when QUEUE_CONNECTION=sqs)
632
- # AWS_REGION=us-east-1
633
- # SQS_QUEUE_URL_PREFIX=
634
- # SQS_VISIBILITY_TIMEOUT=30
635
- # SQS_WAIT_TIME_SECONDS=20
636
-
637
- # RabbitMQ Queue Configuration (when QUEUE_CONNECTION=rabbitmq)
638
- # RABBITMQ_URL=amqp://localhost
639
- # RABBITMQ_EXCHANGE=gravito.events
640
- # RABBITMQ_EXCHANGE_TYPE=fanout
597
+ /**
598
+ * Application environment
599
+ */
600
+ env: process.env.APP_ENV ?? 'development',
641
601
 
642
- `;
643
- }
644
- envContent += `# ============================================================================
645
- # Logging Configuration
646
- # ============================================================================
602
+ /**
603
+ * Application port
604
+ */
605
+ port: Number.parseInt(process.env.PORT ?? '3000', 10),
647
606
 
648
- LOG_LEVEL=debug
649
- `;
650
- return envContent;
651
- }
652
607
  /**
653
- * Generate .gitignore content.
608
+ * View directory
654
609
  */
655
- generateGitignore() {
656
- return `# Dependencies
657
- node_modules/
610
+ VIEW_DIR: process.env.VIEW_DIR ?? 'src/views',
658
611
 
659
- # Build output
660
- dist/
612
+ /**
613
+ * Debug mode
614
+ */
615
+ debug: process.env.APP_DEBUG === 'true',
661
616
 
662
- # Environment
663
- .env
664
- .env.local
665
- .env.*.local
617
+ /**
618
+ * Application URL
619
+ */
620
+ url: process.env.APP_URL ?? 'http://localhost:3000',
666
621
 
667
- # IDE
668
- .idea/
669
- .vscode/
670
- *.swp
671
- *.swo
622
+ /**
623
+ * Timezone
624
+ */
625
+ timezone: 'UTC',
672
626
 
673
- # System
674
- .DS_Store
675
- Thumbs.db
627
+ /**
628
+ * Locale
629
+ */
630
+ locale: 'en',
676
631
 
677
- # Logs
678
- *.log
679
- logs/
632
+ /**
633
+ * Fallback locale
634
+ */
635
+ fallbackLocale: 'en',
680
636
 
681
- # Database
682
- *.sqlite
683
- *.sqlite-journal
637
+ /**
638
+ * Encryption key
639
+ */
640
+ key: process.env.APP_KEY,
684
641
 
685
- # Coverage
686
- coverage/
687
- `;
688
- }
689
642
  /**
690
- * Generate tsconfig.json content.
643
+ * Service providers to register
691
644
  */
692
- generateTsConfig() {
693
- const config = {
694
- compilerOptions: {
695
- target: "ESNext",
696
- module: "ESNext",
697
- moduleResolution: "bundler",
698
- esModuleInterop: true,
699
- strict: true,
700
- skipLibCheck: true,
701
- declaration: true,
702
- experimentalDecorators: true,
703
- emitDecoratorMetadata: true,
704
- types: ["bun-types"],
705
- outDir: "./dist",
706
- rootDir: "./src",
707
- baseUrl: ".",
708
- paths: {
709
- "@/*": ["./src/*"]
710
- }
711
- },
712
- include: ["src/**/*"],
713
- exclude: ["node_modules", "dist"]
714
- };
715
- return JSON.stringify(config, null, 2);
645
+ providers: [
646
+ // Framework providers
647
+ // 'RouteServiceProvider',
648
+
649
+ // Application providers
650
+ // 'AppServiceProvider',
651
+ ],
652
+ }
653
+ `;
716
654
  }
717
655
  /**
718
- * Generate check scripts for project validation.
656
+ * Generate database configuration (simple version)
719
657
  */
720
- async generateCheckScripts(context) {
721
- const scriptsDir = path2.resolve(context.targetDir, "scripts");
722
- await fs2.mkdir(scriptsDir, { recursive: true });
723
- await this.writeFile(
724
- scriptsDir,
725
- "check-dependencies.ts",
726
- this.generateCheckDependenciesScript()
727
- );
728
- await this.writeFile(scriptsDir, "check.sh", this.generateCheckShellScript());
729
- await this.writeFile(scriptsDir, "pre-commit.sh", this.generatePreCommitScript());
730
- await this.writeFile(context.targetDir, "CHECK_SYSTEM.md", this.generateCheckSystemDoc(context));
658
+ static generateSimpleDatabaseConfig() {
659
+ return `export default {
660
+ default: process.env.DB_CONNECTION ?? 'sqlite',
661
+ connections: {
662
+ sqlite: {
663
+ driver: 'sqlite',
664
+ database: process.env.DB_DATABASE ?? 'database/database.sqlite',
665
+ },
666
+ },
667
+ }
668
+ `;
731
669
  }
732
670
  /**
733
- * Generate check-dependencies.ts script content.
671
+ * Generate database configuration (detailed version)
734
672
  */
735
- generateCheckDependenciesScript() {
673
+ static generateDetailedDatabaseConfig() {
736
674
  return `/**
737
- * \u76F8\u4F9D\u5957\u4EF6\u7248\u672C\u6AA2\u67E5\u8173\u672C
738
- *
739
- * \u6AA2\u67E5 package.json \u4E2D\u7684\u5957\u4EF6\u662F\u5426\u70BA\u6700\u65B0\u7A69\u5B9A\u7248\u672C
740
- * \u4E26\u63D0\u4F9B\u66F4\u65B0\u5EFA\u8B70
675
+ * Database Configuration
741
676
  */
677
+ export default {
678
+ /**
679
+ * Default connection
680
+ */
681
+ default: process.env.DB_CONNECTION ?? 'sqlite',
742
682
 
743
- import { readFileSync } from 'fs'
744
- import { join } from 'path'
745
-
746
- interface PackageJson {
747
- dependencies?: Record<string, string>
748
- devDependencies?: Record<string, string>
749
- }
750
-
751
- interface PackageInfo {
752
- name: string
753
- current: string
754
- latest: string
755
- outdated: boolean
756
- }
683
+ /**
684
+ * Database connections
685
+ */
686
+ connections: {
687
+ sqlite: {
688
+ driver: 'sqlite',
689
+ database: process.env.DB_DATABASE ?? 'database/database.sqlite',
690
+ },
757
691
 
758
- const colors = {
759
- reset: '\\x1b[0m',
760
- green: '\\x1b[32m',
761
- yellow: '\\x1b[33m',
762
- red: '\\x1b[31m',
763
- blue: '\\x1b[36m',
764
- }
692
+ mysql: {
693
+ driver: 'mysql',
694
+ host: process.env.DB_HOST ?? 'localhost',
695
+ port: Number(process.env.DB_PORT ?? 3306),
696
+ database: process.env.DB_DATABASE ?? 'forge',
697
+ username: process.env.DB_USERNAME ?? 'forge',
698
+ password: process.env.DB_PASSWORD ?? '',
699
+ },
765
700
 
766
- function log(message: string, color: keyof typeof colors = 'reset') {
767
- console.log(\`\${colors[color]}\${message}\${colors.reset}\`)
768
- }
701
+ postgres: {
702
+ driver: 'postgres',
703
+ host: process.env.DB_HOST ?? 'localhost',
704
+ port: Number(process.env.DB_PORT ?? 5432),
705
+ database: process.env.DB_DATABASE ?? 'forge',
706
+ username: process.env.DB_USERNAME ?? 'forge',
707
+ password: process.env.DB_PASSWORD ?? '',
708
+ },
709
+ },
769
710
 
770
- async function getLatestVersion(packageName: string): Promise<string | null> {
771
- try {
772
- const response = await fetch(\`https://registry.npmjs.org/\${packageName}/latest\`)
773
- if (!response.ok) return null
774
- const data = await response.json()
775
- return data.version
776
- } catch {
777
- return null
711
+ /**
712
+ * Migration settings
713
+ */
714
+ migrations: {
715
+ table: 'migrations',
716
+ path: 'database/migrations',
717
+ },
718
+ }
719
+ `;
778
720
  }
721
+ /**
722
+ * Generate auth configuration
723
+ */
724
+ static generateAuthConfig() {
725
+ return `export default {
726
+ defaults: { guard: 'web' },
727
+ guards: {
728
+ web: { driver: 'session', provider: 'users' },
729
+ api: { driver: 'token', provider: 'users' },
730
+ },
779
731
  }
780
-
781
- function parseVersion(version: string): string {
782
- // \u79FB\u9664 ^, ~, >= \u7B49\u524D\u7DB4
783
- return version.replace(/^[\\^~>=<]/, '')
732
+ `;
733
+ }
734
+ /**
735
+ * Generate cache configuration
736
+ */
737
+ static generateCacheConfig() {
738
+ return `export default {
739
+ default: process.env.CACHE_DRIVER ?? 'memory',
740
+ stores: {
741
+ memory: { driver: 'memory' },
742
+ },
743
+ }
744
+ `;
745
+ }
746
+ /**
747
+ * Generate logging configuration
748
+ */
749
+ static generateLoggingConfig() {
750
+ return `export default {
751
+ default: process.env.LOG_CHANNEL ?? 'console',
752
+ channels: {
753
+ console: { driver: 'console', level: process.env.LOG_LEVEL ?? 'debug' },
754
+ },
784
755
  }
756
+ `;
757
+ }
758
+ /**
759
+ * Generate view configuration
760
+ */
761
+ static generateViewConfig() {
762
+ return `/**
763
+ * View Configuration
764
+ */
765
+ export default {
766
+ /**
767
+ * View engine
768
+ */
769
+ engine: 'html',
785
770
 
786
- async function checkPackage(
787
- name: string,
788
- currentVersion: string
789
- ): Promise<PackageInfo | null> {
790
- // \u8DF3\u904E\u672C\u5730\u9023\u7D50\u7684\u5957\u4EF6
791
- if (currentVersion.startsWith('link:') || currentVersion.startsWith('workspace:')) {
792
- return null
771
+ /**
772
+ * View directory
773
+ */
774
+ path: 'src/views',
775
+
776
+ /**
777
+ * Cache views in production
778
+ */
779
+ cache: process.env.NODE_ENV === 'production',
780
+ }
781
+ `;
793
782
  }
783
+ };
784
+
785
+ // src/utils/ServiceProviderGenerator.ts
786
+ var ServiceProviderGenerator = class {
787
+ /**
788
+ * Generate App Service Provider
789
+ */
790
+ static generateAppServiceProvider(context, architectureName) {
791
+ return `/**
792
+ * App Service Provider
793
+ */
794
794
 
795
- const current = parseVersion(currentVersion)
796
- const latest = await getLatestVersion(name)
795
+ import { ServiceProvider, type Container, type PlanetCore } from '@gravito/core'
797
796
 
798
- if (!latest) {
799
- return null
797
+ export class AppServiceProvider extends ServiceProvider {
798
+ register(_container: Container): void {
799
+ // Register application services
800
800
  }
801
801
 
802
- return {
803
- name,
804
- current,
805
- latest,
806
- outdated: current !== latest,
802
+ boot(_core: PlanetCore): void {
803
+ console.log('${context.name} (${architectureName}) booted!')
807
804
  }
808
805
  }
806
+ `;
807
+ }
808
+ /**
809
+ * Generate Middleware Provider
810
+ */
811
+ static generateMiddlewareProvider() {
812
+ return `/**
813
+ * Middleware Service Provider
814
+ */
809
815
 
810
- async function main() {
811
- log('\\n=== \u76F8\u4F9D\u5957\u4EF6\u7248\u672C\u6AA2\u67E5 ===\\n', 'blue')
816
+ import {
817
+ ServiceProvider,
818
+ type Container,
819
+ type PlanetCore,
820
+ bodySizeLimit,
821
+ securityHeaders,
822
+ } from '@gravito/core'
812
823
 
813
- const packageJsonPath = join(process.cwd(), 'package.json')
814
- const packageJson: PackageJson = JSON.parse(
815
- readFileSync(packageJsonPath, 'utf-8')
816
- )
824
+ export class MiddlewareProvider extends ServiceProvider {
825
+ register(_container: Container): void {}
817
826
 
818
- const allDependencies = {
819
- ...packageJson.dependencies,
820
- ...packageJson.devDependencies,
821
- }
827
+ boot(core: PlanetCore): void {
828
+ const isDev = process.env.NODE_ENV !== 'production'
822
829
 
823
- log(\`\u6AA2\u67E5 \${Object.keys(allDependencies).length} \u500B\u5957\u4EF6...\\n\`, 'yellow')
830
+ core.adapter.use('*', securityHeaders({
831
+ contentSecurityPolicy: isDev ? false : undefined,
832
+ }))
824
833
 
825
- const results: PackageInfo[] = []
826
- const outdated: PackageInfo[] = []
827
- const upToDate: PackageInfo[] = []
834
+ core.adapter.use('*', bodySizeLimit(10 * 1024 * 1024))
828
835
 
829
- // \u6AA2\u67E5\u6240\u6709\u5957\u4EF6
830
- for (const [name, version] of Object.entries(allDependencies)) {
831
- const info = await checkPackage(name, version)
832
- if (info) {
833
- results.push(info)
834
- if (info.outdated) {
835
- outdated.push(info)
836
- } else {
837
- upToDate.push(info)
838
- }
839
- }
836
+ core.logger.info('\u{1F6E1}\uFE0F Middleware registered')
840
837
  }
841
-
842
- // \u986F\u793A\u7D50\u679C
843
- if (upToDate.length > 0) {
844
- log(\`\\n\u2713 \u5DF2\u662F\u6700\u65B0\u7248\u672C (\${upToDate.length}):\`, 'green')
845
- upToDate.forEach((pkg) => {
846
- log(\` \${pkg.name}: \${pkg.current}\`, 'green')
847
- })
838
+ }
839
+ `;
848
840
  }
841
+ /**
842
+ * Generate Route Provider
843
+ */
844
+ static generateRouteProvider(routePath = "../../routes/api", importType = "default") {
845
+ const importStatement = importType === "default" ? `import routes from '${routePath}'` : `import { registerApiRoutes } from '${routePath}'`;
846
+ const routeCall = importType === "default" ? "routes(core.router)" : "registerApiRoutes(core.router)";
847
+ return `/**
848
+ * Route Service Provider
849
+ */
849
850
 
850
- if (outdated.length > 0) {
851
- log(\`\\n\u26A0 \u9700\u8981\u66F4\u65B0 (\${outdated.length}):\`, 'yellow')
852
- outdated.forEach((pkg) => {
853
- log(\` \${pkg.name}: \${pkg.current} \u2192 \${pkg.latest}\`, 'yellow')
854
- })
855
- }
851
+ import { ServiceProvider, type Container, type PlanetCore } from '@gravito/core'
852
+ ${importStatement}
856
853
 
857
- // \u7E3D\u7D50
858
- log('\\n=== \u6AA2\u67E5\u7D50\u679C ===', 'blue')
859
- log(\`\u7E3D\u8A08: \${results.length} \u500B\u5957\u4EF6\`, 'blue')
860
- log(\`\u6700\u65B0: \${upToDate.length} \u500B\`, 'green')
861
- log(\`\u9700\u66F4\u65B0: \${outdated.length} \u500B\`, outdated.length > 0 ? 'yellow' : 'green')
854
+ export class RouteProvider extends ServiceProvider {
855
+ register(_container: Container): void {}
862
856
 
863
- // \u5982\u679C\u6709\u9700\u8981\u66F4\u65B0\u7684\u5957\u4EF6\uFF0C\u8FD4\u56DE\u975E\u96F6\u9000\u51FA\u78BC
864
- if (outdated.length > 0) {
865
- log('\\n\u5EFA\u8B70\u57F7\u884C\u4EE5\u4E0B\u547D\u4EE4\u66F4\u65B0\u5957\u4EF6\uFF1A', 'yellow')
866
- log(' bun update', 'yellow')
867
- process.exit(1)
868
- } else {
869
- log('\\n\u2713 \u6240\u6709\u5957\u4EF6\u90FD\u662F\u6700\u65B0\u7248\u672C\uFF01', 'green')
870
- process.exit(0)
857
+ boot(core: PlanetCore): void {
858
+ ${routeCall}
859
+ core.logger.info('\u{1F6E4}\uFE0F Routes registered')
871
860
  }
872
861
  }
873
-
874
- main().catch((error) => {
875
- log(\`\\n\u932F\u8AA4: \${error.message}\`, 'red')
876
- process.exit(1)
877
- })
878
862
  `;
879
863
  }
880
864
  /**
881
- * Generate check.sh script content.
865
+ * Generate Providers Index
882
866
  */
883
- generateCheckShellScript() {
884
- return `#!/bin/bash
885
-
886
- # \u5C08\u6848\u6AA2\u67E5\u8173\u672C
887
- # \u57F7\u884C\u6240\u6709\u5FC5\u8981\u7684\u6AA2\u67E5\uFF1A\u985E\u578B\u6AA2\u67E5\u3001\u6E2C\u8A66\u3001\u4F9D\u8CF4\u6AA2\u67E5\u7B49
888
-
889
- set -e
890
-
891
- # \u984F\u8272\u5B9A\u7FA9
892
- GREEN='\\033[0;32m'
893
- YELLOW='\\033[1;33m'
894
- RED='\\033[0;31m'
895
- BLUE='\\033[0;34m'
896
- NC='\\033[0m' # No Color
897
-
898
- echo -e "\${BLUE}=== \u5C08\u6848\u6AA2\u67E5 ===\${NC}\\n"
899
-
900
- # \u6AA2\u67E5\u662F\u5426\u5728\u6B63\u78BA\u7684\u76EE\u9304
901
- if [ ! -f "package.json" ]; then
902
- echo -e "\${RED}\u932F\u8AA4: \u8ACB\u5728\u5C08\u6848\u6839\u76EE\u9304\u57F7\u884C\u6B64\u8173\u672C\${NC}"
903
- exit 1
904
- fi
905
-
906
- # \u6AA2\u67E5 Bun \u662F\u5426\u5B89\u88DD
907
- if ! command -v bun &> /dev/null; then
908
- echo -e "\${RED}\u932F\u8AA4: \u672A\u627E\u5230 bun\uFF0C\u8ACB\u5148\u5B89\u88DD Bun\${NC}"
909
- exit 1
910
- fi
911
-
912
- # 1. \u985E\u578B\u6AA2\u67E5
913
- echo -e "\${YELLOW}[1/3] \u57F7\u884C\u985E\u578B\u6AA2\u67E5...\${NC}"
914
- if bun run typecheck; then
915
- echo -e "\${GREEN}\u2713 \u985E\u578B\u6AA2\u67E5\u901A\u904E\${NC}\\n"
916
- else
917
- echo -e "\${RED}\u2717 \u985E\u578B\u6AA2\u67E5\u5931\u6557\${NC}"
918
- exit 1
919
- fi
920
-
921
- # 2. \u57F7\u884C\u6E2C\u8A66
922
- echo -e "\${YELLOW}[2/3] \u57F7\u884C\u6E2C\u8A66...\${NC}"
923
- if bun test; then
924
- echo -e "\${GREEN}\u2713 \u6E2C\u8A66\u901A\u904E\${NC}\\n"
925
- else
926
- echo -e "\${RED}\u2717 \u6E2C\u8A66\u5931\u6557\${NC}"
927
- exit 1
928
- fi
929
-
930
- # 3. \u6AA2\u67E5\u4F9D\u8CF4\u7248\u672C\uFF08\u53EF\u9078\uFF0C\u56E0\u70BA\u9700\u8981\u7DB2\u8DEF\u9023\u7DDA\uFF09
931
- echo -e "\${YELLOW}[3/3] \u6AA2\u67E5\u4F9D\u8CF4\u7248\u672C...\${NC}"
932
- if bun run check:deps; then
933
- echo -e "\${GREEN}\u2713 \u4F9D\u8CF4\u6AA2\u67E5\u5B8C\u6210\${NC}\\n"
934
- else
935
- 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"
936
- fi
937
-
938
- echo -e "\${GREEN}=== \u6240\u6709\u6AA2\u67E5\u5B8C\u6210 ===\${NC}"
867
+ static generateProvidersIndex(providers = ["AppServiceProvider", "MiddlewareProvider", "RouteProvider"]) {
868
+ const exports = providers.map((p) => `export { ${p} } from './${p}'`).join("\n");
869
+ return `/**
870
+ * Application Service Providers
871
+ */
872
+
873
+ ${exports}
939
874
  `;
940
875
  }
941
876
  /**
942
- * Generate pre-commit.sh script content.
877
+ * Generate Repository Service Provider
943
878
  */
944
- generatePreCommitScript() {
945
- return `#!/bin/bash
946
-
947
- # Pre-commit Hook
948
- # \u5728 git commit \u524D\u81EA\u52D5\u57F7\u884C\u6AA2\u67E5
949
- #
950
- # \u5B89\u88DD\u65B9\u5F0F\uFF1A
951
- # ln -s ../../scripts/pre-commit.sh .git/hooks/pre-commit
952
- # \u6216
953
- # cp scripts/pre-commit.sh .git/hooks/pre-commit
954
- # chmod +x .git/hooks/pre-commit
955
-
956
- set -e
957
-
958
- # \u984F\u8272\u5B9A\u7FA9
959
- GREEN='\\033[0;32m'
960
- YELLOW='\\033[1;33m'
961
- RED='\\033[0;31m'
962
- BLUE='\\033[0;34m'
963
- NC='\\033[0m' # No Color
964
-
965
- echo -e "\${BLUE}=== Pre-commit \u6AA2\u67E5 ===\${NC}\\n"
966
-
967
- # \u5207\u63DB\u5230\u5C08\u6848\u6839\u76EE\u9304
968
- cd "$(git rev-parse --show-toplevel)"
969
-
970
- # \u6AA2\u67E5\u662F\u5426\u5728\u6B63\u78BA\u7684\u76EE\u9304
971
- if [ ! -f "package.json" ]; then
972
- echo -e "\${RED}\u932F\u8AA4: \u627E\u4E0D\u5230 package.json\${NC}"
973
- exit 1
974
- fi
975
-
976
- # \u6AA2\u67E5 Bun \u662F\u5426\u5B89\u88DD
977
- if ! command -v bun &> /dev/null; then
978
- echo -e "\${RED}\u932F\u8AA4: \u672A\u627E\u5230 bun\uFF0C\u8ACB\u5148\u5B89\u88DD Bun\${NC}"
979
- exit 1
980
- fi
981
-
982
- # 1. \u985E\u578B\u6AA2\u67E5\uFF08\u5FEB\u901F\u6AA2\u67E5\uFF09
983
- echo -e "\${YELLOW}[1/2] \u57F7\u884C\u985E\u578B\u6AA2\u67E5...\${NC}"
984
- if bun run typecheck; then
985
- echo -e "\${GREEN}\u2713 \u985E\u578B\u6AA2\u67E5\u901A\u904E\${NC}\\n"
986
- else
987
- echo -e "\${RED}\u2717 \u985E\u578B\u6AA2\u67E5\u5931\u6557\${NC}"
988
- echo -e "\${YELLOW}\u63D0\u793A: \u8ACB\u4FEE\u6B63\u985E\u578B\u932F\u8AA4\u5F8C\u518D\u63D0\u4EA4\${NC}"
989
- exit 1
990
- fi
991
-
992
- # 2. \u57F7\u884C\u6E2C\u8A66\uFF08\u53EF\u9078\uFF0C\u5982\u679C\u6E2C\u8A66\u6642\u9593\u8F03\u9577\u53EF\u4EE5\u8A3B\u89E3\u6389\uFF09
993
- echo -e "\${YELLOW}[2/2] \u57F7\u884C\u6E2C\u8A66...\${NC}"
994
- if bun test; then
995
- echo -e "\${GREEN}\u2713 \u6E2C\u8A66\u901A\u904E\${NC}\\n"
996
- else
997
- echo -e "\${RED}\u2717 \u6E2C\u8A66\u5931\u6557\${NC}"
998
- echo -e "\${YELLOW}\u63D0\u793A: \u8ACB\u4FEE\u6B63\u6E2C\u8A66\u932F\u8AA4\u5F8C\u518D\u63D0\u4EA4\${NC}"
999
- exit 1
1000
- fi
1001
-
1002
- echo -e "\${GREEN}=== Pre-commit \u6AA2\u67E5\u901A\u904E ===\${NC}\\n"
879
+ static generateRepositoryServiceProvider(repositories = [], additionalServices = []) {
880
+ const repositoryRegistrations = repositories.map((repo) => ` container.singleton('${repo}', () => new ${repo}())`).join("\n");
881
+ const serviceRegistrations = additionalServices.map((service) => ` container.singleton('${service}', () => new ${service}())`).join("\n");
882
+ const imports = [
883
+ ...repositories.map(
884
+ (repo) => `import { ${repo} } from '../Persistence/Repositories/${repo}'`
885
+ ),
886
+ ...additionalServices.map(
887
+ (service) => `import { ${service} } from '../ExternalServices/${service}'`
888
+ )
889
+ ].join("\n");
890
+ return `/**
891
+ * Repository Service Provider
892
+ *
893
+ * Binds repository interfaces to implementations.
894
+ */
895
+
896
+ import { ServiceProvider, type Container } from '@gravito/core'
897
+ ${imports}
898
+
899
+ export class RepositoryServiceProvider extends ServiceProvider {
900
+ register(container: Container): void {
901
+ ${repositoryRegistrations || " // Bind repositories here"}
902
+ ${serviceRegistrations || " // Bind external services here"}
903
+ }
904
+ }
1003
905
  `;
1004
906
  }
1005
907
  /**
1006
- * Generate CHECK_SYSTEM.md documentation.
908
+ * Generate Database Provider
1007
909
  */
1008
- generateCheckSystemDoc(context) {
1009
- return `# \u5C08\u6848\u6AA2\u67E5\u7CFB\u7D71
1010
-
1011
- \u672C\u5C08\u6848\u5DF2\u5EFA\u7ACB\u5B8C\u6574\u7684\u672C\u5730\u6AA2\u67E5\u6A5F\u5236\uFF0C\u7121\u9700\u4F9D\u8CF4 GitHub CI\u3002
1012
-
1013
- ## \u5FEB\u901F\u958B\u59CB
1014
-
1015
- ### \u57F7\u884C\u5B8C\u6574\u6AA2\u67E5
1016
- \`\`\`bash
1017
- bun run validate
1018
- \`\`\`
1019
-
1020
- ### \u57F7\u884C\u55AE\u9805\u6AA2\u67E5
1021
- \`\`\`bash
1022
- # \u985E\u578B\u6AA2\u67E5
1023
- bun run typecheck
1024
-
1025
- # \u6E2C\u8A66
1026
- bun run test
1027
-
1028
- # \u4F9D\u8CF4\u7248\u672C\u6AA2\u67E5
1029
- bun run check:deps
1030
- \`\`\`
1031
-
1032
- ## \u53EF\u7528\u547D\u4EE4
1033
-
1034
- ### Package.json \u8173\u672C
1035
-
1036
- | \u547D\u4EE4 | \u8AAA\u660E |
1037
- |------|------|
1038
- | \`bun run typecheck\` | TypeScript \u985E\u578B\u6AA2\u67E5 |
1039
- | \`bun run test\` | \u57F7\u884C\u6240\u6709\u6E2C\u8A66 |
1040
- | \`bun run check\` | \u985E\u578B\u6AA2\u67E5 + \u6E2C\u8A66 |
1041
- | \`bun run check:deps\` | \u6AA2\u67E5\u4F9D\u8CF4\u7248\u672C |
1042
- | \`bun run validate\` | \u5B8C\u6574\u9A57\u8B49\uFF08\u985E\u578B + \u6E2C\u8A66 + \u4F9D\u8CF4\uFF09 |
1043
- | \`bun run precommit\` | \u7B49\u540C\u65BC \`validate\` |
1044
-
1045
- ### Shell \u8173\u672C
1046
-
1047
- | \u8173\u672C | \u8AAA\u660E |
1048
- |------|------|
1049
- | \`./scripts/check.sh\` | \u5B8C\u6574\u5C08\u6848\u6AA2\u67E5\uFF08Shell \u7248\u672C\uFF09 |
1050
- | \`./scripts/pre-commit.sh\` | Pre-commit hook \u8173\u672C |
1051
-
1052
- ## Pre-commit Hook\uFF08\u63A8\u85A6\uFF09
1053
-
1054
- \u5B89\u88DD pre-commit hook \u5F8C\uFF0C\u6BCF\u6B21 \`git commit\` \u524D\u6703\u81EA\u52D5\u57F7\u884C\u6AA2\u67E5\uFF1A
1055
-
1056
- \`\`\`bash
1057
- # \u5B89\u88DD pre-commit hook
1058
- ln -s ../../scripts/pre-commit.sh .git/hooks/pre-commit
1059
-
1060
- # \u6216\u4F7F\u7528\u8907\u88FD\u65B9\u5F0F
1061
- cp scripts/pre-commit.sh .git/hooks/pre-commit
1062
- chmod +x .git/hooks/pre-commit
1063
- \`\`\`
1064
-
1065
- **\u529F\u80FD\uFF1A**
1066
- - \u2705 \u81EA\u52D5\u57F7\u884C\u985E\u578B\u6AA2\u67E5
1067
- - \u2705 \u81EA\u52D5\u57F7\u884C\u6E2C\u8A66
1068
- - \u274C \u6AA2\u67E5\u5931\u6557\u6642\u963B\u6B62\u63D0\u4EA4
1069
-
1070
- **\u8DF3\u904E\u6AA2\u67E5\uFF08\u4E0D\u63A8\u85A6\uFF09\uFF1A**
1071
- \`\`\`bash
1072
- git commit --no-verify -m "\u7DCA\u6025\u4FEE\u5FA9"
1073
- \`\`\`
1074
-
1075
- ## \u6AA2\u67E5\u9805\u76EE
1076
-
1077
- ### 1. \u985E\u578B\u6AA2\u67E5
1078
- - \u4F7F\u7528 \`tsc --noEmit\` \u6AA2\u67E5 TypeScript \u985E\u578B
1079
- - \u78BA\u4FDD\u6C92\u6709\u985E\u578B\u932F\u8AA4
1080
-
1081
- ### 2. \u6E2C\u8A66
1082
- - \u57F7\u884C\u6240\u6709\u55AE\u5143\u6E2C\u8A66\u548C\u6574\u5408\u6E2C\u8A66
1083
- - \u78BA\u4FDD\u6E2C\u8A66\u901A\u904E
1084
-
1085
- ### 3. \u4F9D\u8CF4\u6AA2\u67E5\uFF08\u53EF\u9078\uFF09
1086
- - \u6AA2\u67E5\u5957\u4EF6\u7248\u672C\u662F\u5426\u70BA\u6700\u65B0
1087
- - \u63D0\u4F9B\u66F4\u65B0\u5EFA\u8B70
1088
- - \u9700\u8981\u7DB2\u8DEF\u9023\u7DDA
1089
-
1090
- ## \u5DE5\u4F5C\u6D41\u7A0B\u5EFA\u8B70
1091
-
1092
- ### \u958B\u767C\u6642
1093
- 1. \u958B\u767C\u529F\u80FD
1094
- 2. \u63D0\u4EA4\u524D\u57F7\u884C \`bun run validate\`
1095
- 3. \u4FEE\u6B63\u554F\u984C
1096
- 4. \u63D0\u4EA4\u7A0B\u5F0F\u78BC
1097
-
1098
- ### \u4F7F\u7528 Pre-commit Hook\uFF08\u63A8\u85A6\uFF09
1099
- 1. \u5B89\u88DD pre-commit hook\uFF08\u53EA\u9700\u4E00\u6B21\uFF09
1100
- 2. \u6B63\u5E38\u958B\u767C\u548C\u63D0\u4EA4
1101
- 3. \u6AA2\u67E5\u6703\u81EA\u52D5\u57F7\u884C
1102
- 4. \u5982\u6709\u554F\u984C\uFF0C\u4FEE\u6B63\u5F8C\u91CD\u65B0\u63D0\u4EA4
1103
-
1104
- ## \u6A94\u6848\u7D50\u69CB
1105
-
1106
- \`\`\`
1107
- ${context.nameKebabCase}/
1108
- \u251C\u2500\u2500 package.json # \u6AA2\u67E5\u8173\u672C\u5B9A\u7FA9
1109
- \u251C\u2500\u2500 scripts/
1110
- \u2502 \u251C\u2500\u2500 check.sh # \u5B8C\u6574\u6AA2\u67E5\u8173\u672C\uFF08Shell\uFF09
1111
- \u2502 \u251C\u2500\u2500 check-dependencies.ts # \u4F9D\u8CF4\u7248\u672C\u6AA2\u67E5
1112
- \u2502 \u2514\u2500\u2500 pre-commit.sh # Pre-commit hook
1113
- \u2514\u2500\u2500 CHECK_SYSTEM.md # \u672C\u6587\u4EF6
1114
- \`\`\`
1115
-
1116
- ## \u6CE8\u610F\u4E8B\u9805
1117
-
1118
- 1. **\u4F9D\u8CF4\u6AA2\u67E5\u9700\u8981\u7DB2\u8DEF\u9023\u7DDA**\uFF1A\`check:deps\` \u9700\u8981\u9023\u63A5\u5230 npm registry
1119
- 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
1120
- 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
1121
-
1122
- ## \u6545\u969C\u6392\u9664
1123
-
1124
- ### \u6AA2\u67E5\u5931\u6557
1125
- 1. \u67E5\u770B\u932F\u8AA4\u8A0A\u606F
1126
- 2. \u4FEE\u6B63\u554F\u984C
1127
- 3. \u91CD\u65B0\u57F7\u884C\u6AA2\u67E5
910
+ static generateDatabaseProvider() {
911
+ return `/**
912
+ * Database Service Provider
913
+ */
1128
914
 
1129
- ### \u8DF3\u904E\u6AA2\u67E5
1130
- \u53EA\u6709\u5728\u7DCA\u6025\u60C5\u6CC1\u4E0B\u624D\u4F7F\u7528\uFF1A
1131
- \`\`\`bash
1132
- git commit --no-verify
1133
- \`\`\`
915
+ import { ServiceProvider, type Container, type PlanetCore } from '@gravito/core'
916
+ import { OrbitAtlas } from '@gravito/atlas'
1134
917
 
1135
- ### \u79FB\u9664 Pre-commit Hook
1136
- \`\`\`bash
1137
- rm .git/hooks/pre-commit
1138
- \`\`\`
1139
- `;
918
+ export class DatabaseProvider extends ServiceProvider {
919
+ register(_container: Container): void {
920
+ // Register database connections
1140
921
  }
1141
- /**
1142
- * Log a message if verbose mode is enabled.
1143
- */
1144
- log(message) {
1145
- if (this.config.verbose) {
1146
- console.log(message);
1147
- }
922
+
923
+ boot(core: PlanetCore): void {
924
+ // Initialize database
925
+ core.logger.info('\u{1F5C4}\uFE0F Database initialized')
1148
926
  }
1149
- /**
1150
- * Create generator context from options.
1151
- */
1152
- static createContext(name, targetDir, architecture, packageManager = "bun", extra = {}) {
1153
- const now = /* @__PURE__ */ new Date();
1154
- const pascalCase = name.replace(/[-_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : "").replace(/^./, (c) => c.toUpperCase());
1155
- const camelCase = pascalCase.replace(/^./, (c) => c.toLowerCase());
1156
- const snakeCase = name.replace(/([A-Z])/g, "_$1").toLowerCase().replace(/^_/, "").replace(/[-\s]+/g, "_");
1157
- const kebabCase = name.replace(/([A-Z])/g, "-$1").toLowerCase().replace(/^-/, "").replace(/[_\s]+/g, "-");
1158
- return {
1159
- name,
1160
- namePascalCase: pascalCase,
1161
- nameCamelCase: camelCase,
1162
- nameSnakeCase: snakeCase,
1163
- nameKebabCase: kebabCase,
1164
- targetDir,
1165
- architecture,
1166
- packageManager,
1167
- year: now.getFullYear().toString(),
1168
- date: now.toISOString().split("T")[0] ?? now.toISOString().slice(0, 10),
1169
- ...extra
1170
- };
927
+ }
928
+ `;
1171
929
  }
1172
930
  };
1173
931
 
@@ -1421,57 +1179,22 @@ var CleanArchitectureGenerator = class extends BaseGenerator {
1421
1179
  ];
1422
1180
  }
1423
1181
  // ─────────────────────────────────────────────────────────────
1424
- // Config Generators (similar to MVC but simplified)
1182
+ // Config Generators (using shared ConfigGenerator)
1425
1183
  // ─────────────────────────────────────────────────────────────
1426
1184
  generateAppConfig(context) {
1427
- return `export default {
1428
- name: process.env.APP_NAME ?? '${context.name}',
1429
- env: process.env.APP_ENV ?? 'development',
1430
- debug: process.env.APP_DEBUG === 'true',
1431
- url: process.env.APP_URL ?? 'http://localhost:3000',
1432
- key: process.env.APP_KEY,
1433
- }
1434
- `;
1185
+ return ConfigGenerator.generateSimpleAppConfig(context);
1435
1186
  }
1436
1187
  generateDatabaseConfig() {
1437
- return `export default {
1438
- default: process.env.DB_CONNECTION ?? 'sqlite',
1439
- connections: {
1440
- sqlite: {
1441
- driver: 'sqlite',
1442
- database: process.env.DB_DATABASE ?? 'database/database.sqlite',
1443
- },
1444
- },
1445
- }
1446
- `;
1188
+ return ConfigGenerator.generateSimpleDatabaseConfig();
1447
1189
  }
1448
1190
  generateAuthConfig() {
1449
- return `export default {
1450
- defaults: { guard: 'web' },
1451
- guards: {
1452
- web: { driver: 'session', provider: 'users' },
1453
- api: { driver: 'token', provider: 'users' },
1454
- },
1455
- }
1456
- `;
1191
+ return ConfigGenerator.generateAuthConfig();
1457
1192
  }
1458
1193
  generateCacheConfig() {
1459
- return `export default {
1460
- default: process.env.CACHE_DRIVER ?? 'memory',
1461
- stores: {
1462
- memory: { driver: 'memory' },
1463
- },
1464
- }
1465
- `;
1194
+ return ConfigGenerator.generateCacheConfig();
1466
1195
  }
1467
1196
  generateLoggingConfig() {
1468
- return `export default {
1469
- default: process.env.LOG_CHANNEL ?? 'console',
1470
- channels: {
1471
- console: { driver: 'console', level: process.env.LOG_LEVEL ?? 'debug' },
1472
- },
1473
- }
1474
- `;
1197
+ return ConfigGenerator.generateLoggingConfig();
1475
1198
  }
1476
1199
  generateUserEntity() {
1477
1200
  return `/**
@@ -1798,103 +1521,30 @@ export class MailService implements IMailService {
1798
1521
  `;
1799
1522
  }
1800
1523
  generateAppServiceProvider(context) {
1801
- return `/**
1802
- * App Service Provider
1803
- */
1804
-
1805
- import { ServiceProvider, type Container, type PlanetCore } from '@gravito/core'
1806
-
1807
- export class AppServiceProvider extends ServiceProvider {
1808
- register(_container: Container): void {
1809
- // Register application services
1810
- }
1811
-
1812
- boot(_core: PlanetCore): void {
1813
- console.log('${context.name} (Clean Architecture) booted!')
1814
- }
1815
- }
1816
- `;
1524
+ return ServiceProviderGenerator.generateAppServiceProvider(context, "Clean Architecture");
1817
1525
  }
1818
1526
  generateRepositoryServiceProvider() {
1819
- return `/**
1820
- * Repository Service Provider
1821
- *
1822
- * Binds repository interfaces to implementations.
1823
- */
1824
-
1825
- import { ServiceProvider, type Container } from '@gravito/core'
1826
- import { UserRepository } from '../Persistence/Repositories/UserRepository'
1827
- import { MailService } from '../ExternalServices/MailService'
1828
-
1829
- export class RepositoryServiceProvider extends ServiceProvider {
1830
- register(container: Container): void {
1831
- // Bind repositories
1832
- container.singleton('userRepository', () => new UserRepository())
1833
-
1834
- // Bind external services
1835
- container.singleton('mailService', () => new MailService())
1836
- }
1837
- }
1838
- `;
1527
+ return ServiceProviderGenerator.generateRepositoryServiceProvider(
1528
+ ["UserRepository"],
1529
+ ["MailService"]
1530
+ );
1839
1531
  }
1840
1532
  generateProvidersIndex() {
1841
- return `/**
1842
- * Application Service Providers
1843
- */
1844
-
1845
- export { AppServiceProvider } from './AppServiceProvider'
1846
- export { RepositoryServiceProvider } from './RepositoryServiceProvider'
1847
- export { MiddlewareProvider } from './MiddlewareProvider'
1848
- export { RouteProvider } from './RouteProvider'
1849
- `;
1533
+ return ServiceProviderGenerator.generateProvidersIndex([
1534
+ "AppServiceProvider",
1535
+ "RepositoryServiceProvider",
1536
+ "MiddlewareProvider",
1537
+ "RouteProvider"
1538
+ ]);
1850
1539
  }
1851
1540
  generateMiddlewareProvider() {
1852
- return `/**
1853
- * Middleware Service Provider
1854
- */
1855
-
1856
- import {
1857
- ServiceProvider,
1858
- type Container,
1859
- type PlanetCore,
1860
- bodySizeLimit,
1861
- securityHeaders,
1862
- } from '@gravito/core'
1863
-
1864
- export class MiddlewareProvider extends ServiceProvider {
1865
- register(_container: Container): void {}
1866
-
1867
- boot(core: PlanetCore): void {
1868
- const isDev = process.env.NODE_ENV !== 'production'
1869
-
1870
- core.adapter.use('*', securityHeaders({
1871
- contentSecurityPolicy: isDev ? false : undefined,
1872
- }))
1873
-
1874
- core.adapter.use('*', bodySizeLimit(10 * 1024 * 1024))
1875
-
1876
- core.logger.info('\u{1F6E1}\uFE0F Middleware registered')
1877
- }
1878
- }
1879
- `;
1541
+ return ServiceProviderGenerator.generateMiddlewareProvider();
1880
1542
  }
1881
1543
  generateRouteProvider() {
1882
- return `/**
1883
- * Route Service Provider
1884
- */
1885
-
1886
- import { ServiceProvider, type Container, type PlanetCore } from '@gravito/core'
1887
- import { registerApiRoutes } from '../../Interface/Http/Routes/api'
1888
-
1889
- export class RouteProvider extends ServiceProvider {
1890
- register(_container: Container): void {}
1891
-
1892
- boot(core: PlanetCore): void {
1893
- registerApiRoutes(core.router)
1894
- core.logger.info('\u{1F6E4}\uFE0F Routes registered')
1895
- }
1896
- }
1897
- `;
1544
+ return ServiceProviderGenerator.generateRouteProvider(
1545
+ "../../Interface/Http/Routes/api",
1546
+ "named"
1547
+ );
1898
1548
  }
1899
1549
  // ─────────────────────────────────────────────────────────────
1900
1550
  // Interface Layer
@@ -2117,7 +1767,7 @@ Created with \u2764\uFE0F using Gravito Framework
2117
1767
  build: "bun build ./src/bootstrap.ts --outdir ./dist --target bun",
2118
1768
  start: "bun run dist/bootstrap.js",
2119
1769
  test: "bun test",
2120
- typecheck: "tsc --noEmit",
1770
+ typecheck: "bun tsc --noEmit",
2121
1771
  check: "bun run typecheck && bun run test",
2122
1772
  "check:deps": "bun run scripts/check-dependencies.ts",
2123
1773
  validate: "bun run check && bun run check:deps",
@@ -2132,107 +1782,16 @@ Created with \u2764\uFE0F using Gravito Framework
2132
1782
  },
2133
1783
  devDependencies: {
2134
1784
  "bun-types": "latest",
2135
- typescript: "^5.0.0"
1785
+ typescript: "^5.9.3"
2136
1786
  }
2137
1787
  };
2138
1788
  return JSON.stringify(pkg, null, 2);
2139
1789
  }
2140
1790
  };
2141
1791
 
2142
- // src/generators/DddGenerator.ts
2143
- var DddGenerator = class extends BaseGenerator {
2144
- get architectureType() {
2145
- return "ddd";
2146
- }
2147
- get displayName() {
2148
- return "Domain-Driven Design (DDD)";
2149
- }
2150
- get description() {
2151
- return "Full DDD with Bounded Contexts, Aggregates, and Event-Driven patterns";
2152
- }
2153
- getDirectoryStructure(context) {
2154
- return [
2155
- {
2156
- type: "directory",
2157
- name: "config",
2158
- children: [
2159
- { type: "file", name: "app.ts", content: this.generateAppConfig(context) },
2160
- { type: "file", name: "database.ts", content: this.generateDatabaseConfig() },
2161
- { type: "file", name: "modules.ts", content: this.generateModulesConfig() },
2162
- { type: "file", name: "cache.ts", content: this.generateCacheConfig() },
2163
- { type: "file", name: "logging.ts", content: this.generateLoggingConfig() }
2164
- ]
2165
- },
2166
- {
2167
- type: "directory",
2168
- name: "src",
2169
- children: [
2170
- // Bootstrap - Application startup and configuration
2171
- this.generateBootstrapDirectory(context),
2172
- // Shared - Cross-module shared components
2173
- this.generateShared(),
2174
- // Modules - Bounded Contexts
2175
- {
2176
- type: "directory",
2177
- name: "Modules",
2178
- children: [
2179
- this.generateModule("Ordering", context),
2180
- this.generateModule("Catalog", context)
2181
- ]
2182
- },
2183
- { type: "file", name: "main.ts", content: this.generateMainEntry(context) }
2184
- ]
2185
- },
2186
- {
2187
- type: "directory",
2188
- name: "tests",
2189
- children: [
2190
- {
2191
- type: "directory",
2192
- name: "Modules",
2193
- children: [
2194
- {
2195
- type: "directory",
2196
- name: "Ordering",
2197
- children: [
2198
- {
2199
- type: "directory",
2200
- name: "Unit",
2201
- children: [{ type: "file", name: ".gitkeep", content: "" }]
2202
- },
2203
- {
2204
- type: "directory",
2205
- name: "Integration",
2206
- children: [{ type: "file", name: ".gitkeep", content: "" }]
2207
- }
2208
- ]
2209
- },
2210
- {
2211
- type: "directory",
2212
- name: "Catalog",
2213
- children: [
2214
- {
2215
- type: "directory",
2216
- name: "Unit",
2217
- children: [{ type: "file", name: ".gitkeep", content: "" }]
2218
- }
2219
- ]
2220
- }
2221
- ]
2222
- },
2223
- {
2224
- type: "directory",
2225
- name: "Shared",
2226
- children: [{ type: "file", name: ".gitkeep", content: "" }]
2227
- }
2228
- ]
2229
- }
2230
- ];
2231
- }
2232
- // ─────────────────────────────────────────────────────────────
2233
- // Bootstrap Directory
2234
- // ─────────────────────────────────────────────────────────────
2235
- generateBootstrapDirectory(context) {
1792
+ // src/generators/ddd/BootstrapGenerator.ts
1793
+ var BootstrapGenerator = class {
1794
+ generate(context) {
2236
1795
  return {
2237
1796
  type: "directory",
2238
1797
  name: "Bootstrap",
@@ -2244,207 +1803,33 @@ var DddGenerator = class extends BaseGenerator {
2244
1803
  ]
2245
1804
  };
2246
1805
  }
2247
- // ─────────────────────────────────────────────────────────────
2248
- // Shared Directory (replaces SharedKernel with user's structure)
2249
- // ─────────────────────────────────────────────────────────────
2250
- generateShared() {
1806
+ generateConfigDirectory(context) {
2251
1807
  return {
2252
1808
  type: "directory",
2253
- name: "Shared",
1809
+ name: "config",
2254
1810
  children: [
2255
- {
2256
- type: "directory",
2257
- name: "Domain",
2258
- children: [
2259
- {
2260
- type: "directory",
2261
- name: "ValueObjects",
2262
- children: [
2263
- { type: "file", name: "Id.ts", content: this.generateIdValueObject() },
2264
- { type: "file", name: "Money.ts", content: this.generateMoneyValueObject() },
2265
- { type: "file", name: "Email.ts", content: this.generateEmailValueObject() }
2266
- ]
2267
- }
2268
- ]
2269
- },
2270
- {
2271
- type: "directory",
2272
- name: "Infrastructure",
2273
- children: [
2274
- {
2275
- type: "directory",
2276
- name: "EventBus",
2277
- children: [
2278
- {
2279
- type: "file",
2280
- name: "EventDispatcher.ts",
2281
- content: this.generateEventDispatcher()
2282
- }
2283
- ]
2284
- }
2285
- ]
2286
- },
2287
- {
2288
- type: "directory",
2289
- name: "Exceptions",
2290
- children: [
2291
- { type: "file", name: "Handler.ts", content: this.generateExceptionHandler() }
2292
- ]
2293
- }
1811
+ { type: "file", name: "app.ts", content: this.generateAppConfig(context) },
1812
+ { type: "file", name: "database.ts", content: this.generateDatabaseConfig() },
1813
+ { type: "file", name: "modules.ts", content: this.generateModulesConfig() },
1814
+ { type: "file", name: "cache.ts", content: this.generateCacheConfig() },
1815
+ { type: "file", name: "logging.ts", content: this.generateLoggingConfig() }
2294
1816
  ]
2295
1817
  };
2296
1818
  }
2297
- // ─────────────────────────────────────────────────────────────
2298
- // Module Generator (replaces Bounded Context)
2299
- // ─────────────────────────────────────────────────────────────
2300
- generateModule(name, context) {
2301
- return {
2302
- type: "directory",
2303
- name,
2304
- children: [
2305
- // Domain Layer
2306
- {
2307
- type: "directory",
2308
- name: "Domain",
2309
- children: [
2310
- {
2311
- type: "directory",
2312
- name: "Aggregates",
2313
- children: [
2314
- {
2315
- type: "directory",
2316
- name,
2317
- children: [
2318
- { type: "file", name: `${name}.ts`, content: this.generateAggregate(name) },
2319
- {
2320
- type: "file",
2321
- name: `${name}Status.ts`,
2322
- content: this.generateAggregateStatus(name)
2323
- }
2324
- ]
2325
- }
2326
- ]
2327
- },
2328
- {
2329
- type: "directory",
2330
- name: "Events",
2331
- children: [
2332
- {
2333
- type: "file",
2334
- name: `${name}Created.ts`,
2335
- content: this.generateCreatedEvent(name)
2336
- }
2337
- ]
2338
- },
2339
- {
2340
- type: "directory",
2341
- name: "Repositories",
2342
- children: [
2343
- {
2344
- type: "file",
2345
- name: `I${name}Repository.ts`,
2346
- content: this.generateRepositoryInterface(name)
2347
- }
2348
- ]
2349
- },
2350
- {
2351
- type: "directory",
2352
- name: "Services",
2353
- children: [{ type: "file", name: ".gitkeep", content: "" }]
2354
- }
2355
- ]
2356
- },
2357
- // Application Layer
2358
- {
2359
- type: "directory",
2360
- name: "Application",
2361
- children: [
2362
- {
2363
- type: "directory",
2364
- name: "Commands",
2365
- children: [
2366
- {
2367
- type: "directory",
2368
- name: `Create${name}`,
2369
- children: [
2370
- {
2371
- type: "file",
2372
- name: `Create${name}Command.ts`,
2373
- content: this.generateCommand(name)
2374
- },
2375
- {
2376
- type: "file",
2377
- name: `Create${name}Handler.ts`,
2378
- content: this.generateCommandHandler(name)
2379
- }
2380
- ]
2381
- }
2382
- ]
2383
- },
2384
- {
2385
- type: "directory",
2386
- name: "Queries",
2387
- children: [
2388
- {
2389
- type: "directory",
2390
- name: `Get${name}ById`,
2391
- children: [
2392
- {
2393
- type: "file",
2394
- name: `Get${name}ByIdQuery.ts`,
2395
- content: this.generateQuery(name)
2396
- },
2397
- {
2398
- type: "file",
2399
- name: `Get${name}ByIdHandler.ts`,
2400
- content: this.generateQueryHandler(name)
2401
- }
2402
- ]
2403
- }
2404
- ]
2405
- },
2406
- {
2407
- type: "directory",
2408
- name: "DTOs",
2409
- children: [{ type: "file", name: `${name}DTO.ts`, content: this.generateDTO(name) }]
2410
- }
2411
- ]
2412
- },
2413
- // Infrastructure Layer
2414
- {
2415
- type: "directory",
2416
- name: "Infrastructure",
2417
- children: [
2418
- {
2419
- type: "directory",
2420
- name: "Persistence",
2421
- children: [
2422
- {
2423
- type: "file",
2424
- name: `${name}Repository.ts`,
2425
- content: this.generateRepository(name)
2426
- }
2427
- ]
2428
- },
2429
- {
2430
- type: "directory",
2431
- name: "Providers",
2432
- children: [
2433
- {
2434
- type: "file",
2435
- name: `${name}ServiceProvider.ts`,
2436
- content: this.generateModuleServiceProvider(name, context)
2437
- }
2438
- ]
2439
- }
2440
- ]
2441
- }
2442
- ]
2443
- };
1819
+ generateMainEntry(_context) {
1820
+ return `/**
1821
+ * Application Entry Point
1822
+ *
1823
+ * Start the HTTP server.
1824
+ */
1825
+
1826
+ import { createApp } from './Bootstrap/app'
1827
+
1828
+ const app = await createApp()
1829
+
1830
+ export default app.liftoff()
1831
+ `;
2444
1832
  }
2445
- // ─────────────────────────────────────────────────────────────
2446
- // Bootstrap File Generators
2447
- // ─────────────────────────────────────────────────────────────
2448
1833
  generateBootstrapApp(_context) {
2449
1834
  return `/**
2450
1835
  * Application Bootstrap
@@ -2570,20 +1955,6 @@ export function registerRoutes(router: any): void {
2570
1955
  // Catalog module
2571
1956
  router.get('/api/products', (c: any) => c.json({ message: 'Product list' }))
2572
1957
  }
2573
- `;
2574
- }
2575
- generateMainEntry(_context) {
2576
- return `/**
2577
- * Application Entry Point
2578
- *
2579
- * Start the HTTP server.
2580
- */
2581
-
2582
- import { createApp } from './Bootstrap/app'
2583
-
2584
- const app = await createApp()
2585
-
2586
- export default app.liftoff()
2587
1958
  `;
2588
1959
  }
2589
1960
  generateModulesConfig() {
@@ -2614,60 +1985,6 @@ export default {
2614
1985
  }
2615
1986
  `;
2616
1987
  }
2617
- generateModuleServiceProvider(name, _context) {
2618
- return `/**
2619
- * ${name} Service Provider
2620
- */
2621
-
2622
- import { ServiceProvider, type Container, type PlanetCore } from '@gravito/core'
2623
- import { ${name}Repository } from '../Persistence/${name}Repository'
2624
-
2625
- export class ${name}ServiceProvider extends ServiceProvider {
2626
- register(container: Container): void {
2627
- container.singleton('${name.toLowerCase()}Repository', () => new ${name}Repository())
2628
- }
2629
-
2630
- boot(_core: PlanetCore): void {
2631
- console.log('[${name}] Module loaded')
2632
- }
2633
- }
2634
- `;
2635
- }
2636
- /**
2637
- * Override package.json for DDD architecture (uses main.ts instead of bootstrap.ts)
2638
- */
2639
- generatePackageJson(context) {
2640
- const pkg = {
2641
- name: context.nameKebabCase,
2642
- version: "0.1.0",
2643
- type: "module",
2644
- scripts: {
2645
- dev: "bun run --watch src/main.ts",
2646
- build: "bun build ./src/main.ts --outdir ./dist --target bun",
2647
- start: "bun run dist/main.js",
2648
- test: "bun test",
2649
- typecheck: "tsc --noEmit",
2650
- check: "bun run typecheck && bun run test",
2651
- "check:deps": "bun run scripts/check-dependencies.ts",
2652
- validate: "bun run check && bun run check:deps",
2653
- precommit: "bun run validate",
2654
- "docker:build": `docker build -t ${context.nameKebabCase} .`,
2655
- "docker:run": `docker run -it -p 3000:3000 ${context.nameKebabCase}`
2656
- },
2657
- dependencies: {
2658
- "@gravito/core": "^1.0.0-beta.5",
2659
- "@gravito/enterprise": "workspace:*"
2660
- },
2661
- devDependencies: {
2662
- "bun-types": "latest",
2663
- typescript: "^5.0.0"
2664
- }
2665
- };
2666
- return JSON.stringify(pkg, null, 2);
2667
- }
2668
- // ─────────────────────────────────────────────────────────────
2669
- // Config Generators
2670
- // ─────────────────────────────────────────────────────────────
2671
1988
  generateAppConfig(context) {
2672
1989
  return `export default {
2673
1990
  name: process.env.APP_NAME ?? '${context.name}',
@@ -2702,159 +2019,155 @@ export class ${name}ServiceProvider extends ServiceProvider {
2702
2019
  }
2703
2020
  `;
2704
2021
  }
2705
- // ─────────────────────────────────────────────────────────────
2706
- // Shared Kernel Files
2707
- // ─────────────────────────────────────────────────────────────
2708
- generateIdValueObject() {
2709
- return `/**
2710
- * ID Value Object
2711
- *
2712
- * Shared identifier across all contexts.
2713
- */
2714
-
2715
- import { ValueObject } from '@gravito/enterprise'
2716
-
2717
- interface IdProps {
2718
- value: string
2719
- }
2720
-
2721
- export class Id extends ValueObject<IdProps> {
2722
- private constructor(value: string) {
2723
- super({ value })
2724
- }
2725
-
2726
- static create(): Id {
2727
- return new Id(crypto.randomUUID())
2728
- }
2729
-
2730
- static from(value: string): Id {
2731
- if (!value) throw new Error('Id cannot be empty')
2732
- return new Id(value)
2733
- }
2734
-
2735
- get value(): string {
2736
- return this.props.value
2737
- }
2738
-
2739
- toString(): string {
2740
- return this.props.value
2741
- }
2742
- }
2743
- `;
2744
- }
2745
- generateMoneyValueObject() {
2746
- return `/**
2747
- * Money Value Object
2748
- */
2749
-
2750
- import { ValueObject } from '@gravito/enterprise'
2751
-
2752
- interface MoneyProps {
2753
- amount: number
2754
- currency: string
2755
- }
2756
-
2757
- export class Money extends ValueObject<MoneyProps> {
2758
- constructor(amount: number, currency: string = 'USD') {
2759
- if (amount < 0) throw new Error('Amount cannot be negative')
2760
- super({ amount, currency })
2761
- }
2762
-
2763
- get amount(): number {
2764
- return this.props.amount
2765
- }
2766
-
2767
- get currency(): string {
2768
- return this.props.currency
2769
- }
2770
-
2771
- add(other: Money): Money {
2772
- this.assertSameCurrency(other)
2773
- return new Money(this.amount + other.amount, this.currency)
2774
- }
2775
-
2776
- subtract(other: Money): Money {
2777
- this.assertSameCurrency(other)
2778
- return new Money(this.amount - other.amount, this.currency)
2779
- }
2780
-
2781
- private assertSameCurrency(other: Money): void {
2782
- if (this.currency !== other.currency) {
2783
- throw new Error('Cannot operate on different currencies')
2784
- }
2785
- }
2786
- }
2787
- `;
2788
- }
2789
- generateEmailValueObject() {
2790
- return `/**
2791
- * Email Value Object
2792
- */
2793
-
2794
- import { ValueObject } from '@gravito/enterprise'
2795
-
2796
- interface EmailProps {
2797
- value: string
2798
- }
2799
-
2800
- export class Email extends ValueObject<EmailProps> {
2801
- private constructor(value: string) {
2802
- super({ value: value.toLowerCase().trim() })
2803
- }
2804
-
2805
- static create(email: string): Email {
2806
- if (!Email.isValid(email)) {
2807
- throw new Error(\`Invalid email: \${email}\`)
2808
- }
2809
- return new Email(email)
2810
- }
2811
-
2812
- static isValid(email: string): boolean {
2813
- return /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email)
2814
- }
2815
-
2816
- get value(): string {
2817
- return this.props.value
2818
- }
2819
- }
2820
- `;
2821
- }
2822
- generateEventDispatcher() {
2823
- return `/**
2824
- * Event Dispatcher
2825
- */
2826
-
2827
- import type { DomainEvent } from '@gravito/enterprise'
2828
-
2829
- type EventHandler = (event: DomainEvent) => void | Promise<void>
2830
-
2831
- export class EventDispatcher {
2832
- private handlers: Map<string, EventHandler[]> = new Map()
2833
-
2834
- subscribe(eventName: string, handler: EventHandler): void {
2835
- const handlers = this.handlers.get(eventName) ?? []
2836
- handlers.push(handler)
2837
- this.handlers.set(eventName, handlers)
2838
- }
2839
-
2840
- async dispatch(event: DomainEvent): Promise<void> {
2841
- const handlers = this.handlers.get(event.eventName) ?? []
2842
- for (const handler of handlers) {
2843
- await handler(event)
2844
- }
2845
- }
2022
+ };
2846
2023
 
2847
- async dispatchAll(events: DomainEvent[]): Promise<void> {
2848
- for (const event of events) {
2849
- await this.dispatch(event)
2850
- }
2851
- }
2852
- }
2853
- `;
2024
+ // src/generators/ddd/ModuleGenerator.ts
2025
+ var ModuleGenerator = class {
2026
+ generate(name, context) {
2027
+ return {
2028
+ type: "directory",
2029
+ name,
2030
+ children: [
2031
+ // Domain Layer
2032
+ {
2033
+ type: "directory",
2034
+ name: "Domain",
2035
+ children: [
2036
+ {
2037
+ type: "directory",
2038
+ name: "Aggregates",
2039
+ children: [
2040
+ {
2041
+ type: "directory",
2042
+ name,
2043
+ children: [
2044
+ { type: "file", name: `${name}.ts`, content: this.generateAggregate(name) },
2045
+ {
2046
+ type: "file",
2047
+ name: `${name}Status.ts`,
2048
+ content: this.generateAggregateStatus(name)
2049
+ }
2050
+ ]
2051
+ }
2052
+ ]
2053
+ },
2054
+ {
2055
+ type: "directory",
2056
+ name: "Events",
2057
+ children: [
2058
+ {
2059
+ type: "file",
2060
+ name: `${name}Created.ts`,
2061
+ content: this.generateCreatedEvent(name)
2062
+ }
2063
+ ]
2064
+ },
2065
+ {
2066
+ type: "directory",
2067
+ name: "Repositories",
2068
+ children: [
2069
+ {
2070
+ type: "file",
2071
+ name: `I${name}Repository.ts`,
2072
+ content: this.generateRepositoryInterface(name)
2073
+ }
2074
+ ]
2075
+ },
2076
+ {
2077
+ type: "directory",
2078
+ name: "Services",
2079
+ children: [{ type: "file", name: ".gitkeep", content: "" }]
2080
+ }
2081
+ ]
2082
+ },
2083
+ // Application Layer
2084
+ {
2085
+ type: "directory",
2086
+ name: "Application",
2087
+ children: [
2088
+ {
2089
+ type: "directory",
2090
+ name: "Commands",
2091
+ children: [
2092
+ {
2093
+ type: "directory",
2094
+ name: `Create${name}`,
2095
+ children: [
2096
+ {
2097
+ type: "file",
2098
+ name: `Create${name}Command.ts`,
2099
+ content: this.generateCommand(name)
2100
+ },
2101
+ {
2102
+ type: "file",
2103
+ name: `Create${name}Handler.ts`,
2104
+ content: this.generateCommandHandler(name)
2105
+ }
2106
+ ]
2107
+ }
2108
+ ]
2109
+ },
2110
+ {
2111
+ type: "directory",
2112
+ name: "Queries",
2113
+ children: [
2114
+ {
2115
+ type: "directory",
2116
+ name: `Get${name}ById`,
2117
+ children: [
2118
+ {
2119
+ type: "file",
2120
+ name: `Get${name}ByIdQuery.ts`,
2121
+ content: this.generateQuery(name)
2122
+ },
2123
+ {
2124
+ type: "file",
2125
+ name: `Get${name}ByIdHandler.ts`,
2126
+ content: this.generateQueryHandler(name)
2127
+ }
2128
+ ]
2129
+ }
2130
+ ]
2131
+ },
2132
+ {
2133
+ type: "directory",
2134
+ name: "DTOs",
2135
+ children: [{ type: "file", name: `${name}DTO.ts`, content: this.generateDTO(name) }]
2136
+ }
2137
+ ]
2138
+ },
2139
+ // Infrastructure Layer
2140
+ {
2141
+ type: "directory",
2142
+ name: "Infrastructure",
2143
+ children: [
2144
+ {
2145
+ type: "directory",
2146
+ name: "Persistence",
2147
+ children: [
2148
+ {
2149
+ type: "file",
2150
+ name: `${name}Repository.ts`,
2151
+ content: this.generateRepository(name)
2152
+ }
2153
+ ]
2154
+ },
2155
+ {
2156
+ type: "directory",
2157
+ name: "Providers",
2158
+ children: [
2159
+ {
2160
+ type: "file",
2161
+ name: `${name}ServiceProvider.ts`,
2162
+ content: this.generateModuleServiceProvider(name, context)
2163
+ }
2164
+ ]
2165
+ }
2166
+ ]
2167
+ }
2168
+ ]
2169
+ };
2854
2170
  }
2855
- // ─────────────────────────────────────────────────────────────
2856
- // Bounded Context Templates
2857
- // ─────────────────────────────────────────────────────────────
2858
2171
  generateAggregate(name) {
2859
2172
  return `/**
2860
2173
  * ${name} Aggregate Root
@@ -3076,15 +2389,359 @@ export class ${name}Repository implements I${name}Repository {
3076
2389
  }
3077
2390
  `;
3078
2391
  }
3079
- generateExceptionHandler() {
3080
- return `/**
3081
- * Exception Handler
3082
- */
3083
-
3084
- export function report(error: unknown): void {
3085
- console.error('[Exception]', error)
3086
- }
3087
- `;
2392
+ generateModuleServiceProvider(name, _context) {
2393
+ return `/**
2394
+ * ${name} Service Provider
2395
+ */
2396
+
2397
+ import { ServiceProvider, type Container, type PlanetCore } from '@gravito/core'
2398
+ import { ${name}Repository } from '../Persistence/${name}Repository'
2399
+
2400
+ export class ${name}ServiceProvider extends ServiceProvider {
2401
+ register(container: Container): void {
2402
+ container.singleton('${name.toLowerCase()}Repository', () => new ${name}Repository())
2403
+ }
2404
+
2405
+ boot(_core: PlanetCore): void {
2406
+ console.log('[${name}] Module loaded')
2407
+ }
2408
+ }
2409
+ `;
2410
+ }
2411
+ };
2412
+
2413
+ // src/generators/ddd/SharedKernelGenerator.ts
2414
+ var SharedKernelGenerator = class {
2415
+ generate() {
2416
+ return {
2417
+ type: "directory",
2418
+ name: "Shared",
2419
+ children: [
2420
+ {
2421
+ type: "directory",
2422
+ name: "Domain",
2423
+ children: [
2424
+ {
2425
+ type: "directory",
2426
+ name: "ValueObjects",
2427
+ children: [
2428
+ { type: "file", name: "Id.ts", content: this.generateIdValueObject() },
2429
+ { type: "file", name: "Money.ts", content: this.generateMoneyValueObject() },
2430
+ { type: "file", name: "Email.ts", content: this.generateEmailValueObject() }
2431
+ ]
2432
+ }
2433
+ ]
2434
+ },
2435
+ {
2436
+ type: "directory",
2437
+ name: "Infrastructure",
2438
+ children: [
2439
+ {
2440
+ type: "directory",
2441
+ name: "EventBus",
2442
+ children: [
2443
+ {
2444
+ type: "file",
2445
+ name: "EventDispatcher.ts",
2446
+ content: this.generateEventDispatcher()
2447
+ }
2448
+ ]
2449
+ }
2450
+ ]
2451
+ },
2452
+ {
2453
+ type: "directory",
2454
+ name: "Exceptions",
2455
+ children: [
2456
+ { type: "file", name: "Handler.ts", content: this.generateExceptionHandler() }
2457
+ ]
2458
+ }
2459
+ ]
2460
+ };
2461
+ }
2462
+ generateIdValueObject() {
2463
+ return `/**
2464
+ * ID Value Object
2465
+ *
2466
+ * Shared identifier across all contexts.
2467
+ */
2468
+
2469
+ import { ValueObject } from '@gravito/enterprise'
2470
+
2471
+ interface IdProps {
2472
+ value: string
2473
+ }
2474
+
2475
+ export class Id extends ValueObject<IdProps> {
2476
+ private constructor(value: string) {
2477
+ super({ value })
2478
+ }
2479
+
2480
+ static create(): Id {
2481
+ return new Id(crypto.randomUUID())
2482
+ }
2483
+
2484
+ static from(value: string): Id {
2485
+ if (!value) throw new Error('Id cannot be empty')
2486
+ return new Id(value)
2487
+ }
2488
+
2489
+ get value(): string {
2490
+ return this.props.value
2491
+ }
2492
+
2493
+ toString(): string {
2494
+ return this.props.value
2495
+ }
2496
+ }
2497
+ `;
2498
+ }
2499
+ generateMoneyValueObject() {
2500
+ return `/**
2501
+ * Money Value Object
2502
+ */
2503
+
2504
+ import { ValueObject } from '@gravito/enterprise'
2505
+
2506
+ interface MoneyProps {
2507
+ amount: number
2508
+ currency: string
2509
+ }
2510
+
2511
+ export class Money extends ValueObject<MoneyProps> {
2512
+ constructor(amount: number, currency: string = 'USD') {
2513
+ if (amount < 0) throw new Error('Amount cannot be negative')
2514
+ super({ amount, currency })
2515
+ }
2516
+
2517
+ get amount(): number {
2518
+ return this.props.amount
2519
+ }
2520
+
2521
+ get currency(): string {
2522
+ return this.props.currency
2523
+ }
2524
+
2525
+ add(other: Money): Money {
2526
+ this.assertSameCurrency(other)
2527
+ return new Money(this.amount + other.amount, this.currency)
2528
+ }
2529
+
2530
+ subtract(other: Money): Money {
2531
+ this.assertSameCurrency(other)
2532
+ return new Money(this.amount - other.amount, this.currency)
2533
+ }
2534
+
2535
+ private assertSameCurrency(other: Money): void {
2536
+ if (this.currency !== other.currency) {
2537
+ throw new Error('Cannot operate on different currencies')
2538
+ }
2539
+ }
2540
+ }
2541
+ `;
2542
+ }
2543
+ generateEmailValueObject() {
2544
+ return `/**
2545
+ * Email Value Object
2546
+ */
2547
+
2548
+ import { ValueObject } from '@gravito/enterprise'
2549
+
2550
+ interface EmailProps {
2551
+ value: string
2552
+ }
2553
+
2554
+ export class Email extends ValueObject<EmailProps> {
2555
+ private constructor(value: string) {
2556
+ super({ value: value.toLowerCase().trim() })
2557
+ }
2558
+
2559
+ static create(email: string): Email {
2560
+ if (!Email.isValid(email)) {
2561
+ throw new Error(\`Invalid email: \${email}\`)
2562
+ }
2563
+ return new Email(email)
2564
+ }
2565
+
2566
+ static isValid(email: string): boolean {
2567
+ return /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email)
2568
+ }
2569
+
2570
+ get value(): string {
2571
+ return this.props.value
2572
+ }
2573
+ }
2574
+ `;
2575
+ }
2576
+ generateEventDispatcher() {
2577
+ return `/**
2578
+ * Event Dispatcher
2579
+ */
2580
+
2581
+ import type { DomainEvent } from '@gravito/enterprise'
2582
+
2583
+ type EventHandler = (event: DomainEvent) => void | Promise<void>
2584
+
2585
+ export class EventDispatcher {
2586
+ private handlers: Map<string, EventHandler[]> = new Map()
2587
+
2588
+ subscribe(eventName: string, handler: EventHandler): void {
2589
+ const handlers = this.handlers.get(eventName) ?? []
2590
+ handlers.push(handler)
2591
+ this.handlers.set(eventName, handlers)
2592
+ }
2593
+
2594
+ async dispatch(event: DomainEvent): Promise<void> {
2595
+ const handlers = this.handlers.get(event.eventName) ?? []
2596
+ for (const handler of handlers) {
2597
+ await handler(event)
2598
+ }
2599
+ }
2600
+
2601
+ async dispatchAll(events: DomainEvent[]): Promise<void> {
2602
+ for (const event of events) {
2603
+ await this.dispatch(event)
2604
+ }
2605
+ }
2606
+ }
2607
+ `;
2608
+ }
2609
+ generateExceptionHandler() {
2610
+ return `/**
2611
+ * Exception Handler
2612
+ */
2613
+
2614
+ export function report(error: unknown): void {
2615
+ console.error('[Exception]', error)
2616
+ }
2617
+ `;
2618
+ }
2619
+ };
2620
+
2621
+ // src/generators/DddGenerator.ts
2622
+ var DddGenerator = class extends BaseGenerator {
2623
+ moduleGenerator;
2624
+ sharedKernelGenerator;
2625
+ bootstrapGenerator;
2626
+ constructor(config) {
2627
+ super(config);
2628
+ this.moduleGenerator = new ModuleGenerator();
2629
+ this.sharedKernelGenerator = new SharedKernelGenerator();
2630
+ this.bootstrapGenerator = new BootstrapGenerator();
2631
+ }
2632
+ get architectureType() {
2633
+ return "ddd";
2634
+ }
2635
+ get displayName() {
2636
+ return "Domain-Driven Design (DDD)";
2637
+ }
2638
+ get description() {
2639
+ return "Full DDD with Bounded Contexts, Aggregates, and Event-Driven patterns";
2640
+ }
2641
+ getDirectoryStructure(context) {
2642
+ return [
2643
+ this.bootstrapGenerator.generateConfigDirectory(context),
2644
+ {
2645
+ type: "directory",
2646
+ name: "src",
2647
+ children: [
2648
+ // Bootstrap - Application startup and configuration
2649
+ this.bootstrapGenerator.generate(context),
2650
+ // Shared - Cross-module shared components
2651
+ this.sharedKernelGenerator.generate(),
2652
+ // Modules - Bounded Contexts
2653
+ {
2654
+ type: "directory",
2655
+ name: "Modules",
2656
+ children: [
2657
+ this.moduleGenerator.generate("Ordering", context),
2658
+ this.moduleGenerator.generate("Catalog", context)
2659
+ ]
2660
+ },
2661
+ {
2662
+ type: "file",
2663
+ name: "main.ts",
2664
+ content: this.bootstrapGenerator.generateMainEntry(context)
2665
+ }
2666
+ ]
2667
+ },
2668
+ {
2669
+ type: "directory",
2670
+ name: "tests",
2671
+ children: [
2672
+ {
2673
+ type: "directory",
2674
+ name: "Modules",
2675
+ children: [
2676
+ {
2677
+ type: "directory",
2678
+ name: "Ordering",
2679
+ children: [
2680
+ {
2681
+ type: "directory",
2682
+ name: "Unit",
2683
+ children: [{ type: "file", name: ".gitkeep", content: "" }]
2684
+ },
2685
+ {
2686
+ type: "directory",
2687
+ name: "Integration",
2688
+ children: [{ type: "file", name: ".gitkeep", content: "" }]
2689
+ }
2690
+ ]
2691
+ },
2692
+ {
2693
+ type: "directory",
2694
+ name: "Catalog",
2695
+ children: [
2696
+ {
2697
+ type: "directory",
2698
+ name: "Unit",
2699
+ children: [{ type: "file", name: ".gitkeep", content: "" }]
2700
+ }
2701
+ ]
2702
+ }
2703
+ ]
2704
+ },
2705
+ {
2706
+ type: "directory",
2707
+ name: "Shared",
2708
+ children: [{ type: "file", name: ".gitkeep", content: "" }]
2709
+ }
2710
+ ]
2711
+ }
2712
+ ];
2713
+ }
2714
+ /**
2715
+ * Override package.json for DDD architecture (uses main.ts instead of bootstrap.ts)
2716
+ */
2717
+ generatePackageJson(context) {
2718
+ const pkg = {
2719
+ name: context.nameKebabCase,
2720
+ version: "0.1.0",
2721
+ type: "module",
2722
+ scripts: {
2723
+ dev: "bun run --watch src/main.ts",
2724
+ build: "bun build ./src/main.ts --outdir ./dist --target bun",
2725
+ start: "bun run dist/main.js",
2726
+ test: "bun test",
2727
+ typecheck: "bun tsc --noEmit",
2728
+ check: "bun run typecheck && bun run test",
2729
+ "check:deps": "bun run scripts/check-dependencies.ts",
2730
+ validate: "bun run check && bun run check:deps",
2731
+ precommit: "bun run validate",
2732
+ "docker:build": `docker build -t ${context.nameKebabCase} .`,
2733
+ "docker:run": `docker run -it -p 3000:3000 ${context.nameKebabCase}`
2734
+ },
2735
+ dependencies: {
2736
+ "@gravito/core": "^1.0.0-beta.5",
2737
+ "@gravito/enterprise": "workspace:*"
2738
+ },
2739
+ devDependencies: {
2740
+ "bun-types": "latest",
2741
+ typescript: "^5.9.3"
2742
+ }
2743
+ };
2744
+ return JSON.stringify(pkg, null, 2);
3088
2745
  }
3089
2746
  generateArchitectureDoc(context) {
3090
2747
  return `# ${context.name} - DDD Architecture Guide
@@ -3313,123 +2970,13 @@ var EnterpriseMvcGenerator = class extends BaseGenerator {
3313
2970
  ];
3314
2971
  }
3315
2972
  // ─────────────────────────────────────────────────────────────
3316
- // Config Generators
2973
+ // Config Generators (using shared ConfigGenerator)
3317
2974
  // ─────────────────────────────────────────────────────────────
3318
2975
  generateAppConfig(context) {
3319
- return `/**
3320
- * Application Configuration
3321
- */
3322
- export default {
3323
- /**
3324
- * Application name
3325
- */
3326
- name: process.env.APP_NAME ?? '${context.name}',
3327
-
3328
- /**
3329
- * Application environment
3330
- */
3331
- env: process.env.APP_ENV ?? 'development',
3332
-
3333
- /**
3334
- * Application port
3335
- */
3336
- port: Number.parseInt(process.env.PORT ?? '3000', 10),
3337
-
3338
- /**
3339
- * View directory
3340
- */
3341
- VIEW_DIR: process.env.VIEW_DIR ?? 'src/views',
3342
-
3343
- /**
3344
- * Debug mode
3345
- */
3346
- debug: process.env.APP_DEBUG === 'true',
3347
-
3348
- /**
3349
- * Application URL
3350
- */
3351
- url: process.env.APP_URL ?? 'http://localhost:3000',
3352
-
3353
- /**
3354
- * Timezone
3355
- */
3356
- timezone: 'UTC',
3357
-
3358
- /**
3359
- * Locale
3360
- */
3361
- locale: 'en',
3362
-
3363
- /**
3364
- * Fallback locale
3365
- */
3366
- fallbackLocale: 'en',
3367
-
3368
- /**
3369
- * Encryption key
3370
- */
3371
- key: process.env.APP_KEY,
3372
-
3373
- /**
3374
- * Service providers to register
3375
- */
3376
- providers: [
3377
- // Framework providers
3378
- // 'RouteServiceProvider',
3379
-
3380
- // Application providers
3381
- // 'AppServiceProvider',
3382
- ],
3383
- }
3384
- `;
2976
+ return ConfigGenerator.generateDetailedAppConfig(context);
3385
2977
  }
3386
2978
  generateDatabaseConfig() {
3387
- return `/**
3388
- * Database Configuration
3389
- */
3390
- export default {
3391
- /**
3392
- * Default connection
3393
- */
3394
- default: process.env.DB_CONNECTION ?? 'sqlite',
3395
-
3396
- /**
3397
- * Database connections
3398
- */
3399
- connections: {
3400
- sqlite: {
3401
- driver: 'sqlite',
3402
- database: process.env.DB_DATABASE ?? 'database/database.sqlite',
3403
- },
3404
-
3405
- mysql: {
3406
- driver: 'mysql',
3407
- host: process.env.DB_HOST ?? 'localhost',
3408
- port: Number(process.env.DB_PORT ?? 3306),
3409
- database: process.env.DB_DATABASE ?? 'forge',
3410
- username: process.env.DB_USERNAME ?? 'forge',
3411
- password: process.env.DB_PASSWORD ?? '',
3412
- },
3413
-
3414
- postgres: {
3415
- driver: 'postgres',
3416
- host: process.env.DB_HOST ?? 'localhost',
3417
- port: Number(process.env.DB_PORT ?? 5432),
3418
- database: process.env.DB_DATABASE ?? 'forge',
3419
- username: process.env.DB_USERNAME ?? 'forge',
3420
- password: process.env.DB_PASSWORD ?? '',
3421
- },
3422
- },
3423
-
3424
- /**
3425
- * Migration settings
3426
- */
3427
- migrations: {
3428
- table: 'migrations',
3429
- path: 'database/migrations',
3430
- },
3431
- }
3432
- `;
2979
+ return ConfigGenerator.generateDetailedDatabaseConfig();
3433
2980
  }
3434
2981
  generateAuthConfig() {
3435
2982
  return `/**
@@ -3712,33 +3259,6 @@ export class AppServiceProvider extends ServiceProvider {
3712
3259
  console.log('${context.name} application booted!')
3713
3260
  }
3714
3261
  }
3715
- `;
3716
- }
3717
- generateRouteServiceProvider(_context) {
3718
- return `/**
3719
- * Route Service Provider
3720
- *
3721
- * Configures and registers application routes.
3722
- */
3723
-
3724
- import { ServiceProvider, type Container, type PlanetCore } from '@gravito/core'
3725
- import { registerRoutes } from '../routes'
3726
-
3727
- export class RouteServiceProvider extends ServiceProvider {
3728
- /**
3729
- * Register any application services.
3730
- */
3731
- register(_container: Container): void {
3732
- // Routes are registered in boot
3733
- }
3734
-
3735
- /**
3736
- * Bootstrap any application services.
3737
- */
3738
- boot(core: PlanetCore): void {
3739
- registerRoutes(core.router)
3740
- }
3741
- }
3742
3262
  `;
3743
3263
  }
3744
3264
  // ─────────────────────────────────────────────────────────────
@@ -4409,11 +3929,9 @@ export class ${name}ServiceProvider extends ServiceProvider {
4409
3929
  module: "dist/index.mjs",
4410
3930
  types: "dist/index.d.ts",
4411
3931
  scripts: {
4412
- build: "tsup src/index.ts --format cjs,esm --dts",
3932
+ build: "tsup src/index.ts --format esm --dts",
4413
3933
  test: "bun test",
4414
- typecheck: "tsc --noEmit",
4415
- check: "bun run typecheck && bun run test",
4416
- validate: "bun run check"
3934
+ typecheck: "bun tsc --noEmit"
4417
3935
  },
4418
3936
  dependencies: {
4419
3937
  "@gravito/core": depVersion,
@@ -4422,8 +3940,12 @@ export class ${name}ServiceProvider extends ServiceProvider {
4422
3940
  "@gravito/stasis": depVersion
4423
3941
  },
4424
3942
  devDependencies: {
4425
- tsup: "^8.0.0",
4426
- typescript: "^5.0.0"
3943
+ "bun-types": "latest",
3944
+ typescript: "^5.9.3",
3945
+ tsup: "^8.0.0"
3946
+ },
3947
+ peerDependencies: {
3948
+ "@gravito/core": ">=1.0.0"
4427
3949
  }
4428
3950
  };
4429
3951
  return JSON.stringify(pkg, null, 2);
@@ -4573,7 +4095,7 @@ var ProfileResolver = class _ProfileResolver {
4573
4095
  };
4574
4096
 
4575
4097
  // src/Scaffold.ts
4576
- import path3 from "path";
4098
+ import path6 from "path";
4577
4099
 
4578
4100
  // src/generators/ActionDomainGenerator.ts
4579
4101
  var ActionDomainGenerator = class extends BaseGenerator {
@@ -4780,6 +4302,9 @@ var ActionDomainGenerator = class extends BaseGenerator {
4780
4302
 
4781
4303
  import { Model, column } from '@gravito/atlas'
4782
4304
 
4305
+ /**
4306
+ * Represents a user in the system.
4307
+ */
4783
4308
  export class User extends Model {
4784
4309
  static table = 'users'
4785
4310
 
@@ -5064,7 +4589,7 @@ Created with \u2764\uFE0F using Gravito Framework
5064
4589
  build: "bun build ./src/bootstrap.ts --outdir ./dist --target bun",
5065
4590
  start: "bun run dist/bootstrap.js",
5066
4591
  test: "bun test",
5067
- typecheck: "tsc --noEmit",
4592
+ typecheck: "bun tsc --noEmit",
5068
4593
  check: "bun run typecheck && bun run test",
5069
4594
  "check:deps": "bun run scripts/check-dependencies.ts",
5070
4595
  validate: "bun run check && bun run check:deps",
@@ -5078,7 +4603,7 @@ Created with \u2764\uFE0F using Gravito Framework
5078
4603
  },
5079
4604
  devDependencies: {
5080
4605
  "bun-types": "latest",
5081
- typescript: "^5.0.0"
4606
+ typescript: "^5.9.3"
5082
4607
  }
5083
4608
  };
5084
4609
  return JSON.stringify(pkg, null, 2);
@@ -5086,6 +4611,7 @@ Created with \u2764\uFE0F using Gravito Framework
5086
4611
  };
5087
4612
 
5088
4613
  // src/generators/StandaloneEngineGenerator.ts
4614
+ import path5 from "path";
5089
4615
  var StandaloneEngineGenerator = class extends BaseGenerator {
5090
4616
  get architectureType() {
5091
4617
  return "standalone-engine";
@@ -5117,9 +4643,16 @@ var StandaloneEngineGenerator = class extends BaseGenerator {
5117
4643
  ];
5118
4644
  }
5119
4645
  async generateCommonFiles(context) {
4646
+ const commonDir = path5.resolve(this.config.templatesDir, "common");
4647
+ const extendedContext = { ...context };
5120
4648
  await this.writeFile(context.targetDir, "package.json", this.generatePackageJson(context));
5121
- await this.writeFile(context.targetDir, "tsconfig.json", this.generateTsConfig());
5122
- await this.writeFile(context.targetDir, ".gitignore", this.generateGitignore());
4649
+ await this.generateFileFromTemplate(
4650
+ commonDir,
4651
+ "tsconfig.json.hbs",
4652
+ "tsconfig.json",
4653
+ extendedContext
4654
+ );
4655
+ await this.generateFileFromTemplate(commonDir, "gitignore.hbs", ".gitignore", extendedContext);
5123
4656
  }
5124
4657
  generatePackageJson(context) {
5125
4658
  const pkg = {
@@ -5174,23 +4707,17 @@ A high-performance web application powered by Gravito Engine.
5174
4707
 
5175
4708
  ### Install Dependencies
5176
4709
 
5177
- \`\`\`bash
5178
- bun install
5179
- \`\`\`
5180
-
4710
+ bun install
4711
+
5181
4712
  ### Run Development Server
5182
4713
 
5183
- \`\`\`bash
5184
- bun run dev
5185
- \`\`\`
5186
-
4714
+ bun run dev
4715
+
5187
4716
  ### Production Build
5188
4717
 
5189
- \`\`\`bash
5190
- bun run build
4718
+ bun run build
5191
4719
  bun start
5192
- \`\`\`
5193
- `;
4720
+ `;
5194
4721
  }
5195
4722
  };
5196
4723
 
@@ -5199,11 +4726,14 @@ var Scaffold = class {
5199
4726
  templatesDir;
5200
4727
  verbose;
5201
4728
  constructor(options = {}) {
5202
- this.templatesDir = options.templatesDir ?? path3.resolve(__dirname, "../templates");
4729
+ this.templatesDir = options.templatesDir ?? path6.resolve(__dirname, "../templates");
5203
4730
  this.verbose = options.verbose ?? false;
5204
4731
  }
5205
4732
  /**
5206
- * Get all available architecture types.
4733
+ * Returns a list of all architectural patterns supported by the engine,
4734
+ * along with human-readable names and descriptions.
4735
+ *
4736
+ * @returns {Array<{type: ArchitectureType, name: string, description: string}>}
5207
4737
  */
5208
4738
  getArchitectureTypes() {
5209
4739
  return [
@@ -5240,11 +4770,16 @@ var Scaffold = class {
5240
4770
  ];
5241
4771
  }
5242
4772
  /**
5243
- * Create a new project scaffold.
4773
+ * Orchestrates the complete project generation lifecycle.
4774
+ * This includes directory creation, file layout, profile resolution,
4775
+ * dependency mapping, and optional post-install hooks.
4776
+ *
4777
+ * @param {ScaffoldOptions} options - Detailed configuration for the new project.
4778
+ * @returns {Promise<ScaffoldResult>}
5244
4779
  */
5245
4780
  async create(options) {
5246
4781
  const generator = this.createGenerator(options.architecture);
5247
- const fs3 = await import("fs/promises");
4782
+ const fs5 = await import("fs/promises");
5248
4783
  const profileResolver = new ProfileResolver();
5249
4784
  const profileConfig = profileResolver.resolve(options.profile, options.features);
5250
4785
  const context = BaseGenerator.createContext(
@@ -5271,8 +4806,8 @@ var Scaffold = class {
5271
4806
  // Default template for now, should come from options if applicable
5272
4807
  "1.0.0"
5273
4808
  );
5274
- const lockPath = path3.resolve(options.targetDir, "gravito.lock.json");
5275
- await fs3.writeFile(lockPath, lockContent, "utf-8");
4809
+ const lockPath = path6.resolve(options.targetDir, "gravito.lock.json");
4810
+ await fs5.writeFile(lockPath, lockContent, "utf-8");
5276
4811
  filesCreated.push(lockPath);
5277
4812
  return {
5278
4813
  success: true,