@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.cjs CHANGED
@@ -144,12 +144,52 @@ ${overlay}`;
144
144
  };
145
145
 
146
146
  // src/generators/BaseGenerator.ts
147
- var import_promises2 = __toESM(require("fs/promises"), 1);
148
- var import_node_path2 = __toESM(require("path"), 1);
147
+ var import_promises4 = __toESM(require("fs/promises"), 1);
148
+ var import_node_path4 = __toESM(require("path"), 1);
149
149
 
150
- // src/generators/StubGenerator.ts
150
+ // src/utils/FileUtilities.ts
151
151
  var import_promises = __toESM(require("fs/promises"), 1);
152
152
  var import_node_path = __toESM(require("path"), 1);
153
+ var FileUtilities = class _FileUtilities {
154
+ static async walk(dir) {
155
+ const files = await import_promises.default.readdir(dir);
156
+ const paths = [];
157
+ for (const file of files) {
158
+ const filePath = import_node_path.default.join(dir, file);
159
+ const stat = await import_promises.default.stat(filePath);
160
+ if (stat.isDirectory()) {
161
+ paths.push(...await _FileUtilities.walk(filePath));
162
+ } else {
163
+ paths.push(filePath);
164
+ }
165
+ }
166
+ return paths;
167
+ }
168
+ static async writeFile(basePath, relativePath, content, fileMerger, log) {
169
+ const fullPath = import_node_path.default.resolve(basePath, relativePath);
170
+ await import_promises.default.mkdir(import_node_path.default.dirname(fullPath), { recursive: true });
171
+ let finalContent = content;
172
+ try {
173
+ const existingContent = await import_promises.default.readFile(fullPath, "utf-8");
174
+ finalContent = fileMerger.merge(relativePath, existingContent, content);
175
+ if (finalContent !== content) {
176
+ log?.(`\u{1F504} Merged file: ${relativePath}`);
177
+ }
178
+ } catch {
179
+ }
180
+ await import_promises.default.writeFile(fullPath, finalContent, "utf-8");
181
+ log?.(`\u{1F4C4} Created file: ${relativePath}`);
182
+ return fullPath;
183
+ }
184
+ };
185
+
186
+ // src/utils/TemplateManager.ts
187
+ var import_promises3 = __toESM(require("fs/promises"), 1);
188
+ var import_node_path3 = __toESM(require("path"), 1);
189
+
190
+ // src/generators/StubGenerator.ts
191
+ var import_promises2 = __toESM(require("fs/promises"), 1);
192
+ var import_node_path2 = __toESM(require("path"), 1);
153
193
  var import_handlebars = __toESM(require("handlebars"), 1);
154
194
  var StubGenerator = class {
155
195
  config;
@@ -238,16 +278,16 @@ var StubGenerator = class {
238
278
  * @returns Path to the generated file
239
279
  */
240
280
  async generate(stubName, outputPath, variables = {}) {
241
- const stubPath = import_node_path.default.resolve(this.config.stubsDir, stubName);
242
- const template = await import_promises.default.readFile(stubPath, "utf-8");
281
+ const stubPath = import_node_path2.default.resolve(this.config.stubsDir, stubName);
282
+ const template = await import_promises2.default.readFile(stubPath, "utf-8");
243
283
  const compiled = this.handlebars.compile(template);
244
284
  const content = compiled({
245
285
  ...this.config.defaultVariables,
246
286
  ...variables
247
287
  });
248
- const fullOutputPath = import_node_path.default.resolve(this.config.outputDir, outputPath);
249
- await import_promises.default.mkdir(import_node_path.default.dirname(fullOutputPath), { recursive: true });
250
- await import_promises.default.writeFile(fullOutputPath, content, "utf-8");
288
+ const fullOutputPath = import_node_path2.default.resolve(this.config.outputDir, outputPath);
289
+ await import_promises2.default.mkdir(import_node_path2.default.dirname(fullOutputPath), { recursive: true });
290
+ await import_promises2.default.writeFile(fullOutputPath, content, "utf-8");
251
291
  return fullOutputPath;
252
292
  }
253
293
  /**
@@ -299,41 +339,57 @@ var StubGenerator = class {
299
339
  }
300
340
  };
301
341
 
302
- // src/generators/BaseGenerator.ts
303
- async function walk(dir) {
304
- const files = await import_promises2.default.readdir(dir);
305
- const paths = [];
306
- for (const file of files) {
307
- const filePath = import_node_path2.default.join(dir, file);
308
- const stat = await import_promises2.default.stat(filePath);
309
- if (stat.isDirectory()) {
310
- paths.push(...await walk(filePath));
311
- } else {
312
- paths.push(filePath);
342
+ // src/utils/TemplateManager.ts
343
+ var TemplateManager = class {
344
+ stubGenerator;
345
+ constructor(templatesDir) {
346
+ this.stubGenerator = new StubGenerator({
347
+ stubsDir: templatesDir,
348
+ outputDir: ""
349
+ });
350
+ }
351
+ render(template, context) {
352
+ return this.stubGenerator.render(template, context);
353
+ }
354
+ async applyOverlay(sourceDir, targetDir, context, fileMerger, log) {
355
+ const createdFiles = [];
356
+ try {
357
+ await import_promises3.default.access(sourceDir);
358
+ } catch {
359
+ return [];
360
+ }
361
+ const files = await FileUtilities.walk(sourceDir);
362
+ for (const filePath of files) {
363
+ const relativePath = import_node_path3.default.relative(sourceDir, filePath);
364
+ let content = await import_promises3.default.readFile(filePath, "utf-8");
365
+ try {
366
+ content = this.render(content, context);
367
+ } catch {
368
+ }
369
+ const fullPath = await FileUtilities.writeFile(
370
+ targetDir,
371
+ relativePath,
372
+ content,
373
+ fileMerger,
374
+ log
375
+ );
376
+ createdFiles.push(fullPath);
313
377
  }
378
+ return createdFiles;
314
379
  }
315
- return paths;
316
- }
380
+ };
381
+
382
+ // src/generators/BaseGenerator.ts
317
383
  var BaseGenerator = class {
318
384
  config;
319
- stubGenerator;
385
+ templateManager;
320
386
  fileMerger;
321
387
  filesCreated = [];
322
388
  constructor(config) {
323
389
  this.config = config;
324
- this.stubGenerator = new StubGenerator({
325
- stubsDir: config.templatesDir,
326
- outputDir: ""
327
- // Set per-generation
328
- });
390
+ this.templateManager = new TemplateManager(config.templatesDir);
329
391
  this.fileMerger = new FileMerger();
330
392
  }
331
- /**
332
- * Generate the project scaffold.
333
- *
334
- * @param context - Generator context
335
- * @returns Array of created file paths
336
- */
337
393
  async generate(context) {
338
394
  this.filesCreated = [];
339
395
  const structure = this.getDirectoryStructure(context);
@@ -343,50 +399,73 @@ var BaseGenerator = class {
343
399
  await this.applyFeatureOverlays(context);
344
400
  return this.filesCreated;
345
401
  }
346
- /**
347
- * Create directory structure recursively.
348
- */
349
402
  async createStructure(basePath, nodes, context) {
350
403
  for (const node of nodes) {
351
- const fullPath = import_node_path2.default.resolve(basePath, node.name);
404
+ const fullPath = import_node_path4.default.resolve(basePath, node.name);
352
405
  if (node.type === "directory") {
353
- await import_promises2.default.mkdir(fullPath, { recursive: true });
406
+ await import_promises4.default.mkdir(fullPath, { recursive: true });
354
407
  this.log(`\u{1F4C1} Created directory: ${node.name}`);
355
408
  if (node.children) {
356
409
  await this.createStructure(fullPath, node.children, context);
357
410
  }
358
411
  } else {
359
- await import_promises2.default.mkdir(import_node_path2.default.dirname(fullPath), { recursive: true });
412
+ await import_promises4.default.mkdir(import_node_path4.default.dirname(fullPath), { recursive: true });
413
+ let content = "";
360
414
  if (node.template) {
361
- const templatePath = import_node_path2.default.resolve(this.config.templatesDir, node.template);
362
415
  try {
363
- const template = await import_promises2.default.readFile(templatePath, "utf-8");
364
- const content = this.stubGenerator.render(template, context);
365
- await import_promises2.default.writeFile(fullPath, content, "utf-8");
416
+ const templatePath = import_node_path4.default.resolve(this.config.templatesDir, node.template);
417
+ const template = await import_promises4.default.readFile(templatePath, "utf-8");
418
+ content = this.templateManager.render(template, context);
366
419
  } catch {
367
- await import_promises2.default.writeFile(fullPath, node.content ?? "", "utf-8");
420
+ content = node.content ?? "";
368
421
  }
369
- } else if (node.content) {
370
- await import_promises2.default.writeFile(fullPath, node.content, "utf-8");
371
422
  } else {
372
- await import_promises2.default.writeFile(fullPath, "", "utf-8");
423
+ content = node.content ?? "";
373
424
  }
374
- this.filesCreated.push(fullPath);
375
- this.log(`\u{1F4C4} Created file: ${node.name}`);
425
+ const relativePath = import_node_path4.default.relative(context.targetDir, fullPath);
426
+ const writtenPath = await FileUtilities.writeFile(
427
+ context.targetDir,
428
+ relativePath,
429
+ content,
430
+ this.fileMerger,
431
+ (msg) => this.log(msg)
432
+ );
433
+ this.filesCreated.push(writtenPath);
376
434
  }
377
435
  }
378
436
  }
379
- /**
380
- * Generate common files (package.json, .env, etc.)
381
- */
382
437
  async generateCommonFiles(context) {
438
+ const commonDir = import_node_path4.default.resolve(this.config.templatesDir, "common");
439
+ const extendedContext = {
440
+ ...context,
441
+ entrypoint: context.architecture === "ddd" ? "dist/main.js" : "dist/bootstrap.js",
442
+ dbConnection: context.profile === "core" ? "sqlite" : "postgres"
443
+ };
444
+ await this.generateFileFromTemplate(
445
+ commonDir,
446
+ "env.example.hbs",
447
+ ".env.example",
448
+ extendedContext
449
+ );
450
+ await this.generateFileFromTemplate(commonDir, "env.example.hbs", ".env", extendedContext);
451
+ await this.generateFileFromTemplate(commonDir, "gitignore.hbs", ".gitignore", extendedContext);
452
+ await this.generateFileFromTemplate(
453
+ commonDir,
454
+ "tsconfig.json.hbs",
455
+ "tsconfig.json",
456
+ extendedContext
457
+ );
458
+ await this.generateFileFromTemplate(commonDir, "Dockerfile.hbs", "Dockerfile", extendedContext);
383
459
  await this.writeFile(context.targetDir, "package.json", this.generatePackageJson(context));
384
- await this.writeFile(context.targetDir, ".env.example", this.generateEnvExample(context));
385
- await this.writeFile(context.targetDir, ".env", this.generateEnvExample(context));
386
- await this.writeFile(context.targetDir, ".gitignore", this.generateGitignore());
387
- await this.writeFile(context.targetDir, "tsconfig.json", this.generateTsConfig());
388
- await this.writeFile(context.targetDir, "Dockerfile", this.generateDockerfile(context));
389
- await this.writeFile(context.targetDir, ".dockerignore", this.generateDockerIgnore());
460
+ await this.writeFile(
461
+ context.targetDir,
462
+ ".dockerignore",
463
+ `node_modules
464
+ dist
465
+ .git
466
+ .env
467
+ `
468
+ );
390
469
  await this.writeFile(
391
470
  context.targetDir,
392
471
  "ARCHITECTURE.md",
@@ -395,104 +474,74 @@ var BaseGenerator = class {
395
474
  await this.generateCheckScripts(context);
396
475
  await this.generateSkills(context);
397
476
  }
398
- /**
399
- * Copy AI Skills to the project
400
- */
401
- async generateSkills(context) {
402
- const skillsDir = import_node_path2.default.resolve(this.config.templatesDir, "skills");
403
- const targetSkillsDir = import_node_path2.default.join(".skills");
477
+ async generateFileFromTemplate(tplDir, tplName, targetName, context) {
404
478
  try {
405
- await import_promises2.default.access(skillsDir);
406
- } catch {
407
- return;
408
- }
409
- const files = await walk(skillsDir);
410
- for (const filePath of files) {
411
- const relativePath = import_node_path2.default.relative(skillsDir, filePath);
412
- const targetPath = import_node_path2.default.join(targetSkillsDir, relativePath);
413
- let content = await import_promises2.default.readFile(filePath, "utf-8");
414
- try {
415
- content = this.stubGenerator.render(content, context);
416
- } catch {
417
- }
418
- await this.writeFile(context.targetDir, targetPath, content);
479
+ const template = await import_promises4.default.readFile(import_node_path4.default.join(tplDir, tplName), "utf-8");
480
+ const content = this.templateManager.render(template, context);
481
+ await this.writeFile(context.targetDir, targetName, content);
482
+ } catch (e) {
483
+ this.log(`\u26A0\uFE0F Failed to generate ${targetName}: ${e}`);
419
484
  }
420
485
  }
421
- /**
422
- * Apply profile-specific overlays
423
- */
486
+ async generateSkills(context) {
487
+ const skillsDir = import_node_path4.default.resolve(this.config.templatesDir, "skills");
488
+ const created = await this.templateManager.applyOverlay(
489
+ skillsDir,
490
+ import_node_path4.default.join(context.targetDir, ".skills"),
491
+ context,
492
+ this.fileMerger,
493
+ (msg) => this.log(msg)
494
+ );
495
+ this.filesCreated.push(...created);
496
+ }
424
497
  async applyOverlays(context) {
425
498
  const profile = context.profile;
426
- if (!profile) return;
427
- const overlayDir = import_node_path2.default.resolve(this.config.templatesDir, "overlays", profile);
428
- await this.copyOverlayDirectory(overlayDir, context);
499
+ if (profile) {
500
+ const overlayDir = import_node_path4.default.resolve(this.config.templatesDir, "overlays", profile);
501
+ await this.copyOverlayDirectory(overlayDir, context);
502
+ }
429
503
  }
430
- /**
431
- * Apply feature-specific overlays
432
- */
433
504
  async applyFeatureOverlays(context) {
434
505
  const features = context.features || [];
435
506
  for (const feature of features) {
436
- const overlayDir = import_node_path2.default.resolve(this.config.templatesDir, "features", feature);
507
+ const overlayDir = import_node_path4.default.resolve(this.config.templatesDir, "features", feature);
437
508
  await this.copyOverlayDirectory(overlayDir, context);
438
509
  }
439
510
  }
440
- /**
441
- * Helper to copy/merge an overlay directory into the target
442
- */
443
511
  async copyOverlayDirectory(sourceDir, context) {
444
- try {
445
- await import_promises2.default.access(sourceDir);
446
- } catch {
447
- return;
448
- }
449
- const files = await walk(sourceDir);
450
- for (const filePath of files) {
451
- const relativePath = import_node_path2.default.relative(sourceDir, filePath);
452
- let content = await import_promises2.default.readFile(filePath, "utf-8");
453
- try {
454
- content = this.stubGenerator.render(content, context);
455
- } catch {
456
- }
457
- await this.writeFile(context.targetDir, relativePath, content);
458
- }
512
+ const created = await this.templateManager.applyOverlay(
513
+ sourceDir,
514
+ context.targetDir,
515
+ context,
516
+ this.fileMerger,
517
+ (msg) => this.log(msg)
518
+ );
519
+ this.filesCreated.push(...created);
459
520
  }
460
- /**
461
- * Write a file and track it.
462
- */
463
521
  async writeFile(basePath, relativePath, content) {
464
- const fullPath = import_node_path2.default.resolve(basePath, relativePath);
465
- await import_promises2.default.mkdir(import_node_path2.default.dirname(fullPath), { recursive: true });
466
- let finalContent = content;
467
- try {
468
- const existingContent = await import_promises2.default.readFile(fullPath, "utf-8");
469
- finalContent = this.fileMerger.merge(relativePath, existingContent, content);
470
- if (finalContent !== content) {
471
- this.log(`\u{1F504} Merged file: ${relativePath}`);
472
- }
473
- } catch {
474
- }
475
- await import_promises2.default.writeFile(fullPath, finalContent, "utf-8");
476
- this.filesCreated.push(fullPath);
477
- this.log(`\u{1F4C4} Created file: ${relativePath}`);
522
+ const writtenPath = await FileUtilities.writeFile(
523
+ basePath,
524
+ relativePath,
525
+ content,
526
+ this.fileMerger,
527
+ (msg) => this.log(msg)
528
+ );
529
+ this.filesCreated.push(writtenPath);
478
530
  }
479
- /**
480
- * Generate package.json content.
481
- */
482
531
  generatePackageJson(context) {
483
532
  const profile = context.profile || "core";
484
- const baseDependencies = {
533
+ const deps = {
485
534
  "@gravito/core": "^1.0.0-beta.5",
486
535
  "@gravito/atlas": "^1.0.0-beta.5",
487
536
  "@gravito/plasma": "^1.0.0-beta.5",
488
537
  "@gravito/stream": "^1.0.0-beta.5"
489
538
  };
490
539
  if (profile === "enterprise" || profile === "scale") {
491
- baseDependencies["@gravito/quasar"] = "^1.0.0-beta.5";
492
- baseDependencies["@gravito/horizon"] = "^1.0.0-beta.5";
540
+ deps["@gravito/quasar"] = "^1.0.0-beta.5";
541
+ deps["@gravito/horizon"] = "^1.0.0-beta.5";
493
542
  }
494
543
  if (context.withSpectrum) {
495
- baseDependencies["@gravito/spectrum"] = "^1.0.0-beta.1";
544
+ deps["@gravito/spectrum"] = "^1.0.0-beta.5";
496
545
  }
497
546
  const pkg = {
498
547
  name: context.nameKebabCase,
@@ -503,717 +552,426 @@ var BaseGenerator = class {
503
552
  build: "bun build ./src/bootstrap.ts --outdir ./dist --target bun",
504
553
  start: "bun run dist/bootstrap.js",
505
554
  test: "bun test",
506
- typecheck: "tsc --noEmit",
507
- check: "bun run typecheck && bun run test",
508
- "check:deps": "bun run scripts/check-dependencies.ts",
509
- validate: "bun run check && bun run check:deps",
510
- precommit: "bun run validate",
511
- "docker:build": `docker build -t ${context.nameKebabCase} .`,
512
- "docker:run": `docker run -it -p 3000:3000 ${context.nameKebabCase}`
555
+ typecheck: "bun tsc --noEmit",
556
+ validate: "bun run typecheck && bun run test"
513
557
  },
514
- dependencies: baseDependencies,
515
- devDependencies: {
516
- "bun-types": "latest",
517
- typescript: "^5.0.0"
518
- }
558
+ dependencies: deps,
559
+ devDependencies: { "bun-types": "latest", typescript: "^5.9.3" }
519
560
  };
520
561
  return JSON.stringify(pkg, null, 2);
521
562
  }
563
+ async generateCheckScripts(context) {
564
+ const scriptsDir = import_node_path4.default.resolve(context.targetDir, "scripts");
565
+ await import_promises4.default.mkdir(scriptsDir, { recursive: true });
566
+ const templatesDir = import_node_path4.default.resolve(this.config.templatesDir, "scripts");
567
+ await this.generateFileFromTemplate(
568
+ templatesDir,
569
+ "check-dependencies.ts.hbs",
570
+ "scripts/check-dependencies.ts",
571
+ context
572
+ );
573
+ await this.generateFileFromTemplate(templatesDir, "check.sh.hbs", "scripts/check.sh", context);
574
+ await this.generateFileFromTemplate(
575
+ templatesDir,
576
+ "pre-commit.sh.hbs",
577
+ "scripts/pre-commit.sh",
578
+ context
579
+ );
580
+ await this.writeFile(
581
+ context.targetDir,
582
+ "CHECK_SYSTEM.md",
583
+ "# Project Check System\n\nRun `bun run validate` to check everything.\n"
584
+ );
585
+ }
586
+ log(message) {
587
+ if (this.config.verbose) {
588
+ console.log(message);
589
+ }
590
+ }
591
+ static createContext(name, targetDir, architecture, packageManager = "bun", extra = {}) {
592
+ const toPascalCase = (str) => str.replace(/([a-z])([A-Z])/g, "$1 $2").split(/[-_ ]+/).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
593
+ const toCamelCase = (str) => {
594
+ const pascal = toPascalCase(str);
595
+ return pascal.charAt(0).toLowerCase() + pascal.slice(1);
596
+ };
597
+ const toSnakeCase = (str) => str.replace(/([a-z])([A-Z])/g, "$1_$2").split(/[-_ ]+/).map((word) => word.toLowerCase()).join("_");
598
+ const toKebabCase = (str) => str.replace(/([a-z])([A-Z])/g, "$1-$2").split(/[-_ ]+/).map((word) => word.toLowerCase()).join("-");
599
+ return {
600
+ name,
601
+ namePascalCase: toPascalCase(name),
602
+ nameCamelCase: toCamelCase(name),
603
+ nameSnakeCase: toSnakeCase(name),
604
+ nameKebabCase: toKebabCase(name),
605
+ targetDir,
606
+ architecture,
607
+ packageManager,
608
+ year: (/* @__PURE__ */ new Date()).getFullYear().toString(),
609
+ date: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
610
+ ...extra
611
+ };
612
+ }
613
+ };
614
+
615
+ // src/utils/ConfigGenerator.ts
616
+ var ConfigGenerator = class {
522
617
  /**
523
- * Generate Dockerfile content.
618
+ * Generate app configuration (simple version for Clean Architecture)
524
619
  */
525
- generateDockerfile(context) {
526
- const entrypoint = context.architecture === "ddd" ? "dist/main.js" : "dist/bootstrap.js";
527
- return `FROM oven/bun:1.0 AS base
528
- WORKDIR /usr/src/app
529
-
530
- # Install dependencies
531
- FROM base AS install
532
- RUN mkdir -p /temp/dev
533
- COPY package.json bun.lockb /temp/dev/
534
- RUN cd /temp/dev && bun install --frozen-lockfile
535
-
536
- # Build application
537
- FROM base AS build
538
- COPY --from=install /temp/dev/node_modules node_modules
539
- COPY . .
540
- ENV NODE_ENV=production
541
- RUN bun run build
542
-
543
- # Final production image
544
- FROM base AS release
545
- COPY --from=build /usr/src/app/${entrypoint} index.js
546
- COPY --from=build /usr/src/app/package.json .
547
-
548
- # Create a non-root user for security
549
- USER bun
550
- EXPOSE 3000/tcp
551
- ENTRYPOINT [ "bun", "run", "index.js" ]
620
+ static generateSimpleAppConfig(context) {
621
+ return `export default {
622
+ name: process.env.APP_NAME ?? '${context.name}',
623
+ env: process.env.APP_ENV ?? 'development',
624
+ debug: process.env.APP_DEBUG === 'true',
625
+ url: process.env.APP_URL ?? 'http://localhost:3000',
626
+ key: process.env.APP_KEY,
627
+ }
552
628
  `;
553
629
  }
554
630
  /**
555
- * Generate .dockerignore content.
631
+ * Generate app configuration (detailed version for Enterprise MVC)
556
632
  */
557
- generateDockerIgnore() {
558
- return `node_modules
559
- dist
560
- .git
561
- .env
562
- *.log
563
- .vscode
564
- .idea
565
- tests
566
- `;
567
- }
633
+ static generateDetailedAppConfig(context) {
634
+ return `/**
635
+ * Application Configuration
636
+ */
637
+ export default {
568
638
  /**
569
- * Generate .env.example content.
639
+ * Application name
570
640
  */
571
- generateEnvExample(context) {
572
- const profile = context.profile || "core";
573
- let envContent = `# ============================================================================
574
- # Application Configuration
575
- # ============================================================================
576
-
577
- APP_NAME=${context.name}
578
- APP_ENV=development
579
- APP_DEBUG=true
580
- APP_URL=http://localhost:3000
581
- APP_KEY=
582
-
583
- # ============================================================================
584
- # Database Configuration
585
- # ============================================================================
586
-
587
- # Database Connection (sqlite, postgres, mysql)
588
- DB_CONNECTION=${profile === "core" ? "sqlite" : "postgres"}
589
-
590
- # SQLite Configuration (when DB_CONNECTION=sqlite)
591
- DB_DATABASE=database/database.sqlite
592
-
593
- # PostgreSQL Configuration (when DB_CONNECTION=postgres)
594
- ${profile !== "core" ? `DB_HOST=127.0.0.1
595
- DB_PORT=5432
596
- DB_DATABASE=${context.name}
597
- DB_USERNAME=postgres
598
- DB_PASSWORD=
599
- DB_SSLMODE=prefer` : `# DB_HOST=127.0.0.1
600
- # DB_PORT=5432
601
- # DB_DATABASE=${context.name}
602
- # DB_USERNAME=postgres
603
- # DB_PASSWORD=
604
- # DB_SSLMODE=prefer`}
605
-
606
- # MySQL Configuration (when DB_CONNECTION=mysql)
607
- # DB_HOST=127.0.0.1
608
- # DB_PORT=3306
609
- # DB_DATABASE=${context.name}
610
- # DB_USERNAME=root
611
- # DB_PASSWORD=
612
-
613
- # ============================================================================
614
- # Redis Configuration (@gravito/plasma)
615
- # ============================================================================
616
-
617
- # Default Redis Connection
618
- REDIS_CONNECTION=default
619
- REDIS_HOST=127.0.0.1
620
- REDIS_PORT=6379
621
- REDIS_PASSWORD=
622
- REDIS_DB=0
623
-
624
- # Redis Connection Options
625
- REDIS_CONNECT_TIMEOUT=10000
626
- REDIS_COMMAND_TIMEOUT=5000
627
- REDIS_KEY_PREFIX=
628
- REDIS_MAX_RETRIES=3
629
- REDIS_RETRY_DELAY=1000
630
-
631
- # Cache-specific Redis Connection (optional, falls back to default)
632
- # REDIS_CACHE_HOST=127.0.0.1
633
- # REDIS_CACHE_PORT=6379
634
- # REDIS_CACHE_PASSWORD=
635
- REDIS_CACHE_DB=1
636
-
637
- # Queue-specific Redis Connection (optional, falls back to default)
638
- # REDIS_QUEUE_HOST=127.0.0.1
639
- # REDIS_QUEUE_PORT=6379
640
- # REDIS_QUEUE_PASSWORD=
641
- REDIS_QUEUE_DB=2
642
-
643
- # ============================================================================
644
- # Cache Configuration (@gravito/stasis)
645
- # ============================================================================
646
-
647
- # Cache Driver (memory, file, redis)
648
- CACHE_DRIVER=${profile === "core" ? "memory" : "redis"}
649
-
650
- # File Cache Path (when CACHE_DRIVER=file)
651
- CACHE_PATH=storage/framework/cache
652
-
653
- # Redis Cache Configuration (when CACHE_DRIVER=redis)
654
- REDIS_CACHE_CONNECTION=cache
655
- REDIS_CACHE_PREFIX=cache:
656
-
657
- # ============================================================================
658
- # Queue Configuration (@gravito/stream)
659
- # ============================================================================
660
-
661
- # Queue Connection (sync, memory, database, redis, kafka, sqs, rabbitmq)
662
- QUEUE_CONNECTION=${profile === "core" ? "sync" : "redis"}
663
-
664
- # Database Queue Configuration (when QUEUE_CONNECTION=database)
665
- QUEUE_TABLE=jobs
666
-
667
- # Redis Queue Configuration (when QUEUE_CONNECTION=redis)
668
- REDIS_PREFIX=queue:
641
+ name: process.env.APP_NAME ?? '${context.name}',
669
642
 
670
- `;
671
- if (profile === "enterprise" || profile === "scale") {
672
- envContent += `# Kafka Queue Configuration (when QUEUE_CONNECTION=kafka)
673
- # KAFKA_BROKERS=localhost:9092
674
- # KAFKA_CONSUMER_GROUP_ID=gravito-workers
675
- # KAFKA_CLIENT_ID=${context.name}
676
-
677
- # AWS SQS Queue Configuration (when QUEUE_CONNECTION=sqs)
678
- # AWS_REGION=us-east-1
679
- # SQS_QUEUE_URL_PREFIX=
680
- # SQS_VISIBILITY_TIMEOUT=30
681
- # SQS_WAIT_TIME_SECONDS=20
682
-
683
- # RabbitMQ Queue Configuration (when QUEUE_CONNECTION=rabbitmq)
684
- # RABBITMQ_URL=amqp://localhost
685
- # RABBITMQ_EXCHANGE=gravito.events
686
- # RABBITMQ_EXCHANGE_TYPE=fanout
643
+ /**
644
+ * Application environment
645
+ */
646
+ env: process.env.APP_ENV ?? 'development',
687
647
 
688
- `;
689
- }
690
- envContent += `# ============================================================================
691
- # Logging Configuration
692
- # ============================================================================
648
+ /**
649
+ * Application port
650
+ */
651
+ port: Number.parseInt(process.env.PORT ?? '3000', 10),
693
652
 
694
- LOG_LEVEL=debug
695
- `;
696
- return envContent;
697
- }
698
653
  /**
699
- * Generate .gitignore content.
654
+ * View directory
700
655
  */
701
- generateGitignore() {
702
- return `# Dependencies
703
- node_modules/
656
+ VIEW_DIR: process.env.VIEW_DIR ?? 'src/views',
704
657
 
705
- # Build output
706
- dist/
658
+ /**
659
+ * Debug mode
660
+ */
661
+ debug: process.env.APP_DEBUG === 'true',
707
662
 
708
- # Environment
709
- .env
710
- .env.local
711
- .env.*.local
663
+ /**
664
+ * Application URL
665
+ */
666
+ url: process.env.APP_URL ?? 'http://localhost:3000',
712
667
 
713
- # IDE
714
- .idea/
715
- .vscode/
716
- *.swp
717
- *.swo
668
+ /**
669
+ * Timezone
670
+ */
671
+ timezone: 'UTC',
718
672
 
719
- # System
720
- .DS_Store
721
- Thumbs.db
673
+ /**
674
+ * Locale
675
+ */
676
+ locale: 'en',
722
677
 
723
- # Logs
724
- *.log
725
- logs/
678
+ /**
679
+ * Fallback locale
680
+ */
681
+ fallbackLocale: 'en',
726
682
 
727
- # Database
728
- *.sqlite
729
- *.sqlite-journal
683
+ /**
684
+ * Encryption key
685
+ */
686
+ key: process.env.APP_KEY,
730
687
 
731
- # Coverage
732
- coverage/
733
- `;
734
- }
735
688
  /**
736
- * Generate tsconfig.json content.
689
+ * Service providers to register
737
690
  */
738
- generateTsConfig() {
739
- const config = {
740
- compilerOptions: {
741
- target: "ESNext",
742
- module: "ESNext",
743
- moduleResolution: "bundler",
744
- esModuleInterop: true,
745
- strict: true,
746
- skipLibCheck: true,
747
- declaration: true,
748
- experimentalDecorators: true,
749
- emitDecoratorMetadata: true,
750
- types: ["bun-types"],
751
- outDir: "./dist",
752
- rootDir: "./src",
753
- baseUrl: ".",
754
- paths: {
755
- "@/*": ["./src/*"]
756
- }
757
- },
758
- include: ["src/**/*"],
759
- exclude: ["node_modules", "dist"]
760
- };
761
- return JSON.stringify(config, null, 2);
691
+ providers: [
692
+ // Framework providers
693
+ // 'RouteServiceProvider',
694
+
695
+ // Application providers
696
+ // 'AppServiceProvider',
697
+ ],
698
+ }
699
+ `;
762
700
  }
763
701
  /**
764
- * Generate check scripts for project validation.
702
+ * Generate database configuration (simple version)
765
703
  */
766
- async generateCheckScripts(context) {
767
- const scriptsDir = import_node_path2.default.resolve(context.targetDir, "scripts");
768
- await import_promises2.default.mkdir(scriptsDir, { recursive: true });
769
- await this.writeFile(
770
- scriptsDir,
771
- "check-dependencies.ts",
772
- this.generateCheckDependenciesScript()
773
- );
774
- await this.writeFile(scriptsDir, "check.sh", this.generateCheckShellScript());
775
- await this.writeFile(scriptsDir, "pre-commit.sh", this.generatePreCommitScript());
776
- await this.writeFile(context.targetDir, "CHECK_SYSTEM.md", this.generateCheckSystemDoc(context));
704
+ static generateSimpleDatabaseConfig() {
705
+ return `export default {
706
+ default: process.env.DB_CONNECTION ?? 'sqlite',
707
+ connections: {
708
+ sqlite: {
709
+ driver: 'sqlite',
710
+ database: process.env.DB_DATABASE ?? 'database/database.sqlite',
711
+ },
712
+ },
713
+ }
714
+ `;
777
715
  }
778
716
  /**
779
- * Generate check-dependencies.ts script content.
717
+ * Generate database configuration (detailed version)
780
718
  */
781
- generateCheckDependenciesScript() {
719
+ static generateDetailedDatabaseConfig() {
782
720
  return `/**
783
- * \u76F8\u4F9D\u5957\u4EF6\u7248\u672C\u6AA2\u67E5\u8173\u672C
784
- *
785
- * \u6AA2\u67E5 package.json \u4E2D\u7684\u5957\u4EF6\u662F\u5426\u70BA\u6700\u65B0\u7A69\u5B9A\u7248\u672C
786
- * \u4E26\u63D0\u4F9B\u66F4\u65B0\u5EFA\u8B70
721
+ * Database Configuration
787
722
  */
723
+ export default {
724
+ /**
725
+ * Default connection
726
+ */
727
+ default: process.env.DB_CONNECTION ?? 'sqlite',
788
728
 
789
- import { readFileSync } from 'fs'
790
- import { join } from 'path'
791
-
792
- interface PackageJson {
793
- dependencies?: Record<string, string>
794
- devDependencies?: Record<string, string>
795
- }
796
-
797
- interface PackageInfo {
798
- name: string
799
- current: string
800
- latest: string
801
- outdated: boolean
802
- }
729
+ /**
730
+ * Database connections
731
+ */
732
+ connections: {
733
+ sqlite: {
734
+ driver: 'sqlite',
735
+ database: process.env.DB_DATABASE ?? 'database/database.sqlite',
736
+ },
803
737
 
804
- const colors = {
805
- reset: '\\x1b[0m',
806
- green: '\\x1b[32m',
807
- yellow: '\\x1b[33m',
808
- red: '\\x1b[31m',
809
- blue: '\\x1b[36m',
810
- }
738
+ mysql: {
739
+ driver: 'mysql',
740
+ host: process.env.DB_HOST ?? 'localhost',
741
+ port: Number(process.env.DB_PORT ?? 3306),
742
+ database: process.env.DB_DATABASE ?? 'forge',
743
+ username: process.env.DB_USERNAME ?? 'forge',
744
+ password: process.env.DB_PASSWORD ?? '',
745
+ },
811
746
 
812
- function log(message: string, color: keyof typeof colors = 'reset') {
813
- console.log(\`\${colors[color]}\${message}\${colors.reset}\`)
814
- }
747
+ postgres: {
748
+ driver: 'postgres',
749
+ host: process.env.DB_HOST ?? 'localhost',
750
+ port: Number(process.env.DB_PORT ?? 5432),
751
+ database: process.env.DB_DATABASE ?? 'forge',
752
+ username: process.env.DB_USERNAME ?? 'forge',
753
+ password: process.env.DB_PASSWORD ?? '',
754
+ },
755
+ },
815
756
 
816
- async function getLatestVersion(packageName: string): Promise<string | null> {
817
- try {
818
- const response = await fetch(\`https://registry.npmjs.org/\${packageName}/latest\`)
819
- if (!response.ok) return null
820
- const data = await response.json()
821
- return data.version
822
- } catch {
823
- return null
757
+ /**
758
+ * Migration settings
759
+ */
760
+ migrations: {
761
+ table: 'migrations',
762
+ path: 'database/migrations',
763
+ },
764
+ }
765
+ `;
824
766
  }
767
+ /**
768
+ * Generate auth configuration
769
+ */
770
+ static generateAuthConfig() {
771
+ return `export default {
772
+ defaults: { guard: 'web' },
773
+ guards: {
774
+ web: { driver: 'session', provider: 'users' },
775
+ api: { driver: 'token', provider: 'users' },
776
+ },
825
777
  }
826
-
827
- function parseVersion(version: string): string {
828
- // \u79FB\u9664 ^, ~, >= \u7B49\u524D\u7DB4
829
- return version.replace(/^[\\^~>=<]/, '')
778
+ `;
779
+ }
780
+ /**
781
+ * Generate cache configuration
782
+ */
783
+ static generateCacheConfig() {
784
+ return `export default {
785
+ default: process.env.CACHE_DRIVER ?? 'memory',
786
+ stores: {
787
+ memory: { driver: 'memory' },
788
+ },
789
+ }
790
+ `;
791
+ }
792
+ /**
793
+ * Generate logging configuration
794
+ */
795
+ static generateLoggingConfig() {
796
+ return `export default {
797
+ default: process.env.LOG_CHANNEL ?? 'console',
798
+ channels: {
799
+ console: { driver: 'console', level: process.env.LOG_LEVEL ?? 'debug' },
800
+ },
830
801
  }
802
+ `;
803
+ }
804
+ /**
805
+ * Generate view configuration
806
+ */
807
+ static generateViewConfig() {
808
+ return `/**
809
+ * View Configuration
810
+ */
811
+ export default {
812
+ /**
813
+ * View engine
814
+ */
815
+ engine: 'html',
831
816
 
832
- async function checkPackage(
833
- name: string,
834
- currentVersion: string
835
- ): Promise<PackageInfo | null> {
836
- // \u8DF3\u904E\u672C\u5730\u9023\u7D50\u7684\u5957\u4EF6
837
- if (currentVersion.startsWith('link:') || currentVersion.startsWith('workspace:')) {
838
- return null
817
+ /**
818
+ * View directory
819
+ */
820
+ path: 'src/views',
821
+
822
+ /**
823
+ * Cache views in production
824
+ */
825
+ cache: process.env.NODE_ENV === 'production',
826
+ }
827
+ `;
839
828
  }
829
+ };
830
+
831
+ // src/utils/ServiceProviderGenerator.ts
832
+ var ServiceProviderGenerator = class {
833
+ /**
834
+ * Generate App Service Provider
835
+ */
836
+ static generateAppServiceProvider(context, architectureName) {
837
+ return `/**
838
+ * App Service Provider
839
+ */
840
840
 
841
- const current = parseVersion(currentVersion)
842
- const latest = await getLatestVersion(name)
841
+ import { ServiceProvider, type Container, type PlanetCore } from '@gravito/core'
843
842
 
844
- if (!latest) {
845
- return null
843
+ export class AppServiceProvider extends ServiceProvider {
844
+ register(_container: Container): void {
845
+ // Register application services
846
846
  }
847
847
 
848
- return {
849
- name,
850
- current,
851
- latest,
852
- outdated: current !== latest,
848
+ boot(_core: PlanetCore): void {
849
+ console.log('${context.name} (${architectureName}) booted!')
853
850
  }
854
851
  }
852
+ `;
853
+ }
854
+ /**
855
+ * Generate Middleware Provider
856
+ */
857
+ static generateMiddlewareProvider() {
858
+ return `/**
859
+ * Middleware Service Provider
860
+ */
855
861
 
856
- async function main() {
857
- log('\\n=== \u76F8\u4F9D\u5957\u4EF6\u7248\u672C\u6AA2\u67E5 ===\\n', 'blue')
862
+ import {
863
+ ServiceProvider,
864
+ type Container,
865
+ type PlanetCore,
866
+ bodySizeLimit,
867
+ securityHeaders,
868
+ } from '@gravito/core'
858
869
 
859
- const packageJsonPath = join(process.cwd(), 'package.json')
860
- const packageJson: PackageJson = JSON.parse(
861
- readFileSync(packageJsonPath, 'utf-8')
862
- )
870
+ export class MiddlewareProvider extends ServiceProvider {
871
+ register(_container: Container): void {}
863
872
 
864
- const allDependencies = {
865
- ...packageJson.dependencies,
866
- ...packageJson.devDependencies,
867
- }
873
+ boot(core: PlanetCore): void {
874
+ const isDev = process.env.NODE_ENV !== 'production'
868
875
 
869
- log(\`\u6AA2\u67E5 \${Object.keys(allDependencies).length} \u500B\u5957\u4EF6...\\n\`, 'yellow')
876
+ core.adapter.use('*', securityHeaders({
877
+ contentSecurityPolicy: isDev ? false : undefined,
878
+ }))
870
879
 
871
- const results: PackageInfo[] = []
872
- const outdated: PackageInfo[] = []
873
- const upToDate: PackageInfo[] = []
880
+ core.adapter.use('*', bodySizeLimit(10 * 1024 * 1024))
874
881
 
875
- // \u6AA2\u67E5\u6240\u6709\u5957\u4EF6
876
- for (const [name, version] of Object.entries(allDependencies)) {
877
- const info = await checkPackage(name, version)
878
- if (info) {
879
- results.push(info)
880
- if (info.outdated) {
881
- outdated.push(info)
882
- } else {
883
- upToDate.push(info)
884
- }
885
- }
882
+ core.logger.info('\u{1F6E1}\uFE0F Middleware registered')
886
883
  }
887
-
888
- // \u986F\u793A\u7D50\u679C
889
- if (upToDate.length > 0) {
890
- log(\`\\n\u2713 \u5DF2\u662F\u6700\u65B0\u7248\u672C (\${upToDate.length}):\`, 'green')
891
- upToDate.forEach((pkg) => {
892
- log(\` \${pkg.name}: \${pkg.current}\`, 'green')
893
- })
884
+ }
885
+ `;
894
886
  }
887
+ /**
888
+ * Generate Route Provider
889
+ */
890
+ static generateRouteProvider(routePath = "../../routes/api", importType = "default") {
891
+ const importStatement = importType === "default" ? `import routes from '${routePath}'` : `import { registerApiRoutes } from '${routePath}'`;
892
+ const routeCall = importType === "default" ? "routes(core.router)" : "registerApiRoutes(core.router)";
893
+ return `/**
894
+ * Route Service Provider
895
+ */
895
896
 
896
- if (outdated.length > 0) {
897
- log(\`\\n\u26A0 \u9700\u8981\u66F4\u65B0 (\${outdated.length}):\`, 'yellow')
898
- outdated.forEach((pkg) => {
899
- log(\` \${pkg.name}: \${pkg.current} \u2192 \${pkg.latest}\`, 'yellow')
900
- })
901
- }
897
+ import { ServiceProvider, type Container, type PlanetCore } from '@gravito/core'
898
+ ${importStatement}
902
899
 
903
- // \u7E3D\u7D50
904
- log('\\n=== \u6AA2\u67E5\u7D50\u679C ===', 'blue')
905
- log(\`\u7E3D\u8A08: \${results.length} \u500B\u5957\u4EF6\`, 'blue')
906
- log(\`\u6700\u65B0: \${upToDate.length} \u500B\`, 'green')
907
- log(\`\u9700\u66F4\u65B0: \${outdated.length} \u500B\`, outdated.length > 0 ? 'yellow' : 'green')
900
+ export class RouteProvider extends ServiceProvider {
901
+ register(_container: Container): void {}
908
902
 
909
- // \u5982\u679C\u6709\u9700\u8981\u66F4\u65B0\u7684\u5957\u4EF6\uFF0C\u8FD4\u56DE\u975E\u96F6\u9000\u51FA\u78BC
910
- if (outdated.length > 0) {
911
- log('\\n\u5EFA\u8B70\u57F7\u884C\u4EE5\u4E0B\u547D\u4EE4\u66F4\u65B0\u5957\u4EF6\uFF1A', 'yellow')
912
- log(' bun update', 'yellow')
913
- process.exit(1)
914
- } else {
915
- log('\\n\u2713 \u6240\u6709\u5957\u4EF6\u90FD\u662F\u6700\u65B0\u7248\u672C\uFF01', 'green')
916
- process.exit(0)
903
+ boot(core: PlanetCore): void {
904
+ ${routeCall}
905
+ core.logger.info('\u{1F6E4}\uFE0F Routes registered')
917
906
  }
918
907
  }
919
-
920
- main().catch((error) => {
921
- log(\`\\n\u932F\u8AA4: \${error.message}\`, 'red')
922
- process.exit(1)
923
- })
924
908
  `;
925
909
  }
926
910
  /**
927
- * Generate check.sh script content.
911
+ * Generate Providers Index
928
912
  */
929
- generateCheckShellScript() {
930
- return `#!/bin/bash
931
-
932
- # \u5C08\u6848\u6AA2\u67E5\u8173\u672C
933
- # \u57F7\u884C\u6240\u6709\u5FC5\u8981\u7684\u6AA2\u67E5\uFF1A\u985E\u578B\u6AA2\u67E5\u3001\u6E2C\u8A66\u3001\u4F9D\u8CF4\u6AA2\u67E5\u7B49
934
-
935
- set -e
936
-
937
- # \u984F\u8272\u5B9A\u7FA9
938
- GREEN='\\033[0;32m'
939
- YELLOW='\\033[1;33m'
940
- RED='\\033[0;31m'
941
- BLUE='\\033[0;34m'
942
- NC='\\033[0m' # No Color
943
-
944
- echo -e "\${BLUE}=== \u5C08\u6848\u6AA2\u67E5 ===\${NC}\\n"
945
-
946
- # \u6AA2\u67E5\u662F\u5426\u5728\u6B63\u78BA\u7684\u76EE\u9304
947
- if [ ! -f "package.json" ]; then
948
- echo -e "\${RED}\u932F\u8AA4: \u8ACB\u5728\u5C08\u6848\u6839\u76EE\u9304\u57F7\u884C\u6B64\u8173\u672C\${NC}"
949
- exit 1
950
- fi
951
-
952
- # \u6AA2\u67E5 Bun \u662F\u5426\u5B89\u88DD
953
- if ! command -v bun &> /dev/null; then
954
- echo -e "\${RED}\u932F\u8AA4: \u672A\u627E\u5230 bun\uFF0C\u8ACB\u5148\u5B89\u88DD Bun\${NC}"
955
- exit 1
956
- fi
957
-
958
- # 1. \u985E\u578B\u6AA2\u67E5
959
- echo -e "\${YELLOW}[1/3] \u57F7\u884C\u985E\u578B\u6AA2\u67E5...\${NC}"
960
- if bun run typecheck; then
961
- echo -e "\${GREEN}\u2713 \u985E\u578B\u6AA2\u67E5\u901A\u904E\${NC}\\n"
962
- else
963
- echo -e "\${RED}\u2717 \u985E\u578B\u6AA2\u67E5\u5931\u6557\${NC}"
964
- exit 1
965
- fi
966
-
967
- # 2. \u57F7\u884C\u6E2C\u8A66
968
- echo -e "\${YELLOW}[2/3] \u57F7\u884C\u6E2C\u8A66...\${NC}"
969
- if bun test; then
970
- echo -e "\${GREEN}\u2713 \u6E2C\u8A66\u901A\u904E\${NC}\\n"
971
- else
972
- echo -e "\${RED}\u2717 \u6E2C\u8A66\u5931\u6557\${NC}"
973
- exit 1
974
- fi
975
-
976
- # 3. \u6AA2\u67E5\u4F9D\u8CF4\u7248\u672C\uFF08\u53EF\u9078\uFF0C\u56E0\u70BA\u9700\u8981\u7DB2\u8DEF\u9023\u7DDA\uFF09
977
- echo -e "\${YELLOW}[3/3] \u6AA2\u67E5\u4F9D\u8CF4\u7248\u672C...\${NC}"
978
- if bun run check:deps; then
979
- echo -e "\${GREEN}\u2713 \u4F9D\u8CF4\u6AA2\u67E5\u5B8C\u6210\${NC}\\n"
980
- else
981
- 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"
982
- fi
983
-
984
- echo -e "\${GREEN}=== \u6240\u6709\u6AA2\u67E5\u5B8C\u6210 ===\${NC}"
913
+ static generateProvidersIndex(providers = ["AppServiceProvider", "MiddlewareProvider", "RouteProvider"]) {
914
+ const exports2 = providers.map((p) => `export { ${p} } from './${p}'`).join("\n");
915
+ return `/**
916
+ * Application Service Providers
917
+ */
918
+
919
+ ${exports2}
985
920
  `;
986
921
  }
987
922
  /**
988
- * Generate pre-commit.sh script content.
923
+ * Generate Repository Service Provider
989
924
  */
990
- generatePreCommitScript() {
991
- return `#!/bin/bash
992
-
993
- # Pre-commit Hook
994
- # \u5728 git commit \u524D\u81EA\u52D5\u57F7\u884C\u6AA2\u67E5
995
- #
996
- # \u5B89\u88DD\u65B9\u5F0F\uFF1A
997
- # ln -s ../../scripts/pre-commit.sh .git/hooks/pre-commit
998
- # \u6216
999
- # cp scripts/pre-commit.sh .git/hooks/pre-commit
1000
- # chmod +x .git/hooks/pre-commit
1001
-
1002
- set -e
1003
-
1004
- # \u984F\u8272\u5B9A\u7FA9
1005
- GREEN='\\033[0;32m'
1006
- YELLOW='\\033[1;33m'
1007
- RED='\\033[0;31m'
1008
- BLUE='\\033[0;34m'
1009
- NC='\\033[0m' # No Color
1010
-
1011
- echo -e "\${BLUE}=== Pre-commit \u6AA2\u67E5 ===\${NC}\\n"
1012
-
1013
- # \u5207\u63DB\u5230\u5C08\u6848\u6839\u76EE\u9304
1014
- cd "$(git rev-parse --show-toplevel)"
1015
-
1016
- # \u6AA2\u67E5\u662F\u5426\u5728\u6B63\u78BA\u7684\u76EE\u9304
1017
- if [ ! -f "package.json" ]; then
1018
- echo -e "\${RED}\u932F\u8AA4: \u627E\u4E0D\u5230 package.json\${NC}"
1019
- exit 1
1020
- fi
1021
-
1022
- # \u6AA2\u67E5 Bun \u662F\u5426\u5B89\u88DD
1023
- if ! command -v bun &> /dev/null; then
1024
- echo -e "\${RED}\u932F\u8AA4: \u672A\u627E\u5230 bun\uFF0C\u8ACB\u5148\u5B89\u88DD Bun\${NC}"
1025
- exit 1
1026
- fi
1027
-
1028
- # 1. \u985E\u578B\u6AA2\u67E5\uFF08\u5FEB\u901F\u6AA2\u67E5\uFF09
1029
- echo -e "\${YELLOW}[1/2] \u57F7\u884C\u985E\u578B\u6AA2\u67E5...\${NC}"
1030
- if bun run typecheck; then
1031
- echo -e "\${GREEN}\u2713 \u985E\u578B\u6AA2\u67E5\u901A\u904E\${NC}\\n"
1032
- else
1033
- echo -e "\${RED}\u2717 \u985E\u578B\u6AA2\u67E5\u5931\u6557\${NC}"
1034
- echo -e "\${YELLOW}\u63D0\u793A: \u8ACB\u4FEE\u6B63\u985E\u578B\u932F\u8AA4\u5F8C\u518D\u63D0\u4EA4\${NC}"
1035
- exit 1
1036
- fi
1037
-
1038
- # 2. \u57F7\u884C\u6E2C\u8A66\uFF08\u53EF\u9078\uFF0C\u5982\u679C\u6E2C\u8A66\u6642\u9593\u8F03\u9577\u53EF\u4EE5\u8A3B\u89E3\u6389\uFF09
1039
- echo -e "\${YELLOW}[2/2] \u57F7\u884C\u6E2C\u8A66...\${NC}"
1040
- if bun test; then
1041
- echo -e "\${GREEN}\u2713 \u6E2C\u8A66\u901A\u904E\${NC}\\n"
1042
- else
1043
- echo -e "\${RED}\u2717 \u6E2C\u8A66\u5931\u6557\${NC}"
1044
- echo -e "\${YELLOW}\u63D0\u793A: \u8ACB\u4FEE\u6B63\u6E2C\u8A66\u932F\u8AA4\u5F8C\u518D\u63D0\u4EA4\${NC}"
1045
- exit 1
1046
- fi
1047
-
1048
- echo -e "\${GREEN}=== Pre-commit \u6AA2\u67E5\u901A\u904E ===\${NC}\\n"
925
+ static generateRepositoryServiceProvider(repositories = [], additionalServices = []) {
926
+ const repositoryRegistrations = repositories.map((repo) => ` container.singleton('${repo}', () => new ${repo}())`).join("\n");
927
+ const serviceRegistrations = additionalServices.map((service) => ` container.singleton('${service}', () => new ${service}())`).join("\n");
928
+ const imports = [
929
+ ...repositories.map(
930
+ (repo) => `import { ${repo} } from '../Persistence/Repositories/${repo}'`
931
+ ),
932
+ ...additionalServices.map(
933
+ (service) => `import { ${service} } from '../ExternalServices/${service}'`
934
+ )
935
+ ].join("\n");
936
+ return `/**
937
+ * Repository Service Provider
938
+ *
939
+ * Binds repository interfaces to implementations.
940
+ */
941
+
942
+ import { ServiceProvider, type Container } from '@gravito/core'
943
+ ${imports}
944
+
945
+ export class RepositoryServiceProvider extends ServiceProvider {
946
+ register(container: Container): void {
947
+ ${repositoryRegistrations || " // Bind repositories here"}
948
+ ${serviceRegistrations || " // Bind external services here"}
949
+ }
950
+ }
1049
951
  `;
1050
952
  }
1051
953
  /**
1052
- * Generate CHECK_SYSTEM.md documentation.
954
+ * Generate Database Provider
1053
955
  */
1054
- generateCheckSystemDoc(context) {
1055
- return `# \u5C08\u6848\u6AA2\u67E5\u7CFB\u7D71
1056
-
1057
- \u672C\u5C08\u6848\u5DF2\u5EFA\u7ACB\u5B8C\u6574\u7684\u672C\u5730\u6AA2\u67E5\u6A5F\u5236\uFF0C\u7121\u9700\u4F9D\u8CF4 GitHub CI\u3002
1058
-
1059
- ## \u5FEB\u901F\u958B\u59CB
1060
-
1061
- ### \u57F7\u884C\u5B8C\u6574\u6AA2\u67E5
1062
- \`\`\`bash
1063
- bun run validate
1064
- \`\`\`
1065
-
1066
- ### \u57F7\u884C\u55AE\u9805\u6AA2\u67E5
1067
- \`\`\`bash
1068
- # \u985E\u578B\u6AA2\u67E5
1069
- bun run typecheck
1070
-
1071
- # \u6E2C\u8A66
1072
- bun run test
1073
-
1074
- # \u4F9D\u8CF4\u7248\u672C\u6AA2\u67E5
1075
- bun run check:deps
1076
- \`\`\`
1077
-
1078
- ## \u53EF\u7528\u547D\u4EE4
1079
-
1080
- ### Package.json \u8173\u672C
1081
-
1082
- | \u547D\u4EE4 | \u8AAA\u660E |
1083
- |------|------|
1084
- | \`bun run typecheck\` | TypeScript \u985E\u578B\u6AA2\u67E5 |
1085
- | \`bun run test\` | \u57F7\u884C\u6240\u6709\u6E2C\u8A66 |
1086
- | \`bun run check\` | \u985E\u578B\u6AA2\u67E5 + \u6E2C\u8A66 |
1087
- | \`bun run check:deps\` | \u6AA2\u67E5\u4F9D\u8CF4\u7248\u672C |
1088
- | \`bun run validate\` | \u5B8C\u6574\u9A57\u8B49\uFF08\u985E\u578B + \u6E2C\u8A66 + \u4F9D\u8CF4\uFF09 |
1089
- | \`bun run precommit\` | \u7B49\u540C\u65BC \`validate\` |
1090
-
1091
- ### Shell \u8173\u672C
1092
-
1093
- | \u8173\u672C | \u8AAA\u660E |
1094
- |------|------|
1095
- | \`./scripts/check.sh\` | \u5B8C\u6574\u5C08\u6848\u6AA2\u67E5\uFF08Shell \u7248\u672C\uFF09 |
1096
- | \`./scripts/pre-commit.sh\` | Pre-commit hook \u8173\u672C |
1097
-
1098
- ## Pre-commit Hook\uFF08\u63A8\u85A6\uFF09
1099
-
1100
- \u5B89\u88DD pre-commit hook \u5F8C\uFF0C\u6BCF\u6B21 \`git commit\` \u524D\u6703\u81EA\u52D5\u57F7\u884C\u6AA2\u67E5\uFF1A
1101
-
1102
- \`\`\`bash
1103
- # \u5B89\u88DD pre-commit hook
1104
- ln -s ../../scripts/pre-commit.sh .git/hooks/pre-commit
1105
-
1106
- # \u6216\u4F7F\u7528\u8907\u88FD\u65B9\u5F0F
1107
- cp scripts/pre-commit.sh .git/hooks/pre-commit
1108
- chmod +x .git/hooks/pre-commit
1109
- \`\`\`
1110
-
1111
- **\u529F\u80FD\uFF1A**
1112
- - \u2705 \u81EA\u52D5\u57F7\u884C\u985E\u578B\u6AA2\u67E5
1113
- - \u2705 \u81EA\u52D5\u57F7\u884C\u6E2C\u8A66
1114
- - \u274C \u6AA2\u67E5\u5931\u6557\u6642\u963B\u6B62\u63D0\u4EA4
1115
-
1116
- **\u8DF3\u904E\u6AA2\u67E5\uFF08\u4E0D\u63A8\u85A6\uFF09\uFF1A**
1117
- \`\`\`bash
1118
- git commit --no-verify -m "\u7DCA\u6025\u4FEE\u5FA9"
1119
- \`\`\`
1120
-
1121
- ## \u6AA2\u67E5\u9805\u76EE
1122
-
1123
- ### 1. \u985E\u578B\u6AA2\u67E5
1124
- - \u4F7F\u7528 \`tsc --noEmit\` \u6AA2\u67E5 TypeScript \u985E\u578B
1125
- - \u78BA\u4FDD\u6C92\u6709\u985E\u578B\u932F\u8AA4
1126
-
1127
- ### 2. \u6E2C\u8A66
1128
- - \u57F7\u884C\u6240\u6709\u55AE\u5143\u6E2C\u8A66\u548C\u6574\u5408\u6E2C\u8A66
1129
- - \u78BA\u4FDD\u6E2C\u8A66\u901A\u904E
1130
-
1131
- ### 3. \u4F9D\u8CF4\u6AA2\u67E5\uFF08\u53EF\u9078\uFF09
1132
- - \u6AA2\u67E5\u5957\u4EF6\u7248\u672C\u662F\u5426\u70BA\u6700\u65B0
1133
- - \u63D0\u4F9B\u66F4\u65B0\u5EFA\u8B70
1134
- - \u9700\u8981\u7DB2\u8DEF\u9023\u7DDA
1135
-
1136
- ## \u5DE5\u4F5C\u6D41\u7A0B\u5EFA\u8B70
1137
-
1138
- ### \u958B\u767C\u6642
1139
- 1. \u958B\u767C\u529F\u80FD
1140
- 2. \u63D0\u4EA4\u524D\u57F7\u884C \`bun run validate\`
1141
- 3. \u4FEE\u6B63\u554F\u984C
1142
- 4. \u63D0\u4EA4\u7A0B\u5F0F\u78BC
1143
-
1144
- ### \u4F7F\u7528 Pre-commit Hook\uFF08\u63A8\u85A6\uFF09
1145
- 1. \u5B89\u88DD pre-commit hook\uFF08\u53EA\u9700\u4E00\u6B21\uFF09
1146
- 2. \u6B63\u5E38\u958B\u767C\u548C\u63D0\u4EA4
1147
- 3. \u6AA2\u67E5\u6703\u81EA\u52D5\u57F7\u884C
1148
- 4. \u5982\u6709\u554F\u984C\uFF0C\u4FEE\u6B63\u5F8C\u91CD\u65B0\u63D0\u4EA4
1149
-
1150
- ## \u6A94\u6848\u7D50\u69CB
1151
-
1152
- \`\`\`
1153
- ${context.nameKebabCase}/
1154
- \u251C\u2500\u2500 package.json # \u6AA2\u67E5\u8173\u672C\u5B9A\u7FA9
1155
- \u251C\u2500\u2500 scripts/
1156
- \u2502 \u251C\u2500\u2500 check.sh # \u5B8C\u6574\u6AA2\u67E5\u8173\u672C\uFF08Shell\uFF09
1157
- \u2502 \u251C\u2500\u2500 check-dependencies.ts # \u4F9D\u8CF4\u7248\u672C\u6AA2\u67E5
1158
- \u2502 \u2514\u2500\u2500 pre-commit.sh # Pre-commit hook
1159
- \u2514\u2500\u2500 CHECK_SYSTEM.md # \u672C\u6587\u4EF6
1160
- \`\`\`
1161
-
1162
- ## \u6CE8\u610F\u4E8B\u9805
1163
-
1164
- 1. **\u4F9D\u8CF4\u6AA2\u67E5\u9700\u8981\u7DB2\u8DEF\u9023\u7DDA**\uFF1A\`check:deps\` \u9700\u8981\u9023\u63A5\u5230 npm registry
1165
- 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
1166
- 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
1167
-
1168
- ## \u6545\u969C\u6392\u9664
1169
-
1170
- ### \u6AA2\u67E5\u5931\u6557
1171
- 1. \u67E5\u770B\u932F\u8AA4\u8A0A\u606F
1172
- 2. \u4FEE\u6B63\u554F\u984C
1173
- 3. \u91CD\u65B0\u57F7\u884C\u6AA2\u67E5
956
+ static generateDatabaseProvider() {
957
+ return `/**
958
+ * Database Service Provider
959
+ */
1174
960
 
1175
- ### \u8DF3\u904E\u6AA2\u67E5
1176
- \u53EA\u6709\u5728\u7DCA\u6025\u60C5\u6CC1\u4E0B\u624D\u4F7F\u7528\uFF1A
1177
- \`\`\`bash
1178
- git commit --no-verify
1179
- \`\`\`
961
+ import { ServiceProvider, type Container, type PlanetCore } from '@gravito/core'
962
+ import { OrbitAtlas } from '@gravito/atlas'
1180
963
 
1181
- ### \u79FB\u9664 Pre-commit Hook
1182
- \`\`\`bash
1183
- rm .git/hooks/pre-commit
1184
- \`\`\`
1185
- `;
964
+ export class DatabaseProvider extends ServiceProvider {
965
+ register(_container: Container): void {
966
+ // Register database connections
1186
967
  }
1187
- /**
1188
- * Log a message if verbose mode is enabled.
1189
- */
1190
- log(message) {
1191
- if (this.config.verbose) {
1192
- console.log(message);
1193
- }
968
+
969
+ boot(core: PlanetCore): void {
970
+ // Initialize database
971
+ core.logger.info('\u{1F5C4}\uFE0F Database initialized')
1194
972
  }
1195
- /**
1196
- * Create generator context from options.
1197
- */
1198
- static createContext(name, targetDir, architecture, packageManager = "bun", extra = {}) {
1199
- const now = /* @__PURE__ */ new Date();
1200
- const pascalCase = name.replace(/[-_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : "").replace(/^./, (c) => c.toUpperCase());
1201
- const camelCase = pascalCase.replace(/^./, (c) => c.toLowerCase());
1202
- const snakeCase = name.replace(/([A-Z])/g, "_$1").toLowerCase().replace(/^_/, "").replace(/[-\s]+/g, "_");
1203
- const kebabCase = name.replace(/([A-Z])/g, "-$1").toLowerCase().replace(/^-/, "").replace(/[_\s]+/g, "-");
1204
- return {
1205
- name,
1206
- namePascalCase: pascalCase,
1207
- nameCamelCase: camelCase,
1208
- nameSnakeCase: snakeCase,
1209
- nameKebabCase: kebabCase,
1210
- targetDir,
1211
- architecture,
1212
- packageManager,
1213
- year: now.getFullYear().toString(),
1214
- date: now.toISOString().split("T")[0] ?? now.toISOString().slice(0, 10),
1215
- ...extra
1216
- };
973
+ }
974
+ `;
1217
975
  }
1218
976
  };
1219
977
 
@@ -1467,57 +1225,22 @@ var CleanArchitectureGenerator = class extends BaseGenerator {
1467
1225
  ];
1468
1226
  }
1469
1227
  // ─────────────────────────────────────────────────────────────
1470
- // Config Generators (similar to MVC but simplified)
1228
+ // Config Generators (using shared ConfigGenerator)
1471
1229
  // ─────────────────────────────────────────────────────────────
1472
1230
  generateAppConfig(context) {
1473
- return `export default {
1474
- name: process.env.APP_NAME ?? '${context.name}',
1475
- env: process.env.APP_ENV ?? 'development',
1476
- debug: process.env.APP_DEBUG === 'true',
1477
- url: process.env.APP_URL ?? 'http://localhost:3000',
1478
- key: process.env.APP_KEY,
1479
- }
1480
- `;
1231
+ return ConfigGenerator.generateSimpleAppConfig(context);
1481
1232
  }
1482
1233
  generateDatabaseConfig() {
1483
- return `export default {
1484
- default: process.env.DB_CONNECTION ?? 'sqlite',
1485
- connections: {
1486
- sqlite: {
1487
- driver: 'sqlite',
1488
- database: process.env.DB_DATABASE ?? 'database/database.sqlite',
1489
- },
1490
- },
1491
- }
1492
- `;
1234
+ return ConfigGenerator.generateSimpleDatabaseConfig();
1493
1235
  }
1494
1236
  generateAuthConfig() {
1495
- return `export default {
1496
- defaults: { guard: 'web' },
1497
- guards: {
1498
- web: { driver: 'session', provider: 'users' },
1499
- api: { driver: 'token', provider: 'users' },
1500
- },
1501
- }
1502
- `;
1237
+ return ConfigGenerator.generateAuthConfig();
1503
1238
  }
1504
1239
  generateCacheConfig() {
1505
- return `export default {
1506
- default: process.env.CACHE_DRIVER ?? 'memory',
1507
- stores: {
1508
- memory: { driver: 'memory' },
1509
- },
1510
- }
1511
- `;
1240
+ return ConfigGenerator.generateCacheConfig();
1512
1241
  }
1513
1242
  generateLoggingConfig() {
1514
- return `export default {
1515
- default: process.env.LOG_CHANNEL ?? 'console',
1516
- channels: {
1517
- console: { driver: 'console', level: process.env.LOG_LEVEL ?? 'debug' },
1518
- },
1519
- }
1520
- `;
1243
+ return ConfigGenerator.generateLoggingConfig();
1521
1244
  }
1522
1245
  generateUserEntity() {
1523
1246
  return `/**
@@ -1844,103 +1567,30 @@ export class MailService implements IMailService {
1844
1567
  `;
1845
1568
  }
1846
1569
  generateAppServiceProvider(context) {
1847
- return `/**
1848
- * App Service Provider
1849
- */
1850
-
1851
- import { ServiceProvider, type Container, type PlanetCore } from '@gravito/core'
1852
-
1853
- export class AppServiceProvider extends ServiceProvider {
1854
- register(_container: Container): void {
1855
- // Register application services
1856
- }
1857
-
1858
- boot(_core: PlanetCore): void {
1859
- console.log('${context.name} (Clean Architecture) booted!')
1860
- }
1861
- }
1862
- `;
1570
+ return ServiceProviderGenerator.generateAppServiceProvider(context, "Clean Architecture");
1863
1571
  }
1864
1572
  generateRepositoryServiceProvider() {
1865
- return `/**
1866
- * Repository Service Provider
1867
- *
1868
- * Binds repository interfaces to implementations.
1869
- */
1870
-
1871
- import { ServiceProvider, type Container } from '@gravito/core'
1872
- import { UserRepository } from '../Persistence/Repositories/UserRepository'
1873
- import { MailService } from '../ExternalServices/MailService'
1874
-
1875
- export class RepositoryServiceProvider extends ServiceProvider {
1876
- register(container: Container): void {
1877
- // Bind repositories
1878
- container.singleton('userRepository', () => new UserRepository())
1879
-
1880
- // Bind external services
1881
- container.singleton('mailService', () => new MailService())
1882
- }
1883
- }
1884
- `;
1573
+ return ServiceProviderGenerator.generateRepositoryServiceProvider(
1574
+ ["UserRepository"],
1575
+ ["MailService"]
1576
+ );
1885
1577
  }
1886
1578
  generateProvidersIndex() {
1887
- return `/**
1888
- * Application Service Providers
1889
- */
1890
-
1891
- export { AppServiceProvider } from './AppServiceProvider'
1892
- export { RepositoryServiceProvider } from './RepositoryServiceProvider'
1893
- export { MiddlewareProvider } from './MiddlewareProvider'
1894
- export { RouteProvider } from './RouteProvider'
1895
- `;
1579
+ return ServiceProviderGenerator.generateProvidersIndex([
1580
+ "AppServiceProvider",
1581
+ "RepositoryServiceProvider",
1582
+ "MiddlewareProvider",
1583
+ "RouteProvider"
1584
+ ]);
1896
1585
  }
1897
1586
  generateMiddlewareProvider() {
1898
- return `/**
1899
- * Middleware Service Provider
1900
- */
1901
-
1902
- import {
1903
- ServiceProvider,
1904
- type Container,
1905
- type PlanetCore,
1906
- bodySizeLimit,
1907
- securityHeaders,
1908
- } from '@gravito/core'
1909
-
1910
- export class MiddlewareProvider extends ServiceProvider {
1911
- register(_container: Container): void {}
1912
-
1913
- boot(core: PlanetCore): void {
1914
- const isDev = process.env.NODE_ENV !== 'production'
1915
-
1916
- core.adapter.use('*', securityHeaders({
1917
- contentSecurityPolicy: isDev ? false : undefined,
1918
- }))
1919
-
1920
- core.adapter.use('*', bodySizeLimit(10 * 1024 * 1024))
1921
-
1922
- core.logger.info('\u{1F6E1}\uFE0F Middleware registered')
1923
- }
1924
- }
1925
- `;
1587
+ return ServiceProviderGenerator.generateMiddlewareProvider();
1926
1588
  }
1927
1589
  generateRouteProvider() {
1928
- return `/**
1929
- * Route Service Provider
1930
- */
1931
-
1932
- import { ServiceProvider, type Container, type PlanetCore } from '@gravito/core'
1933
- import { registerApiRoutes } from '../../Interface/Http/Routes/api'
1934
-
1935
- export class RouteProvider extends ServiceProvider {
1936
- register(_container: Container): void {}
1937
-
1938
- boot(core: PlanetCore): void {
1939
- registerApiRoutes(core.router)
1940
- core.logger.info('\u{1F6E4}\uFE0F Routes registered')
1941
- }
1942
- }
1943
- `;
1590
+ return ServiceProviderGenerator.generateRouteProvider(
1591
+ "../../Interface/Http/Routes/api",
1592
+ "named"
1593
+ );
1944
1594
  }
1945
1595
  // ─────────────────────────────────────────────────────────────
1946
1596
  // Interface Layer
@@ -2163,7 +1813,7 @@ Created with \u2764\uFE0F using Gravito Framework
2163
1813
  build: "bun build ./src/bootstrap.ts --outdir ./dist --target bun",
2164
1814
  start: "bun run dist/bootstrap.js",
2165
1815
  test: "bun test",
2166
- typecheck: "tsc --noEmit",
1816
+ typecheck: "bun tsc --noEmit",
2167
1817
  check: "bun run typecheck && bun run test",
2168
1818
  "check:deps": "bun run scripts/check-dependencies.ts",
2169
1819
  validate: "bun run check && bun run check:deps",
@@ -2178,107 +1828,16 @@ Created with \u2764\uFE0F using Gravito Framework
2178
1828
  },
2179
1829
  devDependencies: {
2180
1830
  "bun-types": "latest",
2181
- typescript: "^5.0.0"
1831
+ typescript: "^5.9.3"
2182
1832
  }
2183
1833
  };
2184
1834
  return JSON.stringify(pkg, null, 2);
2185
1835
  }
2186
1836
  };
2187
1837
 
2188
- // src/generators/DddGenerator.ts
2189
- var DddGenerator = class extends BaseGenerator {
2190
- get architectureType() {
2191
- return "ddd";
2192
- }
2193
- get displayName() {
2194
- return "Domain-Driven Design (DDD)";
2195
- }
2196
- get description() {
2197
- return "Full DDD with Bounded Contexts, Aggregates, and Event-Driven patterns";
2198
- }
2199
- getDirectoryStructure(context) {
2200
- return [
2201
- {
2202
- type: "directory",
2203
- name: "config",
2204
- children: [
2205
- { type: "file", name: "app.ts", content: this.generateAppConfig(context) },
2206
- { type: "file", name: "database.ts", content: this.generateDatabaseConfig() },
2207
- { type: "file", name: "modules.ts", content: this.generateModulesConfig() },
2208
- { type: "file", name: "cache.ts", content: this.generateCacheConfig() },
2209
- { type: "file", name: "logging.ts", content: this.generateLoggingConfig() }
2210
- ]
2211
- },
2212
- {
2213
- type: "directory",
2214
- name: "src",
2215
- children: [
2216
- // Bootstrap - Application startup and configuration
2217
- this.generateBootstrapDirectory(context),
2218
- // Shared - Cross-module shared components
2219
- this.generateShared(),
2220
- // Modules - Bounded Contexts
2221
- {
2222
- type: "directory",
2223
- name: "Modules",
2224
- children: [
2225
- this.generateModule("Ordering", context),
2226
- this.generateModule("Catalog", context)
2227
- ]
2228
- },
2229
- { type: "file", name: "main.ts", content: this.generateMainEntry(context) }
2230
- ]
2231
- },
2232
- {
2233
- type: "directory",
2234
- name: "tests",
2235
- children: [
2236
- {
2237
- type: "directory",
2238
- name: "Modules",
2239
- children: [
2240
- {
2241
- type: "directory",
2242
- name: "Ordering",
2243
- children: [
2244
- {
2245
- type: "directory",
2246
- name: "Unit",
2247
- children: [{ type: "file", name: ".gitkeep", content: "" }]
2248
- },
2249
- {
2250
- type: "directory",
2251
- name: "Integration",
2252
- children: [{ type: "file", name: ".gitkeep", content: "" }]
2253
- }
2254
- ]
2255
- },
2256
- {
2257
- type: "directory",
2258
- name: "Catalog",
2259
- children: [
2260
- {
2261
- type: "directory",
2262
- name: "Unit",
2263
- children: [{ type: "file", name: ".gitkeep", content: "" }]
2264
- }
2265
- ]
2266
- }
2267
- ]
2268
- },
2269
- {
2270
- type: "directory",
2271
- name: "Shared",
2272
- children: [{ type: "file", name: ".gitkeep", content: "" }]
2273
- }
2274
- ]
2275
- }
2276
- ];
2277
- }
2278
- // ─────────────────────────────────────────────────────────────
2279
- // Bootstrap Directory
2280
- // ─────────────────────────────────────────────────────────────
2281
- generateBootstrapDirectory(context) {
1838
+ // src/generators/ddd/BootstrapGenerator.ts
1839
+ var BootstrapGenerator = class {
1840
+ generate(context) {
2282
1841
  return {
2283
1842
  type: "directory",
2284
1843
  name: "Bootstrap",
@@ -2290,207 +1849,33 @@ var DddGenerator = class extends BaseGenerator {
2290
1849
  ]
2291
1850
  };
2292
1851
  }
2293
- // ─────────────────────────────────────────────────────────────
2294
- // Shared Directory (replaces SharedKernel with user's structure)
2295
- // ─────────────────────────────────────────────────────────────
2296
- generateShared() {
1852
+ generateConfigDirectory(context) {
2297
1853
  return {
2298
1854
  type: "directory",
2299
- name: "Shared",
1855
+ name: "config",
2300
1856
  children: [
2301
- {
2302
- type: "directory",
2303
- name: "Domain",
2304
- children: [
2305
- {
2306
- type: "directory",
2307
- name: "ValueObjects",
2308
- children: [
2309
- { type: "file", name: "Id.ts", content: this.generateIdValueObject() },
2310
- { type: "file", name: "Money.ts", content: this.generateMoneyValueObject() },
2311
- { type: "file", name: "Email.ts", content: this.generateEmailValueObject() }
2312
- ]
2313
- }
2314
- ]
2315
- },
2316
- {
2317
- type: "directory",
2318
- name: "Infrastructure",
2319
- children: [
2320
- {
2321
- type: "directory",
2322
- name: "EventBus",
2323
- children: [
2324
- {
2325
- type: "file",
2326
- name: "EventDispatcher.ts",
2327
- content: this.generateEventDispatcher()
2328
- }
2329
- ]
2330
- }
2331
- ]
2332
- },
2333
- {
2334
- type: "directory",
2335
- name: "Exceptions",
2336
- children: [
2337
- { type: "file", name: "Handler.ts", content: this.generateExceptionHandler() }
2338
- ]
2339
- }
1857
+ { type: "file", name: "app.ts", content: this.generateAppConfig(context) },
1858
+ { type: "file", name: "database.ts", content: this.generateDatabaseConfig() },
1859
+ { type: "file", name: "modules.ts", content: this.generateModulesConfig() },
1860
+ { type: "file", name: "cache.ts", content: this.generateCacheConfig() },
1861
+ { type: "file", name: "logging.ts", content: this.generateLoggingConfig() }
2340
1862
  ]
2341
1863
  };
2342
1864
  }
2343
- // ─────────────────────────────────────────────────────────────
2344
- // Module Generator (replaces Bounded Context)
2345
- // ─────────────────────────────────────────────────────────────
2346
- generateModule(name, context) {
2347
- return {
2348
- type: "directory",
2349
- name,
2350
- children: [
2351
- // Domain Layer
2352
- {
2353
- type: "directory",
2354
- name: "Domain",
2355
- children: [
2356
- {
2357
- type: "directory",
2358
- name: "Aggregates",
2359
- children: [
2360
- {
2361
- type: "directory",
2362
- name,
2363
- children: [
2364
- { type: "file", name: `${name}.ts`, content: this.generateAggregate(name) },
2365
- {
2366
- type: "file",
2367
- name: `${name}Status.ts`,
2368
- content: this.generateAggregateStatus(name)
2369
- }
2370
- ]
2371
- }
2372
- ]
2373
- },
2374
- {
2375
- type: "directory",
2376
- name: "Events",
2377
- children: [
2378
- {
2379
- type: "file",
2380
- name: `${name}Created.ts`,
2381
- content: this.generateCreatedEvent(name)
2382
- }
2383
- ]
2384
- },
2385
- {
2386
- type: "directory",
2387
- name: "Repositories",
2388
- children: [
2389
- {
2390
- type: "file",
2391
- name: `I${name}Repository.ts`,
2392
- content: this.generateRepositoryInterface(name)
2393
- }
2394
- ]
2395
- },
2396
- {
2397
- type: "directory",
2398
- name: "Services",
2399
- children: [{ type: "file", name: ".gitkeep", content: "" }]
2400
- }
2401
- ]
2402
- },
2403
- // Application Layer
2404
- {
2405
- type: "directory",
2406
- name: "Application",
2407
- children: [
2408
- {
2409
- type: "directory",
2410
- name: "Commands",
2411
- children: [
2412
- {
2413
- type: "directory",
2414
- name: `Create${name}`,
2415
- children: [
2416
- {
2417
- type: "file",
2418
- name: `Create${name}Command.ts`,
2419
- content: this.generateCommand(name)
2420
- },
2421
- {
2422
- type: "file",
2423
- name: `Create${name}Handler.ts`,
2424
- content: this.generateCommandHandler(name)
2425
- }
2426
- ]
2427
- }
2428
- ]
2429
- },
2430
- {
2431
- type: "directory",
2432
- name: "Queries",
2433
- children: [
2434
- {
2435
- type: "directory",
2436
- name: `Get${name}ById`,
2437
- children: [
2438
- {
2439
- type: "file",
2440
- name: `Get${name}ByIdQuery.ts`,
2441
- content: this.generateQuery(name)
2442
- },
2443
- {
2444
- type: "file",
2445
- name: `Get${name}ByIdHandler.ts`,
2446
- content: this.generateQueryHandler(name)
2447
- }
2448
- ]
2449
- }
2450
- ]
2451
- },
2452
- {
2453
- type: "directory",
2454
- name: "DTOs",
2455
- children: [{ type: "file", name: `${name}DTO.ts`, content: this.generateDTO(name) }]
2456
- }
2457
- ]
2458
- },
2459
- // Infrastructure Layer
2460
- {
2461
- type: "directory",
2462
- name: "Infrastructure",
2463
- children: [
2464
- {
2465
- type: "directory",
2466
- name: "Persistence",
2467
- children: [
2468
- {
2469
- type: "file",
2470
- name: `${name}Repository.ts`,
2471
- content: this.generateRepository(name)
2472
- }
2473
- ]
2474
- },
2475
- {
2476
- type: "directory",
2477
- name: "Providers",
2478
- children: [
2479
- {
2480
- type: "file",
2481
- name: `${name}ServiceProvider.ts`,
2482
- content: this.generateModuleServiceProvider(name, context)
2483
- }
2484
- ]
2485
- }
2486
- ]
2487
- }
2488
- ]
2489
- };
1865
+ generateMainEntry(_context) {
1866
+ return `/**
1867
+ * Application Entry Point
1868
+ *
1869
+ * Start the HTTP server.
1870
+ */
1871
+
1872
+ import { createApp } from './Bootstrap/app'
1873
+
1874
+ const app = await createApp()
1875
+
1876
+ export default app.liftoff()
1877
+ `;
2490
1878
  }
2491
- // ─────────────────────────────────────────────────────────────
2492
- // Bootstrap File Generators
2493
- // ─────────────────────────────────────────────────────────────
2494
1879
  generateBootstrapApp(_context) {
2495
1880
  return `/**
2496
1881
  * Application Bootstrap
@@ -2616,20 +2001,6 @@ export function registerRoutes(router: any): void {
2616
2001
  // Catalog module
2617
2002
  router.get('/api/products', (c: any) => c.json({ message: 'Product list' }))
2618
2003
  }
2619
- `;
2620
- }
2621
- generateMainEntry(_context) {
2622
- return `/**
2623
- * Application Entry Point
2624
- *
2625
- * Start the HTTP server.
2626
- */
2627
-
2628
- import { createApp } from './Bootstrap/app'
2629
-
2630
- const app = await createApp()
2631
-
2632
- export default app.liftoff()
2633
2004
  `;
2634
2005
  }
2635
2006
  generateModulesConfig() {
@@ -2660,60 +2031,6 @@ export default {
2660
2031
  }
2661
2032
  `;
2662
2033
  }
2663
- generateModuleServiceProvider(name, _context) {
2664
- return `/**
2665
- * ${name} Service Provider
2666
- */
2667
-
2668
- import { ServiceProvider, type Container, type PlanetCore } from '@gravito/core'
2669
- import { ${name}Repository } from '../Persistence/${name}Repository'
2670
-
2671
- export class ${name}ServiceProvider extends ServiceProvider {
2672
- register(container: Container): void {
2673
- container.singleton('${name.toLowerCase()}Repository', () => new ${name}Repository())
2674
- }
2675
-
2676
- boot(_core: PlanetCore): void {
2677
- console.log('[${name}] Module loaded')
2678
- }
2679
- }
2680
- `;
2681
- }
2682
- /**
2683
- * Override package.json for DDD architecture (uses main.ts instead of bootstrap.ts)
2684
- */
2685
- generatePackageJson(context) {
2686
- const pkg = {
2687
- name: context.nameKebabCase,
2688
- version: "0.1.0",
2689
- type: "module",
2690
- scripts: {
2691
- dev: "bun run --watch src/main.ts",
2692
- build: "bun build ./src/main.ts --outdir ./dist --target bun",
2693
- start: "bun run dist/main.js",
2694
- test: "bun test",
2695
- typecheck: "tsc --noEmit",
2696
- check: "bun run typecheck && bun run test",
2697
- "check:deps": "bun run scripts/check-dependencies.ts",
2698
- validate: "bun run check && bun run check:deps",
2699
- precommit: "bun run validate",
2700
- "docker:build": `docker build -t ${context.nameKebabCase} .`,
2701
- "docker:run": `docker run -it -p 3000:3000 ${context.nameKebabCase}`
2702
- },
2703
- dependencies: {
2704
- "@gravito/core": "^1.0.0-beta.5",
2705
- "@gravito/enterprise": "workspace:*"
2706
- },
2707
- devDependencies: {
2708
- "bun-types": "latest",
2709
- typescript: "^5.0.0"
2710
- }
2711
- };
2712
- return JSON.stringify(pkg, null, 2);
2713
- }
2714
- // ─────────────────────────────────────────────────────────────
2715
- // Config Generators
2716
- // ─────────────────────────────────────────────────────────────
2717
2034
  generateAppConfig(context) {
2718
2035
  return `export default {
2719
2036
  name: process.env.APP_NAME ?? '${context.name}',
@@ -2748,159 +2065,155 @@ export class ${name}ServiceProvider extends ServiceProvider {
2748
2065
  }
2749
2066
  `;
2750
2067
  }
2751
- // ─────────────────────────────────────────────────────────────
2752
- // Shared Kernel Files
2753
- // ─────────────────────────────────────────────────────────────
2754
- generateIdValueObject() {
2755
- return `/**
2756
- * ID Value Object
2757
- *
2758
- * Shared identifier across all contexts.
2759
- */
2760
-
2761
- import { ValueObject } from '@gravito/enterprise'
2762
-
2763
- interface IdProps {
2764
- value: string
2765
- }
2766
-
2767
- export class Id extends ValueObject<IdProps> {
2768
- private constructor(value: string) {
2769
- super({ value })
2770
- }
2771
-
2772
- static create(): Id {
2773
- return new Id(crypto.randomUUID())
2774
- }
2775
-
2776
- static from(value: string): Id {
2777
- if (!value) throw new Error('Id cannot be empty')
2778
- return new Id(value)
2779
- }
2780
-
2781
- get value(): string {
2782
- return this.props.value
2783
- }
2784
-
2785
- toString(): string {
2786
- return this.props.value
2787
- }
2788
- }
2789
- `;
2790
- }
2791
- generateMoneyValueObject() {
2792
- return `/**
2793
- * Money Value Object
2794
- */
2795
-
2796
- import { ValueObject } from '@gravito/enterprise'
2797
-
2798
- interface MoneyProps {
2799
- amount: number
2800
- currency: string
2801
- }
2802
-
2803
- export class Money extends ValueObject<MoneyProps> {
2804
- constructor(amount: number, currency: string = 'USD') {
2805
- if (amount < 0) throw new Error('Amount cannot be negative')
2806
- super({ amount, currency })
2807
- }
2808
-
2809
- get amount(): number {
2810
- return this.props.amount
2811
- }
2812
-
2813
- get currency(): string {
2814
- return this.props.currency
2815
- }
2816
-
2817
- add(other: Money): Money {
2818
- this.assertSameCurrency(other)
2819
- return new Money(this.amount + other.amount, this.currency)
2820
- }
2821
-
2822
- subtract(other: Money): Money {
2823
- this.assertSameCurrency(other)
2824
- return new Money(this.amount - other.amount, this.currency)
2825
- }
2826
-
2827
- private assertSameCurrency(other: Money): void {
2828
- if (this.currency !== other.currency) {
2829
- throw new Error('Cannot operate on different currencies')
2830
- }
2831
- }
2832
- }
2833
- `;
2834
- }
2835
- generateEmailValueObject() {
2836
- return `/**
2837
- * Email Value Object
2838
- */
2839
-
2840
- import { ValueObject } from '@gravito/enterprise'
2841
-
2842
- interface EmailProps {
2843
- value: string
2844
- }
2845
-
2846
- export class Email extends ValueObject<EmailProps> {
2847
- private constructor(value: string) {
2848
- super({ value: value.toLowerCase().trim() })
2849
- }
2850
-
2851
- static create(email: string): Email {
2852
- if (!Email.isValid(email)) {
2853
- throw new Error(\`Invalid email: \${email}\`)
2854
- }
2855
- return new Email(email)
2856
- }
2857
-
2858
- static isValid(email: string): boolean {
2859
- return /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email)
2860
- }
2861
-
2862
- get value(): string {
2863
- return this.props.value
2864
- }
2865
- }
2866
- `;
2867
- }
2868
- generateEventDispatcher() {
2869
- return `/**
2870
- * Event Dispatcher
2871
- */
2872
-
2873
- import type { DomainEvent } from '@gravito/enterprise'
2874
-
2875
- type EventHandler = (event: DomainEvent) => void | Promise<void>
2876
-
2877
- export class EventDispatcher {
2878
- private handlers: Map<string, EventHandler[]> = new Map()
2879
-
2880
- subscribe(eventName: string, handler: EventHandler): void {
2881
- const handlers = this.handlers.get(eventName) ?? []
2882
- handlers.push(handler)
2883
- this.handlers.set(eventName, handlers)
2884
- }
2885
-
2886
- async dispatch(event: DomainEvent): Promise<void> {
2887
- const handlers = this.handlers.get(event.eventName) ?? []
2888
- for (const handler of handlers) {
2889
- await handler(event)
2890
- }
2891
- }
2068
+ };
2892
2069
 
2893
- async dispatchAll(events: DomainEvent[]): Promise<void> {
2894
- for (const event of events) {
2895
- await this.dispatch(event)
2896
- }
2897
- }
2898
- }
2899
- `;
2070
+ // src/generators/ddd/ModuleGenerator.ts
2071
+ var ModuleGenerator = class {
2072
+ generate(name, context) {
2073
+ return {
2074
+ type: "directory",
2075
+ name,
2076
+ children: [
2077
+ // Domain Layer
2078
+ {
2079
+ type: "directory",
2080
+ name: "Domain",
2081
+ children: [
2082
+ {
2083
+ type: "directory",
2084
+ name: "Aggregates",
2085
+ children: [
2086
+ {
2087
+ type: "directory",
2088
+ name,
2089
+ children: [
2090
+ { type: "file", name: `${name}.ts`, content: this.generateAggregate(name) },
2091
+ {
2092
+ type: "file",
2093
+ name: `${name}Status.ts`,
2094
+ content: this.generateAggregateStatus(name)
2095
+ }
2096
+ ]
2097
+ }
2098
+ ]
2099
+ },
2100
+ {
2101
+ type: "directory",
2102
+ name: "Events",
2103
+ children: [
2104
+ {
2105
+ type: "file",
2106
+ name: `${name}Created.ts`,
2107
+ content: this.generateCreatedEvent(name)
2108
+ }
2109
+ ]
2110
+ },
2111
+ {
2112
+ type: "directory",
2113
+ name: "Repositories",
2114
+ children: [
2115
+ {
2116
+ type: "file",
2117
+ name: `I${name}Repository.ts`,
2118
+ content: this.generateRepositoryInterface(name)
2119
+ }
2120
+ ]
2121
+ },
2122
+ {
2123
+ type: "directory",
2124
+ name: "Services",
2125
+ children: [{ type: "file", name: ".gitkeep", content: "" }]
2126
+ }
2127
+ ]
2128
+ },
2129
+ // Application Layer
2130
+ {
2131
+ type: "directory",
2132
+ name: "Application",
2133
+ children: [
2134
+ {
2135
+ type: "directory",
2136
+ name: "Commands",
2137
+ children: [
2138
+ {
2139
+ type: "directory",
2140
+ name: `Create${name}`,
2141
+ children: [
2142
+ {
2143
+ type: "file",
2144
+ name: `Create${name}Command.ts`,
2145
+ content: this.generateCommand(name)
2146
+ },
2147
+ {
2148
+ type: "file",
2149
+ name: `Create${name}Handler.ts`,
2150
+ content: this.generateCommandHandler(name)
2151
+ }
2152
+ ]
2153
+ }
2154
+ ]
2155
+ },
2156
+ {
2157
+ type: "directory",
2158
+ name: "Queries",
2159
+ children: [
2160
+ {
2161
+ type: "directory",
2162
+ name: `Get${name}ById`,
2163
+ children: [
2164
+ {
2165
+ type: "file",
2166
+ name: `Get${name}ByIdQuery.ts`,
2167
+ content: this.generateQuery(name)
2168
+ },
2169
+ {
2170
+ type: "file",
2171
+ name: `Get${name}ByIdHandler.ts`,
2172
+ content: this.generateQueryHandler(name)
2173
+ }
2174
+ ]
2175
+ }
2176
+ ]
2177
+ },
2178
+ {
2179
+ type: "directory",
2180
+ name: "DTOs",
2181
+ children: [{ type: "file", name: `${name}DTO.ts`, content: this.generateDTO(name) }]
2182
+ }
2183
+ ]
2184
+ },
2185
+ // Infrastructure Layer
2186
+ {
2187
+ type: "directory",
2188
+ name: "Infrastructure",
2189
+ children: [
2190
+ {
2191
+ type: "directory",
2192
+ name: "Persistence",
2193
+ children: [
2194
+ {
2195
+ type: "file",
2196
+ name: `${name}Repository.ts`,
2197
+ content: this.generateRepository(name)
2198
+ }
2199
+ ]
2200
+ },
2201
+ {
2202
+ type: "directory",
2203
+ name: "Providers",
2204
+ children: [
2205
+ {
2206
+ type: "file",
2207
+ name: `${name}ServiceProvider.ts`,
2208
+ content: this.generateModuleServiceProvider(name, context)
2209
+ }
2210
+ ]
2211
+ }
2212
+ ]
2213
+ }
2214
+ ]
2215
+ };
2900
2216
  }
2901
- // ─────────────────────────────────────────────────────────────
2902
- // Bounded Context Templates
2903
- // ─────────────────────────────────────────────────────────────
2904
2217
  generateAggregate(name) {
2905
2218
  return `/**
2906
2219
  * ${name} Aggregate Root
@@ -3122,15 +2435,359 @@ export class ${name}Repository implements I${name}Repository {
3122
2435
  }
3123
2436
  `;
3124
2437
  }
3125
- generateExceptionHandler() {
3126
- return `/**
3127
- * Exception Handler
3128
- */
3129
-
3130
- export function report(error: unknown): void {
3131
- console.error('[Exception]', error)
3132
- }
3133
- `;
2438
+ generateModuleServiceProvider(name, _context) {
2439
+ return `/**
2440
+ * ${name} Service Provider
2441
+ */
2442
+
2443
+ import { ServiceProvider, type Container, type PlanetCore } from '@gravito/core'
2444
+ import { ${name}Repository } from '../Persistence/${name}Repository'
2445
+
2446
+ export class ${name}ServiceProvider extends ServiceProvider {
2447
+ register(container: Container): void {
2448
+ container.singleton('${name.toLowerCase()}Repository', () => new ${name}Repository())
2449
+ }
2450
+
2451
+ boot(_core: PlanetCore): void {
2452
+ console.log('[${name}] Module loaded')
2453
+ }
2454
+ }
2455
+ `;
2456
+ }
2457
+ };
2458
+
2459
+ // src/generators/ddd/SharedKernelGenerator.ts
2460
+ var SharedKernelGenerator = class {
2461
+ generate() {
2462
+ return {
2463
+ type: "directory",
2464
+ name: "Shared",
2465
+ children: [
2466
+ {
2467
+ type: "directory",
2468
+ name: "Domain",
2469
+ children: [
2470
+ {
2471
+ type: "directory",
2472
+ name: "ValueObjects",
2473
+ children: [
2474
+ { type: "file", name: "Id.ts", content: this.generateIdValueObject() },
2475
+ { type: "file", name: "Money.ts", content: this.generateMoneyValueObject() },
2476
+ { type: "file", name: "Email.ts", content: this.generateEmailValueObject() }
2477
+ ]
2478
+ }
2479
+ ]
2480
+ },
2481
+ {
2482
+ type: "directory",
2483
+ name: "Infrastructure",
2484
+ children: [
2485
+ {
2486
+ type: "directory",
2487
+ name: "EventBus",
2488
+ children: [
2489
+ {
2490
+ type: "file",
2491
+ name: "EventDispatcher.ts",
2492
+ content: this.generateEventDispatcher()
2493
+ }
2494
+ ]
2495
+ }
2496
+ ]
2497
+ },
2498
+ {
2499
+ type: "directory",
2500
+ name: "Exceptions",
2501
+ children: [
2502
+ { type: "file", name: "Handler.ts", content: this.generateExceptionHandler() }
2503
+ ]
2504
+ }
2505
+ ]
2506
+ };
2507
+ }
2508
+ generateIdValueObject() {
2509
+ return `/**
2510
+ * ID Value Object
2511
+ *
2512
+ * Shared identifier across all contexts.
2513
+ */
2514
+
2515
+ import { ValueObject } from '@gravito/enterprise'
2516
+
2517
+ interface IdProps {
2518
+ value: string
2519
+ }
2520
+
2521
+ export class Id extends ValueObject<IdProps> {
2522
+ private constructor(value: string) {
2523
+ super({ value })
2524
+ }
2525
+
2526
+ static create(): Id {
2527
+ return new Id(crypto.randomUUID())
2528
+ }
2529
+
2530
+ static from(value: string): Id {
2531
+ if (!value) throw new Error('Id cannot be empty')
2532
+ return new Id(value)
2533
+ }
2534
+
2535
+ get value(): string {
2536
+ return this.props.value
2537
+ }
2538
+
2539
+ toString(): string {
2540
+ return this.props.value
2541
+ }
2542
+ }
2543
+ `;
2544
+ }
2545
+ generateMoneyValueObject() {
2546
+ return `/**
2547
+ * Money Value Object
2548
+ */
2549
+
2550
+ import { ValueObject } from '@gravito/enterprise'
2551
+
2552
+ interface MoneyProps {
2553
+ amount: number
2554
+ currency: string
2555
+ }
2556
+
2557
+ export class Money extends ValueObject<MoneyProps> {
2558
+ constructor(amount: number, currency: string = 'USD') {
2559
+ if (amount < 0) throw new Error('Amount cannot be negative')
2560
+ super({ amount, currency })
2561
+ }
2562
+
2563
+ get amount(): number {
2564
+ return this.props.amount
2565
+ }
2566
+
2567
+ get currency(): string {
2568
+ return this.props.currency
2569
+ }
2570
+
2571
+ add(other: Money): Money {
2572
+ this.assertSameCurrency(other)
2573
+ return new Money(this.amount + other.amount, this.currency)
2574
+ }
2575
+
2576
+ subtract(other: Money): Money {
2577
+ this.assertSameCurrency(other)
2578
+ return new Money(this.amount - other.amount, this.currency)
2579
+ }
2580
+
2581
+ private assertSameCurrency(other: Money): void {
2582
+ if (this.currency !== other.currency) {
2583
+ throw new Error('Cannot operate on different currencies')
2584
+ }
2585
+ }
2586
+ }
2587
+ `;
2588
+ }
2589
+ generateEmailValueObject() {
2590
+ return `/**
2591
+ * Email Value Object
2592
+ */
2593
+
2594
+ import { ValueObject } from '@gravito/enterprise'
2595
+
2596
+ interface EmailProps {
2597
+ value: string
2598
+ }
2599
+
2600
+ export class Email extends ValueObject<EmailProps> {
2601
+ private constructor(value: string) {
2602
+ super({ value: value.toLowerCase().trim() })
2603
+ }
2604
+
2605
+ static create(email: string): Email {
2606
+ if (!Email.isValid(email)) {
2607
+ throw new Error(\`Invalid email: \${email}\`)
2608
+ }
2609
+ return new Email(email)
2610
+ }
2611
+
2612
+ static isValid(email: string): boolean {
2613
+ return /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email)
2614
+ }
2615
+
2616
+ get value(): string {
2617
+ return this.props.value
2618
+ }
2619
+ }
2620
+ `;
2621
+ }
2622
+ generateEventDispatcher() {
2623
+ return `/**
2624
+ * Event Dispatcher
2625
+ */
2626
+
2627
+ import type { DomainEvent } from '@gravito/enterprise'
2628
+
2629
+ type EventHandler = (event: DomainEvent) => void | Promise<void>
2630
+
2631
+ export class EventDispatcher {
2632
+ private handlers: Map<string, EventHandler[]> = new Map()
2633
+
2634
+ subscribe(eventName: string, handler: EventHandler): void {
2635
+ const handlers = this.handlers.get(eventName) ?? []
2636
+ handlers.push(handler)
2637
+ this.handlers.set(eventName, handlers)
2638
+ }
2639
+
2640
+ async dispatch(event: DomainEvent): Promise<void> {
2641
+ const handlers = this.handlers.get(event.eventName) ?? []
2642
+ for (const handler of handlers) {
2643
+ await handler(event)
2644
+ }
2645
+ }
2646
+
2647
+ async dispatchAll(events: DomainEvent[]): Promise<void> {
2648
+ for (const event of events) {
2649
+ await this.dispatch(event)
2650
+ }
2651
+ }
2652
+ }
2653
+ `;
2654
+ }
2655
+ generateExceptionHandler() {
2656
+ return `/**
2657
+ * Exception Handler
2658
+ */
2659
+
2660
+ export function report(error: unknown): void {
2661
+ console.error('[Exception]', error)
2662
+ }
2663
+ `;
2664
+ }
2665
+ };
2666
+
2667
+ // src/generators/DddGenerator.ts
2668
+ var DddGenerator = class extends BaseGenerator {
2669
+ moduleGenerator;
2670
+ sharedKernelGenerator;
2671
+ bootstrapGenerator;
2672
+ constructor(config) {
2673
+ super(config);
2674
+ this.moduleGenerator = new ModuleGenerator();
2675
+ this.sharedKernelGenerator = new SharedKernelGenerator();
2676
+ this.bootstrapGenerator = new BootstrapGenerator();
2677
+ }
2678
+ get architectureType() {
2679
+ return "ddd";
2680
+ }
2681
+ get displayName() {
2682
+ return "Domain-Driven Design (DDD)";
2683
+ }
2684
+ get description() {
2685
+ return "Full DDD with Bounded Contexts, Aggregates, and Event-Driven patterns";
2686
+ }
2687
+ getDirectoryStructure(context) {
2688
+ return [
2689
+ this.bootstrapGenerator.generateConfigDirectory(context),
2690
+ {
2691
+ type: "directory",
2692
+ name: "src",
2693
+ children: [
2694
+ // Bootstrap - Application startup and configuration
2695
+ this.bootstrapGenerator.generate(context),
2696
+ // Shared - Cross-module shared components
2697
+ this.sharedKernelGenerator.generate(),
2698
+ // Modules - Bounded Contexts
2699
+ {
2700
+ type: "directory",
2701
+ name: "Modules",
2702
+ children: [
2703
+ this.moduleGenerator.generate("Ordering", context),
2704
+ this.moduleGenerator.generate("Catalog", context)
2705
+ ]
2706
+ },
2707
+ {
2708
+ type: "file",
2709
+ name: "main.ts",
2710
+ content: this.bootstrapGenerator.generateMainEntry(context)
2711
+ }
2712
+ ]
2713
+ },
2714
+ {
2715
+ type: "directory",
2716
+ name: "tests",
2717
+ children: [
2718
+ {
2719
+ type: "directory",
2720
+ name: "Modules",
2721
+ children: [
2722
+ {
2723
+ type: "directory",
2724
+ name: "Ordering",
2725
+ children: [
2726
+ {
2727
+ type: "directory",
2728
+ name: "Unit",
2729
+ children: [{ type: "file", name: ".gitkeep", content: "" }]
2730
+ },
2731
+ {
2732
+ type: "directory",
2733
+ name: "Integration",
2734
+ children: [{ type: "file", name: ".gitkeep", content: "" }]
2735
+ }
2736
+ ]
2737
+ },
2738
+ {
2739
+ type: "directory",
2740
+ name: "Catalog",
2741
+ children: [
2742
+ {
2743
+ type: "directory",
2744
+ name: "Unit",
2745
+ children: [{ type: "file", name: ".gitkeep", content: "" }]
2746
+ }
2747
+ ]
2748
+ }
2749
+ ]
2750
+ },
2751
+ {
2752
+ type: "directory",
2753
+ name: "Shared",
2754
+ children: [{ type: "file", name: ".gitkeep", content: "" }]
2755
+ }
2756
+ ]
2757
+ }
2758
+ ];
2759
+ }
2760
+ /**
2761
+ * Override package.json for DDD architecture (uses main.ts instead of bootstrap.ts)
2762
+ */
2763
+ generatePackageJson(context) {
2764
+ const pkg = {
2765
+ name: context.nameKebabCase,
2766
+ version: "0.1.0",
2767
+ type: "module",
2768
+ scripts: {
2769
+ dev: "bun run --watch src/main.ts",
2770
+ build: "bun build ./src/main.ts --outdir ./dist --target bun",
2771
+ start: "bun run dist/main.js",
2772
+ test: "bun test",
2773
+ typecheck: "bun tsc --noEmit",
2774
+ check: "bun run typecheck && bun run test",
2775
+ "check:deps": "bun run scripts/check-dependencies.ts",
2776
+ validate: "bun run check && bun run check:deps",
2777
+ precommit: "bun run validate",
2778
+ "docker:build": `docker build -t ${context.nameKebabCase} .`,
2779
+ "docker:run": `docker run -it -p 3000:3000 ${context.nameKebabCase}`
2780
+ },
2781
+ dependencies: {
2782
+ "@gravito/core": "^1.0.0-beta.5",
2783
+ "@gravito/enterprise": "workspace:*"
2784
+ },
2785
+ devDependencies: {
2786
+ "bun-types": "latest",
2787
+ typescript: "^5.9.3"
2788
+ }
2789
+ };
2790
+ return JSON.stringify(pkg, null, 2);
3134
2791
  }
3135
2792
  generateArchitectureDoc(context) {
3136
2793
  return `# ${context.name} - DDD Architecture Guide
@@ -3359,123 +3016,13 @@ var EnterpriseMvcGenerator = class extends BaseGenerator {
3359
3016
  ];
3360
3017
  }
3361
3018
  // ─────────────────────────────────────────────────────────────
3362
- // Config Generators
3019
+ // Config Generators (using shared ConfigGenerator)
3363
3020
  // ─────────────────────────────────────────────────────────────
3364
3021
  generateAppConfig(context) {
3365
- return `/**
3366
- * Application Configuration
3367
- */
3368
- export default {
3369
- /**
3370
- * Application name
3371
- */
3372
- name: process.env.APP_NAME ?? '${context.name}',
3373
-
3374
- /**
3375
- * Application environment
3376
- */
3377
- env: process.env.APP_ENV ?? 'development',
3378
-
3379
- /**
3380
- * Application port
3381
- */
3382
- port: Number.parseInt(process.env.PORT ?? '3000', 10),
3383
-
3384
- /**
3385
- * View directory
3386
- */
3387
- VIEW_DIR: process.env.VIEW_DIR ?? 'src/views',
3388
-
3389
- /**
3390
- * Debug mode
3391
- */
3392
- debug: process.env.APP_DEBUG === 'true',
3393
-
3394
- /**
3395
- * Application URL
3396
- */
3397
- url: process.env.APP_URL ?? 'http://localhost:3000',
3398
-
3399
- /**
3400
- * Timezone
3401
- */
3402
- timezone: 'UTC',
3403
-
3404
- /**
3405
- * Locale
3406
- */
3407
- locale: 'en',
3408
-
3409
- /**
3410
- * Fallback locale
3411
- */
3412
- fallbackLocale: 'en',
3413
-
3414
- /**
3415
- * Encryption key
3416
- */
3417
- key: process.env.APP_KEY,
3418
-
3419
- /**
3420
- * Service providers to register
3421
- */
3422
- providers: [
3423
- // Framework providers
3424
- // 'RouteServiceProvider',
3425
-
3426
- // Application providers
3427
- // 'AppServiceProvider',
3428
- ],
3429
- }
3430
- `;
3022
+ return ConfigGenerator.generateDetailedAppConfig(context);
3431
3023
  }
3432
3024
  generateDatabaseConfig() {
3433
- return `/**
3434
- * Database Configuration
3435
- */
3436
- export default {
3437
- /**
3438
- * Default connection
3439
- */
3440
- default: process.env.DB_CONNECTION ?? 'sqlite',
3441
-
3442
- /**
3443
- * Database connections
3444
- */
3445
- connections: {
3446
- sqlite: {
3447
- driver: 'sqlite',
3448
- database: process.env.DB_DATABASE ?? 'database/database.sqlite',
3449
- },
3450
-
3451
- mysql: {
3452
- driver: 'mysql',
3453
- host: process.env.DB_HOST ?? 'localhost',
3454
- port: Number(process.env.DB_PORT ?? 3306),
3455
- database: process.env.DB_DATABASE ?? 'forge',
3456
- username: process.env.DB_USERNAME ?? 'forge',
3457
- password: process.env.DB_PASSWORD ?? '',
3458
- },
3459
-
3460
- postgres: {
3461
- driver: 'postgres',
3462
- host: process.env.DB_HOST ?? 'localhost',
3463
- port: Number(process.env.DB_PORT ?? 5432),
3464
- database: process.env.DB_DATABASE ?? 'forge',
3465
- username: process.env.DB_USERNAME ?? 'forge',
3466
- password: process.env.DB_PASSWORD ?? '',
3467
- },
3468
- },
3469
-
3470
- /**
3471
- * Migration settings
3472
- */
3473
- migrations: {
3474
- table: 'migrations',
3475
- path: 'database/migrations',
3476
- },
3477
- }
3478
- `;
3025
+ return ConfigGenerator.generateDetailedDatabaseConfig();
3479
3026
  }
3480
3027
  generateAuthConfig() {
3481
3028
  return `/**
@@ -3758,33 +3305,6 @@ export class AppServiceProvider extends ServiceProvider {
3758
3305
  console.log('${context.name} application booted!')
3759
3306
  }
3760
3307
  }
3761
- `;
3762
- }
3763
- generateRouteServiceProvider(_context) {
3764
- return `/**
3765
- * Route Service Provider
3766
- *
3767
- * Configures and registers application routes.
3768
- */
3769
-
3770
- import { ServiceProvider, type Container, type PlanetCore } from '@gravito/core'
3771
- import { registerRoutes } from '../routes'
3772
-
3773
- export class RouteServiceProvider extends ServiceProvider {
3774
- /**
3775
- * Register any application services.
3776
- */
3777
- register(_container: Container): void {
3778
- // Routes are registered in boot
3779
- }
3780
-
3781
- /**
3782
- * Bootstrap any application services.
3783
- */
3784
- boot(core: PlanetCore): void {
3785
- registerRoutes(core.router)
3786
- }
3787
- }
3788
3308
  `;
3789
3309
  }
3790
3310
  // ─────────────────────────────────────────────────────────────
@@ -4455,11 +3975,9 @@ export class ${name}ServiceProvider extends ServiceProvider {
4455
3975
  module: "dist/index.mjs",
4456
3976
  types: "dist/index.d.ts",
4457
3977
  scripts: {
4458
- build: "tsup src/index.ts --format cjs,esm --dts",
3978
+ build: "tsup src/index.ts --format esm --dts",
4459
3979
  test: "bun test",
4460
- typecheck: "tsc --noEmit",
4461
- check: "bun run typecheck && bun run test",
4462
- validate: "bun run check"
3980
+ typecheck: "bun tsc --noEmit"
4463
3981
  },
4464
3982
  dependencies: {
4465
3983
  "@gravito/core": depVersion,
@@ -4468,8 +3986,12 @@ export class ${name}ServiceProvider extends ServiceProvider {
4468
3986
  "@gravito/stasis": depVersion
4469
3987
  },
4470
3988
  devDependencies: {
4471
- tsup: "^8.0.0",
4472
- typescript: "^5.0.0"
3989
+ "bun-types": "latest",
3990
+ typescript: "^5.9.3",
3991
+ tsup: "^8.0.0"
3992
+ },
3993
+ peerDependencies: {
3994
+ "@gravito/core": ">=1.0.0"
4473
3995
  }
4474
3996
  };
4475
3997
  return JSON.stringify(pkg, null, 2);
@@ -4619,7 +4141,7 @@ var ProfileResolver = class _ProfileResolver {
4619
4141
  };
4620
4142
 
4621
4143
  // src/Scaffold.ts
4622
- var import_node_path3 = __toESM(require("path"), 1);
4144
+ var import_node_path6 = __toESM(require("path"), 1);
4623
4145
 
4624
4146
  // src/generators/ActionDomainGenerator.ts
4625
4147
  var ActionDomainGenerator = class extends BaseGenerator {
@@ -4826,6 +4348,9 @@ var ActionDomainGenerator = class extends BaseGenerator {
4826
4348
 
4827
4349
  import { Model, column } from '@gravito/atlas'
4828
4350
 
4351
+ /**
4352
+ * Represents a user in the system.
4353
+ */
4829
4354
  export class User extends Model {
4830
4355
  static table = 'users'
4831
4356
 
@@ -5110,7 +4635,7 @@ Created with \u2764\uFE0F using Gravito Framework
5110
4635
  build: "bun build ./src/bootstrap.ts --outdir ./dist --target bun",
5111
4636
  start: "bun run dist/bootstrap.js",
5112
4637
  test: "bun test",
5113
- typecheck: "tsc --noEmit",
4638
+ typecheck: "bun tsc --noEmit",
5114
4639
  check: "bun run typecheck && bun run test",
5115
4640
  "check:deps": "bun run scripts/check-dependencies.ts",
5116
4641
  validate: "bun run check && bun run check:deps",
@@ -5124,7 +4649,7 @@ Created with \u2764\uFE0F using Gravito Framework
5124
4649
  },
5125
4650
  devDependencies: {
5126
4651
  "bun-types": "latest",
5127
- typescript: "^5.0.0"
4652
+ typescript: "^5.9.3"
5128
4653
  }
5129
4654
  };
5130
4655
  return JSON.stringify(pkg, null, 2);
@@ -5132,6 +4657,7 @@ Created with \u2764\uFE0F using Gravito Framework
5132
4657
  };
5133
4658
 
5134
4659
  // src/generators/StandaloneEngineGenerator.ts
4660
+ var import_node_path5 = __toESM(require("path"), 1);
5135
4661
  var StandaloneEngineGenerator = class extends BaseGenerator {
5136
4662
  get architectureType() {
5137
4663
  return "standalone-engine";
@@ -5163,9 +4689,16 @@ var StandaloneEngineGenerator = class extends BaseGenerator {
5163
4689
  ];
5164
4690
  }
5165
4691
  async generateCommonFiles(context) {
4692
+ const commonDir = import_node_path5.default.resolve(this.config.templatesDir, "common");
4693
+ const extendedContext = { ...context };
5166
4694
  await this.writeFile(context.targetDir, "package.json", this.generatePackageJson(context));
5167
- await this.writeFile(context.targetDir, "tsconfig.json", this.generateTsConfig());
5168
- await this.writeFile(context.targetDir, ".gitignore", this.generateGitignore());
4695
+ await this.generateFileFromTemplate(
4696
+ commonDir,
4697
+ "tsconfig.json.hbs",
4698
+ "tsconfig.json",
4699
+ extendedContext
4700
+ );
4701
+ await this.generateFileFromTemplate(commonDir, "gitignore.hbs", ".gitignore", extendedContext);
5169
4702
  }
5170
4703
  generatePackageJson(context) {
5171
4704
  const pkg = {
@@ -5220,23 +4753,17 @@ A high-performance web application powered by Gravito Engine.
5220
4753
 
5221
4754
  ### Install Dependencies
5222
4755
 
5223
- \`\`\`bash
5224
- bun install
5225
- \`\`\`
5226
-
4756
+ bun install
4757
+
5227
4758
  ### Run Development Server
5228
4759
 
5229
- \`\`\`bash
5230
- bun run dev
5231
- \`\`\`
5232
-
4760
+ bun run dev
4761
+
5233
4762
  ### Production Build
5234
4763
 
5235
- \`\`\`bash
5236
- bun run build
4764
+ bun run build
5237
4765
  bun start
5238
- \`\`\`
5239
- `;
4766
+ `;
5240
4767
  }
5241
4768
  };
5242
4769
 
@@ -5245,11 +4772,14 @@ var Scaffold = class {
5245
4772
  templatesDir;
5246
4773
  verbose;
5247
4774
  constructor(options = {}) {
5248
- this.templatesDir = options.templatesDir ?? import_node_path3.default.resolve(__dirname, "../templates");
4775
+ this.templatesDir = options.templatesDir ?? import_node_path6.default.resolve(__dirname, "../templates");
5249
4776
  this.verbose = options.verbose ?? false;
5250
4777
  }
5251
4778
  /**
5252
- * Get all available architecture types.
4779
+ * Returns a list of all architectural patterns supported by the engine,
4780
+ * along with human-readable names and descriptions.
4781
+ *
4782
+ * @returns {Array<{type: ArchitectureType, name: string, description: string}>}
5253
4783
  */
5254
4784
  getArchitectureTypes() {
5255
4785
  return [
@@ -5286,11 +4816,16 @@ var Scaffold = class {
5286
4816
  ];
5287
4817
  }
5288
4818
  /**
5289
- * Create a new project scaffold.
4819
+ * Orchestrates the complete project generation lifecycle.
4820
+ * This includes directory creation, file layout, profile resolution,
4821
+ * dependency mapping, and optional post-install hooks.
4822
+ *
4823
+ * @param {ScaffoldOptions} options - Detailed configuration for the new project.
4824
+ * @returns {Promise<ScaffoldResult>}
5290
4825
  */
5291
4826
  async create(options) {
5292
4827
  const generator = this.createGenerator(options.architecture);
5293
- const fs3 = await import("fs/promises");
4828
+ const fs5 = await import("fs/promises");
5294
4829
  const profileResolver = new ProfileResolver();
5295
4830
  const profileConfig = profileResolver.resolve(options.profile, options.features);
5296
4831
  const context = BaseGenerator.createContext(
@@ -5317,8 +4852,8 @@ var Scaffold = class {
5317
4852
  // Default template for now, should come from options if applicable
5318
4853
  "1.0.0"
5319
4854
  );
5320
- const lockPath = import_node_path3.default.resolve(options.targetDir, "gravito.lock.json");
5321
- await fs3.writeFile(lockPath, lockContent, "utf-8");
4855
+ const lockPath = import_node_path6.default.resolve(options.targetDir, "gravito.lock.json");
4856
+ await fs5.writeFile(lockPath, lockContent, "utf-8");
5322
4857
  filesCreated.push(lockPath);
5323
4858
  return {
5324
4859
  success: true,