@ooneex/cli 1.16.0 → 1.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.js +1602 -1277
  2. package/dist/index.js.map +16 -10
  3. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -4878,11 +4878,197 @@ var require_pluralize = __commonJS((exports, module) => {
4878
4878
  // src/index.ts
4879
4879
  import { run } from "@ooneex/command";
4880
4880
 
4881
- // src/commands/CompletionZshCommand.ts
4882
- import { homedir } from "os";
4881
+ // src/commands/AppBuildCommand.ts
4883
4882
  import { join } from "path";
4884
4883
  import { decorator } from "@ooneex/command";
4885
4884
  import { TerminalLogger } from "@ooneex/logger";
4885
+ class AppBuildCommand {
4886
+ getName() {
4887
+ return "app:build";
4888
+ }
4889
+ getDescription() {
4890
+ return "Build the application";
4891
+ }
4892
+ async run() {
4893
+ const logger = new TerminalLogger;
4894
+ const appDir = join(process.cwd(), "modules", "app");
4895
+ const packageJsonFile = Bun.file(join(appDir, "package.json"));
4896
+ if (!await packageJsonFile.exists()) {
4897
+ logger.error("Module app not found", undefined, {
4898
+ showTimestamp: false,
4899
+ showArrow: false,
4900
+ useSymbol: true
4901
+ });
4902
+ return;
4903
+ }
4904
+ const packageJson = await packageJsonFile.json();
4905
+ const name = packageJson.name ?? "app";
4906
+ logger.info(`Building ${name}...`, undefined, {
4907
+ showTimestamp: false,
4908
+ showArrow: false,
4909
+ useSymbol: true
4910
+ });
4911
+ const proc = Bun.spawn(["bun", "build", "./src/index.ts", "--outdir", "./dist", "--target", "bun"], {
4912
+ cwd: appDir,
4913
+ stdout: "inherit",
4914
+ stderr: "inherit"
4915
+ });
4916
+ const exitCode = await proc.exited;
4917
+ if (exitCode === 0) {
4918
+ logger.success(`Build completed for ${name}`, undefined, {
4919
+ showTimestamp: false,
4920
+ showArrow: false,
4921
+ useSymbol: true
4922
+ });
4923
+ } else {
4924
+ logger.error(`Build failed for ${name} (exit code: ${exitCode})`, undefined, {
4925
+ showTimestamp: false,
4926
+ showArrow: false,
4927
+ useSymbol: true
4928
+ });
4929
+ }
4930
+ }
4931
+ }
4932
+ AppBuildCommand = __legacyDecorateClassTS([
4933
+ decorator.command()
4934
+ ], AppBuildCommand);
4935
+ // src/commands/AppStartCommand.ts
4936
+ import { join as join2 } from "path";
4937
+ import { decorator as decorator2 } from "@ooneex/command";
4938
+ import { TerminalLogger as TerminalLogger2 } from "@ooneex/logger";
4939
+ class AppStartCommand {
4940
+ getName() {
4941
+ return "app:start";
4942
+ }
4943
+ getDescription() {
4944
+ return "Start the application";
4945
+ }
4946
+ async run() {
4947
+ const logger = new TerminalLogger2;
4948
+ const appDir = join2(process.cwd(), "modules", "app");
4949
+ const packageJsonFile = Bun.file(join2(appDir, "package.json"));
4950
+ if (!await packageJsonFile.exists()) {
4951
+ logger.error("Module app not found", undefined, {
4952
+ showTimestamp: false,
4953
+ showArrow: false,
4954
+ useSymbol: true
4955
+ });
4956
+ return;
4957
+ }
4958
+ const packageJson = await packageJsonFile.json();
4959
+ const name = packageJson.name ?? "app";
4960
+ const composeFile = join2(appDir, "docker-compose.yml");
4961
+ const composeExists = await Bun.file(composeFile).exists();
4962
+ if (composeExists) {
4963
+ logger.info(`Starting Docker services for ${name}...`, undefined, {
4964
+ showTimestamp: false,
4965
+ showArrow: false,
4966
+ useSymbol: true
4967
+ });
4968
+ const docker = Bun.spawn(["docker", "compose", "up", "-d"], {
4969
+ cwd: appDir,
4970
+ stdout: "inherit",
4971
+ stderr: "inherit"
4972
+ });
4973
+ const dockerExitCode = await docker.exited;
4974
+ if (dockerExitCode === 0) {
4975
+ logger.success(`Docker services started for ${name}`, undefined, {
4976
+ showTimestamp: false,
4977
+ showArrow: false,
4978
+ useSymbol: true
4979
+ });
4980
+ } else {
4981
+ logger.error(`Docker services failed for ${name} (exit code: ${dockerExitCode})`, undefined, {
4982
+ showTimestamp: false,
4983
+ showArrow: false,
4984
+ useSymbol: true
4985
+ });
4986
+ return;
4987
+ }
4988
+ }
4989
+ logger.info(`Starting ${name}...`, undefined, {
4990
+ showTimestamp: false,
4991
+ showArrow: false,
4992
+ useSymbol: true
4993
+ });
4994
+ const entryPoint = join2(appDir, "src", "index.ts");
4995
+ const app = Bun.spawn(["bun", "--hot", "run", entryPoint], {
4996
+ cwd: process.cwd(),
4997
+ stdout: "inherit",
4998
+ stderr: "inherit"
4999
+ });
5000
+ const appExitCode = await app.exited;
5001
+ if (appExitCode !== 0) {
5002
+ logger.error(`Application exited with code ${appExitCode}`, undefined, {
5003
+ showTimestamp: false,
5004
+ showArrow: false,
5005
+ useSymbol: true
5006
+ });
5007
+ }
5008
+ }
5009
+ }
5010
+ AppStartCommand = __legacyDecorateClassTS([
5011
+ decorator2.command()
5012
+ ], AppStartCommand);
5013
+ // src/commands/AppStopCommand.ts
5014
+ import { join as join3 } from "path";
5015
+ import { decorator as decorator3 } from "@ooneex/command";
5016
+ import { TerminalLogger as TerminalLogger3 } from "@ooneex/logger";
5017
+ class AppStopCommand {
5018
+ getName() {
5019
+ return "app:stop";
5020
+ }
5021
+ getDescription() {
5022
+ return "Stop the application";
5023
+ }
5024
+ async run() {
5025
+ const logger = new TerminalLogger3;
5026
+ const appDir = join3(process.cwd(), "modules", "app");
5027
+ const packageJsonFile = Bun.file(join3(appDir, "package.json"));
5028
+ if (!await packageJsonFile.exists()) {
5029
+ logger.error("Module app not found", undefined, {
5030
+ showTimestamp: false,
5031
+ showArrow: false,
5032
+ useSymbol: true
5033
+ });
5034
+ return;
5035
+ }
5036
+ const packageJson = await packageJsonFile.json();
5037
+ const name = packageJson.name ?? "app";
5038
+ logger.info(`Stopping Docker services for ${name}...`, undefined, {
5039
+ showTimestamp: false,
5040
+ showArrow: false,
5041
+ useSymbol: true
5042
+ });
5043
+ const proc = Bun.spawn(["docker", "compose", "down"], {
5044
+ cwd: appDir,
5045
+ stdout: "inherit",
5046
+ stderr: "inherit"
5047
+ });
5048
+ const exitCode = await proc.exited;
5049
+ if (exitCode === 0) {
5050
+ logger.success(`Docker services stopped for ${name}`, undefined, {
5051
+ showTimestamp: false,
5052
+ showArrow: false,
5053
+ useSymbol: true
5054
+ });
5055
+ } else {
5056
+ logger.error(`Failed to stop Docker services for ${name} (exit code: ${exitCode})`, undefined, {
5057
+ showTimestamp: false,
5058
+ showArrow: false,
5059
+ useSymbol: true
5060
+ });
5061
+ }
5062
+ }
5063
+ }
5064
+ AppStopCommand = __legacyDecorateClassTS([
5065
+ decorator3.command()
5066
+ ], AppStopCommand);
5067
+ // src/commands/CompletionZshCommand.ts
5068
+ import { homedir } from "os";
5069
+ import { join as join4 } from "path";
5070
+ import { decorator as decorator4 } from "@ooneex/command";
5071
+ import { TerminalLogger as TerminalLogger4 } from "@ooneex/logger";
4886
5072
 
4887
5073
  // src/templates/completions/_oo.txt
4888
5074
  var _oo_default = `#compdef oo ooneex
@@ -4898,7 +5084,11 @@ _oo_modules() {
4898
5084
  _oo() {
4899
5085
  local -a commands
4900
5086
  commands=(
5087
+ 'app\\:build:Build the application'
5088
+ 'app\\:start:Start the application'
5089
+ 'app\\:stop:Stop the application'
4901
5090
  'completion\\:zsh:Install Zsh completion for oo command'
5091
+ 'help:Show available commands'
4902
5092
  'make\\:ai:Generate a new AI class'
4903
5093
  'make\\:analytics:Generate a new analytics class'
4904
5094
  'make\\:app:Generate a new application'
@@ -4914,6 +5104,7 @@ _oo() {
4914
5104
  'make\\:mailer:Generate a new mailer class'
4915
5105
  'make\\:middleware:Generate a new middleware class'
4916
5106
  'make\\:migration:Generate a new migration file'
5107
+ 'migration\\:up:Run migrations for all modules'
4917
5108
  'make\\:module:Generate a new module'
4918
5109
  'make\\:permission:Generate a new permission class'
4919
5110
  'make\\:pubsub:Generate a new PubSub event class'
@@ -4934,6 +5125,7 @@ _oo() {
4934
5125
  'make\\:resource\\:user:Generate user resource (entity, migration, repository)'
4935
5126
  'make\\:resource\\:video:Generate video resource (entity, migration, repository)'
4936
5127
  'make\\:seed:Generate a new seed file'
5128
+ 'seed\\:run:Run seeds for all modules'
4937
5129
  'make\\:service:Generate a new service class'
4938
5130
  'make\\:storage:Generate a new storage class'
4939
5131
  'make\\:vector-database:Generate a new vector database class'
@@ -5005,7 +5197,7 @@ _oo() {
5005
5197
  '--name=[Name of the resource]:name' \\
5006
5198
  '--module=[Module name]:module:_oo_modules'
5007
5199
  ;;
5008
- make:release|make:resource:book|make:resource:calendar-event|make:resource:category|make:resource:color|make:resource:discount|make:resource:folder|make:resource:image|make:resource:note|make:resource:status|make:resource:tag|make:resource:task|make:resource:topic|make:resource:user|make:resource:video|make:claude:skill|completion:zsh)
5200
+ app:build|app:start|app:stop|help|make:release|make:resource:book|make:resource:calendar-event|make:resource:category|make:resource:color|make:resource:discount|make:resource:folder|make:resource:image|make:resource:note|make:resource:status|make:resource:tag|make:resource:task|make:resource:topic|make:resource:user|make:resource:video|make:claude:skill|migration:up|seed:run|completion:zsh)
5009
5201
  ;;
5010
5202
  esac
5011
5203
  ;;
@@ -5029,7 +5221,11 @@ _ooneex_modules() {
5029
5221
  _ooneex() {
5030
5222
  local -a commands
5031
5223
  commands=(
5224
+ 'app\\:build:Build the application'
5225
+ 'app\\:start:Start the application'
5226
+ 'app\\:stop:Stop the application'
5032
5227
  'completion\\:zsh:Install Zsh completion for oo command'
5228
+ 'help:Show available commands'
5033
5229
  'make\\:ai:Generate a new AI class'
5034
5230
  'make\\:analytics:Generate a new analytics class'
5035
5231
  'make\\:app:Generate a new application'
@@ -5045,6 +5241,7 @@ _ooneex() {
5045
5241
  'make\\:mailer:Generate a new mailer class'
5046
5242
  'make\\:middleware:Generate a new middleware class'
5047
5243
  'make\\:migration:Generate a new migration file'
5244
+ 'migration\\:up:Run migrations for all modules'
5048
5245
  'make\\:module:Generate a new module'
5049
5246
  'make\\:permission:Generate a new permission class'
5050
5247
  'make\\:pubsub:Generate a new PubSub event class'
@@ -5065,6 +5262,7 @@ _ooneex() {
5065
5262
  'make\\:resource\\:user:Generate user resource (entity, migration, repository)'
5066
5263
  'make\\:resource\\:video:Generate video resource (entity, migration, repository)'
5067
5264
  'make\\:seed:Generate a new seed file'
5265
+ 'seed\\:run:Run seeds for all modules'
5068
5266
  'make\\:service:Generate a new service class'
5069
5267
  'make\\:storage:Generate a new storage class'
5070
5268
  'make\\:vector-database:Generate a new vector database class'
@@ -5136,7 +5334,7 @@ _ooneex() {
5136
5334
  '--name=[Name of the resource]:name' \\
5137
5335
  '--module=[Module name]:module:_ooneex_modules'
5138
5336
  ;;
5139
- make:release|make:resource:book|make:resource:calendar-event|make:resource:category|make:resource:color|make:resource:discount|make:resource:folder|make:resource:image|make:resource:note|make:resource:status|make:resource:tag|make:resource:task|make:resource:topic|make:resource:user|make:resource:video|make:claude:skill|completion:zsh)
5337
+ app:build|app:start|app:stop|help|make:release|make:resource:book|make:resource:calendar-event|make:resource:category|make:resource:color|make:resource:discount|make:resource:folder|make:resource:image|make:resource:note|make:resource:status|make:resource:tag|make:resource:task|make:resource:topic|make:resource:user|make:resource:video|make:claude:skill|migration:up|seed:run|completion:zsh)
5140
5338
  ;;
5141
5339
  esac
5142
5340
  ;;
@@ -5155,12 +5353,12 @@ class CompletionZshCommand {
5155
5353
  return "Install Zsh completion for oo command";
5156
5354
  }
5157
5355
  async run() {
5158
- const completionDir = join(homedir(), ".zsh");
5159
- const ooFilePath = join(completionDir, "_oo");
5356
+ const completionDir = join4(homedir(), ".zsh");
5357
+ const ooFilePath = join4(completionDir, "_oo");
5160
5358
  await Bun.write(ooFilePath, _oo_default);
5161
- const ooneexFilePath = join(completionDir, "_ooneex");
5359
+ const ooneexFilePath = join4(completionDir, "_ooneex");
5162
5360
  await Bun.write(ooneexFilePath, _ooneex_default);
5163
- const logger = new TerminalLogger;
5361
+ const logger = new TerminalLogger4;
5164
5362
  logger.success(`${ooFilePath} created successfully`, undefined, {
5165
5363
  showTimestamp: false,
5166
5364
  showArrow: false,
@@ -5181,12 +5379,42 @@ class CompletionZshCommand {
5181
5379
  }
5182
5380
  }
5183
5381
  CompletionZshCommand = __legacyDecorateClassTS([
5184
- decorator.command()
5382
+ decorator4.command()
5185
5383
  ], CompletionZshCommand);
5384
+ // src/commands/HelpCommand.ts
5385
+ import { COMMANDS_CONTAINER, decorator as decorator5 } from "@ooneex/command";
5386
+ class HelpCommand {
5387
+ getName() {
5388
+ return "help";
5389
+ }
5390
+ getDescription() {
5391
+ return "Show available commands";
5392
+ }
5393
+ run() {
5394
+ const commands = [];
5395
+ for (const CommandClass of COMMANDS_CONTAINER) {
5396
+ const command = new CommandClass;
5397
+ commands.push({ name: command.getName(), description: command.getDescription() });
5398
+ }
5399
+ commands.sort((a, b) => a.name.localeCompare(b.name));
5400
+ const maxNameLength = Math.max(...commands.map((c) => c.name.length));
5401
+ const lines = ["", "Available commands:", ""];
5402
+ for (const { name, description } of commands) {
5403
+ lines.push(` ${name.padEnd(maxNameLength + 2)}${description}`);
5404
+ }
5405
+ lines.push("");
5406
+ process.stdout.write(`${lines.join(`
5407
+ `)}
5408
+ `);
5409
+ }
5410
+ }
5411
+ HelpCommand = __legacyDecorateClassTS([
5412
+ decorator5.command()
5413
+ ], HelpCommand);
5186
5414
  // src/commands/MakeAiCommand.ts
5187
- import { join as join4 } from "path";
5188
- import { decorator as decorator3 } from "@ooneex/command";
5189
- import { TerminalLogger as TerminalLogger3 } from "@ooneex/logger";
5415
+ import { join as join7 } from "path";
5416
+ import { decorator as decorator7 } from "@ooneex/command";
5417
+ import { TerminalLogger as TerminalLogger6 } from "@ooneex/logger";
5190
5418
  import { toPascalCase as toPascalCase2 } from "@ooneex/utils";
5191
5419
 
5192
5420
  // src/prompts/askName.ts
@@ -5283,12 +5511,12 @@ export class {{NAME}}Ai implements IAiChat<OpenAiConfigType> {
5283
5511
  `;
5284
5512
 
5285
5513
  // src/utils.ts
5286
- import { join as join3 } from "path";
5514
+ import { join as join6 } from "path";
5287
5515
 
5288
5516
  // src/commands/MakeModuleCommand.ts
5289
- import { join as join2 } from "path";
5290
- import { decorator as decorator2 } from "@ooneex/command";
5291
- import { TerminalLogger as TerminalLogger2 } from "@ooneex/logger";
5517
+ import { join as join5 } from "path";
5518
+ import { decorator as decorator6 } from "@ooneex/command";
5519
+ import { TerminalLogger as TerminalLogger5 } from "@ooneex/logger";
5292
5520
  import { toKebabCase, toPascalCase } from "@ooneex/utils";
5293
5521
 
5294
5522
  // src/templates/module/command.run.txt
@@ -5436,53 +5664,53 @@ class MakeModuleCommand {
5436
5664
  }
5437
5665
  const pascalName = toPascalCase(name).replace(/Module$/, "");
5438
5666
  const kebabName = toKebabCase(pascalName);
5439
- const moduleDir = join2(cwd, "modules", kebabName);
5440
- const srcDir = join2(moduleDir, "src");
5441
- const testsDir = join2(moduleDir, "tests");
5667
+ const moduleDir = join5(cwd, "modules", kebabName);
5668
+ const srcDir = join5(moduleDir, "src");
5669
+ const testsDir = join5(moduleDir, "tests");
5442
5670
  const moduleContent = module_default.replace(/{{NAME}}/g, pascalName);
5443
5671
  const packageContent = package_default.replace(/{{NAME}}/g, kebabName);
5444
5672
  const testContent = test_default.replace(/{{NAME}}/g, pascalName);
5445
- await Bun.write(join2(srcDir, `${pascalName}Module.ts`), moduleContent);
5673
+ await Bun.write(join5(srcDir, `${pascalName}Module.ts`), moduleContent);
5446
5674
  if (!skipMigrations) {
5447
- await Bun.write(join2(srcDir, "migrations", "migrations.ts"), "");
5675
+ await Bun.write(join5(srcDir, "migrations", "migrations.ts"), "");
5448
5676
  }
5449
5677
  if (!skipSeeds) {
5450
- await Bun.write(join2(srcDir, "seeds", "seeds.ts"), "");
5678
+ await Bun.write(join5(srcDir, "seeds", "seeds.ts"), "");
5451
5679
  }
5452
5680
  if (!skipCommands) {
5453
- await Bun.write(join2(srcDir, "commands", "commands.ts"), "");
5454
- const binCommandRunPath = join2(moduleDir, "bin", "command", "run.ts");
5681
+ await Bun.write(join5(srcDir, "commands", "commands.ts"), "");
5682
+ const binCommandRunPath = join5(moduleDir, "bin", "command", "run.ts");
5455
5683
  const binCommandRunFile = Bun.file(binCommandRunPath);
5456
5684
  if (!await binCommandRunFile.exists()) {
5457
5685
  await Bun.write(binCommandRunPath, command_run_default);
5458
5686
  }
5459
5687
  }
5460
- await Bun.write(join2(moduleDir, "package.json"), packageContent);
5461
- await Bun.write(join2(moduleDir, "tsconfig.json"), tsconfig_default);
5462
- await Bun.write(join2(testsDir, `${pascalName}Module.spec.ts`), testContent);
5688
+ await Bun.write(join5(moduleDir, "package.json"), packageContent);
5689
+ await Bun.write(join5(moduleDir, "tsconfig.json"), tsconfig_default);
5690
+ await Bun.write(join5(testsDir, `${pascalName}Module.spec.ts`), testContent);
5463
5691
  if (kebabName !== "app") {
5464
- const appModulePath = join2(cwd, "modules", "app", "src", "AppModule.ts");
5692
+ const appModulePath = join5(cwd, "modules", "app", "src", "AppModule.ts");
5465
5693
  if (await Bun.file(appModulePath).exists()) {
5466
5694
  await this.addToAppModule(appModulePath, pascalName, kebabName);
5467
5695
  }
5468
- const appTsconfigPath = join2(cwd, "modules", "app", "tsconfig.json");
5696
+ const appTsconfigPath = join5(cwd, "modules", "app", "tsconfig.json");
5469
5697
  if (await Bun.file(appTsconfigPath).exists()) {
5470
5698
  await this.addPathAlias(appTsconfigPath, kebabName);
5471
5699
  }
5472
5700
  }
5473
- const commitlintPath = join2(cwd, ".commitlintrc.ts");
5701
+ const commitlintPath = join5(cwd, ".commitlintrc.ts");
5474
5702
  if (await Bun.file(commitlintPath).exists()) {
5475
5703
  await this.addModuleScope(commitlintPath, kebabName);
5476
5704
  }
5477
5705
  if (!silent) {
5478
- const logger = new TerminalLogger2;
5706
+ const logger = new TerminalLogger5;
5479
5707
  logger.success(`modules/${kebabName} created successfully`, undefined, {
5480
5708
  showTimestamp: false,
5481
5709
  showArrow: false,
5482
5710
  useSymbol: true
5483
5711
  });
5484
5712
  }
5485
- const packageJsonPath = join2(process.cwd(), "package.json");
5713
+ const packageJsonPath = join5(process.cwd(), "package.json");
5486
5714
  const packageJson = await Bun.file(packageJsonPath).json();
5487
5715
  const deps = packageJson.dependencies ?? {};
5488
5716
  const devDeps = packageJson.devDependencies ?? {};
@@ -5497,13 +5725,13 @@ class MakeModuleCommand {
5497
5725
  }
5498
5726
  }
5499
5727
  MakeModuleCommand = __legacyDecorateClassTS([
5500
- decorator2.command()
5728
+ decorator6.command()
5501
5729
  ], MakeModuleCommand);
5502
5730
 
5503
5731
  // src/utils.ts
5504
5732
  var ensureModule = async (module) => {
5505
- const moduleDir = join3(process.cwd(), "modules", module);
5506
- const moduleDirExists = await Bun.file(join3(moduleDir, "package.json")).exists();
5733
+ const moduleDir = join6(process.cwd(), "modules", module);
5734
+ const moduleDirExists = await Bun.file(join6(moduleDir, "package.json")).exists();
5507
5735
  if (!moduleDirExists) {
5508
5736
  const makeModule = new MakeModuleCommand;
5509
5737
  await makeModule.run({ name: module, cwd: process.cwd(), silent: true });
@@ -5528,28 +5756,28 @@ class MakeAiCommand {
5528
5756
  await ensureModule(module);
5529
5757
  }
5530
5758
  const content = ai_default.replace(/{{NAME}}/g, name);
5531
- const base = module ? join4("modules", module) : ".";
5532
- const aiLocalDir = join4(base, "src", "ai");
5533
- const aiDir = join4(process.cwd(), aiLocalDir);
5534
- const filePath = join4(aiDir, `${name}Ai.ts`);
5759
+ const base = module ? join7("modules", module) : ".";
5760
+ const aiLocalDir = join7(base, "src", "ai");
5761
+ const aiDir = join7(process.cwd(), aiLocalDir);
5762
+ const filePath = join7(aiDir, `${name}Ai.ts`);
5535
5763
  await Bun.write(filePath, content);
5536
5764
  const testContent = ai_test_default.replace(/{{NAME}}/g, name);
5537
- const testsLocalDir = join4(base, "tests", "ai");
5538
- const testsDir = join4(process.cwd(), testsLocalDir);
5539
- const testFilePath = join4(testsDir, `${name}Ai.spec.ts`);
5765
+ const testsLocalDir = join7(base, "tests", "ai");
5766
+ const testsDir = join7(process.cwd(), testsLocalDir);
5767
+ const testFilePath = join7(testsDir, `${name}Ai.spec.ts`);
5540
5768
  await Bun.write(testFilePath, testContent);
5541
- const logger = new TerminalLogger3;
5542
- logger.success(`${join4(aiLocalDir, name)}Ai.ts created successfully`, undefined, {
5769
+ const logger = new TerminalLogger6;
5770
+ logger.success(`${join7(aiLocalDir, name)}Ai.ts created successfully`, undefined, {
5543
5771
  showTimestamp: false,
5544
5772
  showArrow: false,
5545
5773
  useSymbol: true
5546
5774
  });
5547
- logger.success(`${join4(testsLocalDir, name)}Ai.spec.ts created successfully`, undefined, {
5775
+ logger.success(`${join7(testsLocalDir, name)}Ai.spec.ts created successfully`, undefined, {
5548
5776
  showTimestamp: false,
5549
5777
  showArrow: false,
5550
5778
  useSymbol: true
5551
5779
  });
5552
- const packageJsonPath = join4(process.cwd(), "package.json");
5780
+ const packageJsonPath = join7(process.cwd(), "package.json");
5553
5781
  const packageJson = await Bun.file(packageJsonPath).json();
5554
5782
  const deps = packageJson.dependencies ?? {};
5555
5783
  const devDeps = packageJson.devDependencies ?? {};
@@ -5564,12 +5792,12 @@ class MakeAiCommand {
5564
5792
  }
5565
5793
  }
5566
5794
  MakeAiCommand = __legacyDecorateClassTS([
5567
- decorator3.command()
5795
+ decorator7.command()
5568
5796
  ], MakeAiCommand);
5569
5797
  // src/commands/MakeAnalyticsCommand.ts
5570
- import { join as join5 } from "path";
5571
- import { decorator as decorator4 } from "@ooneex/command";
5572
- import { TerminalLogger as TerminalLogger4 } from "@ooneex/logger";
5798
+ import { join as join8 } from "path";
5799
+ import { decorator as decorator8 } from "@ooneex/command";
5800
+ import { TerminalLogger as TerminalLogger7 } from "@ooneex/logger";
5573
5801
  import { toPascalCase as toPascalCase3 } from "@ooneex/utils";
5574
5802
 
5575
5803
  // src/templates/analytics.test.txt
@@ -5620,28 +5848,28 @@ class MakeAnalyticsCommand {
5620
5848
  if (module) {
5621
5849
  await ensureModule(module);
5622
5850
  }
5623
- const base = module ? join5("modules", module) : ".";
5624
- const analyticsLocalDir = join5(base, "src", "analytics");
5625
- const analyticsDir = join5(process.cwd(), analyticsLocalDir);
5626
- const filePath = join5(analyticsDir, `${name}Analytics.ts`);
5851
+ const base = module ? join8("modules", module) : ".";
5852
+ const analyticsLocalDir = join8(base, "src", "analytics");
5853
+ const analyticsDir = join8(process.cwd(), analyticsLocalDir);
5854
+ const filePath = join8(analyticsDir, `${name}Analytics.ts`);
5627
5855
  await Bun.write(filePath, content);
5628
5856
  const testContent = analytics_test_default.replace(/{{NAME}}/g, name);
5629
- const testsLocalDir = join5(base, "tests", "analytics");
5630
- const testsDir = join5(process.cwd(), testsLocalDir);
5631
- const testFilePath = join5(testsDir, `${name}Analytics.spec.ts`);
5857
+ const testsLocalDir = join8(base, "tests", "analytics");
5858
+ const testsDir = join8(process.cwd(), testsLocalDir);
5859
+ const testFilePath = join8(testsDir, `${name}Analytics.spec.ts`);
5632
5860
  await Bun.write(testFilePath, testContent);
5633
- const logger = new TerminalLogger4;
5634
- logger.success(`${join5(analyticsLocalDir, name)}Analytics.ts created successfully`, undefined, {
5861
+ const logger = new TerminalLogger7;
5862
+ logger.success(`${join8(analyticsLocalDir, name)}Analytics.ts created successfully`, undefined, {
5635
5863
  showTimestamp: false,
5636
5864
  showArrow: false,
5637
5865
  useSymbol: true
5638
5866
  });
5639
- logger.success(`${join5(testsLocalDir, name)}Analytics.spec.ts created successfully`, undefined, {
5867
+ logger.success(`${join8(testsLocalDir, name)}Analytics.spec.ts created successfully`, undefined, {
5640
5868
  showTimestamp: false,
5641
5869
  showArrow: false,
5642
5870
  useSymbol: true
5643
5871
  });
5644
- const packageJsonPath = join5(process.cwd(), "package.json");
5872
+ const packageJsonPath = join8(process.cwd(), "package.json");
5645
5873
  const packageJson = await Bun.file(packageJsonPath).json();
5646
5874
  const deps = packageJson.dependencies ?? {};
5647
5875
  const devDeps = packageJson.devDependencies ?? {};
@@ -5656,13 +5884,13 @@ class MakeAnalyticsCommand {
5656
5884
  }
5657
5885
  }
5658
5886
  MakeAnalyticsCommand = __legacyDecorateClassTS([
5659
- decorator4.command()
5887
+ decorator8.command()
5660
5888
  ], MakeAnalyticsCommand);
5661
5889
  // src/commands/MakeAppCommand.ts
5662
- import { join as join7 } from "path";
5663
- import { decorator as decorator6 } from "@ooneex/command";
5664
- import { TerminalLogger as TerminalLogger6 } from "@ooneex/logger";
5665
- import { toKebabCase as toKebabCase3, toSnakeCase } from "@ooneex/utils";
5890
+ import { join as join9 } from "path";
5891
+ import { decorator as decorator9 } from "@ooneex/command";
5892
+ import { TerminalLogger as TerminalLogger8 } from "@ooneex/logger";
5893
+ import { toKebabCase as toKebabCase2, toSnakeCase } from "@ooneex/utils";
5666
5894
 
5667
5895
  // src/prompts/askDestination.ts
5668
5896
  var import_enquirer2 = __toESM(require_enquirer(), 1);
@@ -6134,10 +6362,6 @@ FROM base AS prerelease
6134
6362
  COPY --from=build /temp/dev/node_modules node_modules
6135
6363
  COPY . .
6136
6364
 
6137
- # run migrations and seed
6138
- RUN bun run migration:up
6139
- RUN bun run seed:run
6140
-
6141
6365
  # build
6142
6366
  ENV NODE_ENV=production
6143
6367
  RUN bun run build
@@ -6479,9 +6703,7 @@ var package_json_default = `{
6479
6703
  "fmt": "bunx biome check --write",
6480
6704
  "lint": "bunx nx run-many -t lint --output-style=stream --verbose",
6481
6705
  "test": "bunx nx run-many -t test --output-style=stream --verbose",
6482
- "docker:stop": "bunx nx run-many -t docker:stop --output-style=stream --verbose",
6483
- "migration:up": "bunx nx run-many -t migration:up --output-style=stream --verbose",
6484
- "seed:run": "bunx nx run-many -t seed:run --output-style=stream --verbose",
6706
+ "stop": "bunx nx run-many -t stop --output-style=stream --verbose",
6485
6707
  "commit": "bunx commit"
6486
6708
  },
6487
6709
  "workspaces": [
@@ -6521,14 +6743,7 @@ cp modules/app/.env.example modules/app/.env
6521
6743
 
6522
6744
  Edit \`modules/app/.env\` with your configuration values.
6523
6745
 
6524
- ### 3. Run migrations and seeds
6525
-
6526
- \`\`\`bash
6527
- bun run migration:up
6528
- bun run seed:run
6529
- \`\`\`
6530
-
6531
- ### 4. Start the development server
6746
+ ### 3. Start the development server
6532
6747
 
6533
6748
  \`\`\`bash
6534
6749
  bun run dev
@@ -6545,9 +6760,7 @@ The server starts on \`http://localhost:3000\` by default.
6545
6760
  | \`bun run test\` | Run all tests |
6546
6761
  | \`bun run lint\` | Run TypeScript check + Biome linting |
6547
6762
  | \`bun run fmt\` | Format code with Biome |
6548
- | \`bun run migration:up\` | Execute pending database migrations |
6549
- | \`bun run seed:run\` | Populate database with seed data |
6550
- | \`bun run docker:stop\` | Stop all Docker containers |
6763
+ | \`bun run stop\` | Stop all Docker containers |
6551
6764
  | \`bun run commit\` | Interactive conventional commit |
6552
6765
 
6553
6766
  ## Module Architecture
@@ -6791,500 +7004,33 @@ var zed_settings_json_default = `{
6791
7004
  }
6792
7005
  `;
6793
7006
 
6794
- // src/commands/MakeControllerCommand.ts
6795
- import { basename, join as join6 } from "path";
6796
- import { decorator as decorator5 } from "@ooneex/command";
6797
- import { TerminalLogger as TerminalLogger5 } from "@ooneex/logger";
6798
- import { toKebabCase as toKebabCase2, toPascalCase as toPascalCase4, trim } from "@ooneex/utils";
6799
-
6800
- // src/prompts/askConfirm.ts
6801
- var import_enquirer3 = __toESM(require_enquirer(), 1);
6802
- var askConfirm = async (config) => {
6803
- const response = await import_enquirer3.prompt({
6804
- type: "confirm",
6805
- name: "confirm",
6806
- message: config.message,
6807
- initial: config.initial
6808
- });
6809
- return response.confirm;
6810
- };
6811
-
6812
- // ../types/dist/index.js
6813
- var HTTP_METHODS = ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"];
6814
-
6815
- // src/prompts/askRouteMethod.ts
6816
- var import_enquirer4 = __toESM(require_enquirer(), 1);
6817
-
6818
- // src/constraints/AssertRouteMethod.ts
6819
- import { Assert as Assert2, Validation as Validation2 } from "@ooneex/validation";
6820
-
6821
- class AssertRouteMethod extends Validation2 {
6822
- getConstraint() {
6823
- return Assert2("string >= 3");
7007
+ // src/commands/MakeAppCommand.ts
7008
+ class MakeAppCommand {
7009
+ getName() {
7010
+ return "make:app";
6824
7011
  }
6825
- getErrorMessage() {
6826
- return `Route method must be one of: ${HTTP_METHODS.join(", ")}`;
7012
+ getDescription() {
7013
+ return "Generate a new application";
6827
7014
  }
6828
- validate(data, constraint) {
6829
- const basicValidation = super.validate(data, constraint);
6830
- if (!basicValidation.isValid) {
6831
- return basicValidation;
6832
- }
6833
- const method = data;
6834
- if (method.trim() !== method) {
6835
- return {
6836
- isValid: false,
6837
- message: this.getErrorMessage() || "Invalid route method format"
6838
- };
6839
- }
6840
- const upperMethod = method.toUpperCase();
6841
- if (!HTTP_METHODS.includes(upperMethod)) {
6842
- return {
6843
- isValid: false,
6844
- message: this.getErrorMessage() || "Invalid route method"
6845
- };
7015
+ async run(options) {
7016
+ let { name, destination } = options;
7017
+ if (!name) {
7018
+ name = await askName({ message: "Enter application name" });
6846
7019
  }
6847
- return {
6848
- isValid: true
6849
- };
6850
- }
6851
- }
6852
-
6853
- // src/prompts/askRouteMethod.ts
6854
- var askRouteMethod = async (config) => {
6855
- const response = await import_enquirer4.prompt({
6856
- type: "select",
6857
- name: "method",
6858
- message: config.message,
6859
- initial: config.initial ?? 0,
6860
- choices: HTTP_METHODS.map((method) => method),
6861
- validate: (value) => {
6862
- const constraint = new AssertRouteMethod;
6863
- const result = constraint.validate(value);
6864
- if (!result.isValid) {
6865
- return result.message || "Route method is invalid";
6866
- }
6867
- return true;
6868
- }
6869
- });
6870
- return response.method;
6871
- };
6872
-
6873
- // src/prompts/askRouteName.ts
6874
- var import_enquirer5 = __toESM(require_enquirer(), 1);
6875
-
6876
- // src/constraints/AssertRouteName.ts
6877
- import { Assert as Assert3, Validation as Validation3 } from "@ooneex/validation";
6878
- var ROUTE_NAME_REGEX = /^[a-zA-Z0-9]+\.[a-zA-Z0-9]+\.[a-zA-Z0-9]+$/;
6879
-
6880
- class AssertRouteName extends Validation3 {
6881
- getConstraint() {
6882
- return Assert3("string >= 7");
6883
- }
6884
- getErrorMessage() {
6885
- return "Route name must follow format: namespace.resource.action (e.g., 'api.users.list')";
6886
- }
6887
- validate(data, constraint) {
6888
- const basicValidation = super.validate(data, constraint);
6889
- if (!basicValidation.isValid) {
6890
- return basicValidation;
6891
- }
6892
- const routeName = data;
6893
- if (routeName.trim() !== routeName) {
6894
- return {
6895
- isValid: false,
6896
- message: this.getErrorMessage() || "Invalid route name format"
6897
- };
6898
- }
6899
- if (!ROUTE_NAME_REGEX.test(routeName)) {
6900
- return {
6901
- isValid: false,
6902
- message: this.getErrorMessage() || "Invalid route name format"
6903
- };
6904
- }
6905
- const segments = routeName.split(".");
6906
- if (segments.length !== 3) {
6907
- return {
6908
- isValid: false,
6909
- message: this.getErrorMessage() || "Invalid route name format"
6910
- };
6911
- }
6912
- const [namespace, resource, action] = segments;
6913
- if (!namespace || !resource || !action) {
6914
- return {
6915
- isValid: false,
6916
- message: this.getErrorMessage() || "Invalid route name format"
6917
- };
6918
- }
6919
- return {
6920
- isValid: true
6921
- };
6922
- }
6923
- }
6924
-
6925
- // src/prompts/askRouteName.ts
6926
- var askRouteName = async (config) => {
6927
- const response = await import_enquirer5.prompt({
6928
- type: "input",
6929
- name: "routeName",
6930
- message: config.message,
6931
- validate: (value) => {
6932
- const constraint = new AssertRouteName;
6933
- const result = constraint.validate(value);
6934
- if (!result.isValid) {
6935
- return result.message || "Route name is invalid";
6936
- }
6937
- return true;
6938
- }
6939
- });
6940
- return response.routeName;
6941
- };
6942
-
6943
- // src/prompts/askRoutePath.ts
6944
- var import_enquirer6 = __toESM(require_enquirer(), 1);
6945
-
6946
- // src/constraints/AssertRoutePath.ts
6947
- import { Assert as Assert4, Validation as Validation4 } from "@ooneex/validation";
6948
- var ROUTE_PATH_MIN_LENGTH = 1;
6949
- var ROUTE_PATH_REGEX = /^\/[\w\-/:]*$/;
6950
- var VALID_SEGMENT_REGEX = /^[a-zA-Z0-9\-_]+$/;
6951
- var PARAM_SEGMENT_REGEX = /^:[a-zA-Z][a-zA-Z0-9]*$/;
6952
-
6953
- class AssertRoutePath extends Validation4 {
6954
- getConstraint() {
6955
- return Assert4(`string >= ${ROUTE_PATH_MIN_LENGTH}`);
6956
- }
6957
- getErrorMessage() {
6958
- return "Route path must start with '/' and contain only valid segments (e.g., '/users', '/api/users/:id')";
6959
- }
6960
- validate(data, constraint) {
6961
- const basicValidation = super.validate(data, constraint);
6962
- if (!basicValidation.isValid) {
6963
- return basicValidation;
6964
- }
6965
- const path = data;
6966
- if (path.trim() !== path) {
6967
- return {
6968
- isValid: false,
6969
- message: this.getErrorMessage() || "Invalid route path format"
6970
- };
6971
- }
6972
- if (!path.startsWith("/")) {
6973
- return {
6974
- isValid: false,
6975
- message: "Route path must start with '/'"
6976
- };
6977
- }
6978
- if (path.length > 1 && path.endsWith("/")) {
6979
- return {
6980
- isValid: false,
6981
- message: "Route path cannot end with '/' (except for root path)"
6982
- };
6983
- }
6984
- if (!ROUTE_PATH_REGEX.test(path)) {
6985
- return {
6986
- isValid: false,
6987
- message: this.getErrorMessage() || "Invalid route path format"
6988
- };
6989
- }
6990
- if (path === "/") {
6991
- return {
6992
- isValid: true
6993
- };
6994
- }
6995
- const segments = path.slice(1).split("/");
6996
- for (const segment of segments) {
6997
- if (!segment) {
6998
- return {
6999
- isValid: false,
7000
- message: "Route path cannot contain empty segments (double slashes)"
7001
- };
7002
- }
7003
- if (segment.startsWith(":")) {
7004
- if (!PARAM_SEGMENT_REGEX.test(segment)) {
7005
- return {
7006
- isValid: false,
7007
- message: `Invalid parameter segment '${segment}'. Parameters must follow format ':paramName' with alphanumeric characters only`
7008
- };
7009
- }
7010
- } else {
7011
- if (!VALID_SEGMENT_REGEX.test(segment)) {
7012
- return {
7013
- isValid: false,
7014
- message: `Invalid path segment '${segment}'. Segments must contain only letters, numbers, hyphens, and underscores`
7015
- };
7016
- }
7017
- }
7018
- }
7019
- return {
7020
- isValid: true
7021
- };
7022
- }
7023
- }
7024
-
7025
- // src/prompts/askRoutePath.ts
7026
- var askRoutePath = async (config) => {
7027
- const response = await import_enquirer6.prompt({
7028
- type: "input",
7029
- name: "path",
7030
- message: config.message,
7031
- initial: config.initial ?? "/",
7032
- validate: (value) => {
7033
- const constraint = new AssertRoutePath;
7034
- const result = constraint.validate(value);
7035
- if (!result.isValid) {
7036
- return result.message || "Route path is invalid";
7037
- }
7038
- return true;
7039
- }
7040
- });
7041
- return response.path;
7042
- };
7043
-
7044
- // src/templates/controller.socket.txt
7045
- var controller_socket_default = `import type { ContextType } from "@ooneex/socket";
7046
- import { ERole } from "@ooneex/role";
7047
- import { Route } from "@ooneex/routing";
7048
- import { Assert } from "@ooneex/validation";
7049
-
7050
- export type {{TYPE_NAME}}RouteType = {
7051
- params: {
7052
-
7053
- },
7054
- payload: {
7055
-
7056
- },
7057
- queries: {
7058
-
7059
- },
7060
- response: {
7061
-
7062
- },
7063
- };
7064
-
7065
- @Route.socket("{{ROUTE_PATH}}", {
7066
- name: "{{ROUTE_NAME}}",
7067
- version: 1,
7068
- description: "",
7069
- params: {
7070
- // id: Assert("string"),
7071
- },
7072
- payload: Assert({
7073
-
7074
- }),
7075
- queries: Assert({
7076
-
7077
- }),
7078
- response: Assert({
7079
-
7080
- }),
7081
- roles: [ERole.USER],
7082
- })
7083
- export class {{NAME}}Controller {
7084
- public async index(context: ContextType<{{TYPE_NAME}}RouteType>) {
7085
- // const { id } = context.params;
7086
-
7087
- return context.response.json({
7088
-
7089
- });
7090
- }
7091
- }
7092
- `;
7093
-
7094
- // src/templates/controller.test.txt
7095
- var controller_test_default = `import { describe, expect, test } from "bun:test";
7096
- import { {{NAME}}Controller } from "@/controllers/{{NAME}}Controller";
7097
-
7098
- describe("{{NAME}}Controller", () => {
7099
- test("should have class name ending with 'Controller'", () => {
7100
- expect({{NAME}}Controller.name.endsWith("Controller")).toBe(true);
7101
- });
7102
-
7103
- test("should have 'index' method", () => {
7104
- expect({{NAME}}Controller.prototype.index).toBeDefined();
7105
- expect(typeof {{NAME}}Controller.prototype.index).toBe("function");
7106
- });
7107
- });
7108
- `;
7109
-
7110
- // src/templates/controller.txt
7111
- var controller_default = `import type { ContextType } from "@ooneex/controller";
7112
- import { ERole } from "@ooneex/role";
7113
- import { Route } from "@ooneex/routing";
7114
- import { Assert } from "@ooneex/validation";
7115
-
7116
- export type {{TYPE_NAME}}RouteType = {
7117
- params: {
7118
-
7119
- },
7120
- payload: {
7121
-
7122
- },
7123
- queries: {
7124
-
7125
- },
7126
- response: {
7127
-
7128
- },
7129
- };
7130
-
7131
- @Route.{{ROUTE_METHOD}}("{{ROUTE_PATH}}", {
7132
- name: "{{ROUTE_NAME}}",
7133
- version: 1,
7134
- description: "",
7135
- params: {
7136
- // id: Assert("string"),
7137
- },
7138
- payload: Assert({
7139
-
7140
- }),
7141
- queries: Assert({
7142
-
7143
- }),
7144
- response: Assert({
7145
-
7146
- }),
7147
- roles: [ERole.USER],
7148
- })
7149
- export class {{NAME}}Controller {
7150
- public async index(context: ContextType<{{TYPE_NAME}}RouteType>) {
7151
- // const { id } = context.params;
7152
-
7153
- return context.response.json({
7154
-
7155
- });
7156
- }
7157
- }
7158
- `;
7159
-
7160
- // src/commands/MakeControllerCommand.ts
7161
- class MakeControllerCommand {
7162
- getName() {
7163
- return "make:controller";
7164
- }
7165
- getDescription() {
7166
- return "Generate a new controller class";
7167
- }
7168
- async addToModule(modulePath, controllerName) {
7169
- let content = await Bun.file(modulePath).text();
7170
- const className = `${controllerName}Controller`;
7171
- const importLine = `import { ${className} } from "./controllers/${className}";
7172
- `;
7173
- const lastImportIndex = content.lastIndexOf("import ");
7174
- const lineEnd = content.indexOf(`
7175
- `, lastImportIndex);
7176
- content = `${content.slice(0, lineEnd + 1)}${importLine}${content.slice(lineEnd + 1)}`;
7177
- const regex = /(controllers:\s*\[)([^\]]*)/s;
7178
- const match = content.match(regex);
7179
- if (match) {
7180
- const existing = match[2]?.trim();
7181
- const newValue = existing ? `${existing}, ${className}` : className;
7182
- content = content.replace(regex, `$1${newValue}`);
7183
- }
7184
- await Bun.write(modulePath, content);
7185
- }
7186
- async run(options) {
7187
- let { name, module, isSocket } = options;
7188
- if (!name) {
7189
- name = await askName({ message: "Enter controller name" });
7190
- }
7191
- if (isSocket === undefined) {
7192
- isSocket = await askConfirm({ message: "Is this a socket controller?" });
7193
- }
7194
- name = toPascalCase4(name).replace(/Controller$/, "");
7195
- const { route = {} } = options;
7196
- const selectedTemplate = isSocket ? controller_socket_default : controller_default;
7197
- let content = selectedTemplate.replaceAll("{{NAME}}", name);
7198
- if (!route.name) {
7199
- route.name = await askRouteName({ message: "Enter route name (e.g., api.user.create)" });
7200
- }
7201
- const routeTypeName = toPascalCase4(route.name);
7202
- content = content.replaceAll("{{ROUTE_NAME}}", route.name).replaceAll("{{TYPE_NAME}}", routeTypeName);
7203
- if (!route.path) {
7204
- route.path = await askRoutePath({ message: "Enter route path", initial: "/" });
7205
- }
7206
- const routePath = `/${toKebabCase2(trim(route.path, "/"))}`;
7207
- content = content.replaceAll("{{ROUTE_PATH}}", routePath);
7208
- if (!isSocket && !route.method) {
7209
- route.method = await askRouteMethod({ message: "Enter route method" });
7210
- }
7211
- if (!isSocket && route.method) {
7212
- content = content.replaceAll("{{ROUTE_METHOD}}", route.method.toLowerCase());
7213
- }
7214
- if (module) {
7215
- await ensureModule(module);
7216
- }
7217
- const base = module ? join6("modules", module) : ".";
7218
- const controllersLocalDir = join6(base, "src", "controllers");
7219
- const controllersDir = join6(process.cwd(), controllersLocalDir);
7220
- const filePath = join6(controllersDir, `${name}Controller.ts`);
7221
- await Bun.write(filePath, content);
7222
- const testContent = controller_test_default.replace(/{{NAME}}/g, name);
7223
- const testsLocalDir = join6(base, "tests", "controllers");
7224
- const testsDir = join6(process.cwd(), testsLocalDir);
7225
- const testFilePath = join6(testsDir, `${name}Controller.spec.ts`);
7226
- await Bun.write(testFilePath, testContent);
7227
- const modulePascalName = module ? toPascalCase4(module) : toPascalCase4(basename(process.cwd()));
7228
- const modulePath = join6(process.cwd(), base, "src", `${modulePascalName}Module.ts`);
7229
- if (await Bun.file(modulePath).exists()) {
7230
- await this.addToModule(modulePath, name);
7231
- }
7232
- const logger = new TerminalLogger5;
7233
- logger.success(`${join6(controllersLocalDir, name)}Controller.ts created successfully`, undefined, {
7234
- showTimestamp: false,
7235
- showArrow: false,
7236
- useSymbol: true
7237
- });
7238
- logger.success(`${join6(testsLocalDir, name)}Controller.spec.ts created successfully`, undefined, {
7239
- showTimestamp: false,
7240
- showArrow: false,
7241
- useSymbol: true
7242
- });
7243
- const packageJsonPath = join6(process.cwd(), "package.json");
7244
- const packageJson = await Bun.file(packageJsonPath).json();
7245
- const deps = packageJson.dependencies ?? {};
7246
- const devDeps = packageJson.devDependencies ?? {};
7247
- if (!deps["@ooneex/controller"] && !devDeps["@ooneex/controller"]) {
7248
- const install = Bun.spawn(["bun", "add", "@ooneex/controller"], {
7249
- cwd: process.cwd(),
7250
- stdout: "ignore",
7251
- stderr: "inherit"
7252
- });
7253
- await install.exited;
7254
- }
7255
- }
7256
- }
7257
- MakeControllerCommand = __legacyDecorateClassTS([
7258
- decorator5.command()
7259
- ], MakeControllerCommand);
7260
-
7261
- // src/commands/MakeAppCommand.ts
7262
- class MakeAppCommand {
7263
- getName() {
7264
- return "make:app";
7265
- }
7266
- getDescription() {
7267
- return "Generate a new application";
7268
- }
7269
- async run(options) {
7270
- let { name, destination } = options;
7271
- if (!name) {
7272
- name = await askName({ message: "Enter application name" });
7273
- }
7274
- const kebabName = toKebabCase3(name);
7275
- if (!destination) {
7276
- destination = await askDestination({ message: "Enter destination path", initial: kebabName });
7020
+ const kebabName = toKebabCase2(name);
7021
+ if (!destination) {
7022
+ destination = await askDestination({ message: "Enter destination path", initial: kebabName });
7277
7023
  }
7278
7024
  const packageContent = package_json_default.replace(/{{NAME}}/g, kebabName);
7279
- await Bun.write(join7(destination, ".commitlintrc.ts"), _commitlintrc_ts_default);
7280
- await Bun.write(join7(destination, ".gitignore"), _gitignore_default);
7281
- await Bun.write(join7(destination, "biome.jsonc"), biome_jsonc_default);
7282
- await Bun.write(join7(destination, "bunfig.toml"), bunfig_toml_default);
7283
- await Bun.write(join7(destination, "nx.json"), nx_json_default);
7284
- await Bun.write(join7(destination, "package.json"), packageContent);
7285
- await Bun.write(join7(destination, "README.md"), README_md_default.replace(/{{NAME}}/g, kebabName));
7286
- await Bun.write(join7(destination, "tsconfig.json"), tsconfig_json_default);
7287
- await Bun.write(join7(destination, ".zed", "settings.json"), zed_settings_json_default);
7025
+ await Bun.write(join9(destination, ".commitlintrc.ts"), _commitlintrc_ts_default);
7026
+ await Bun.write(join9(destination, ".gitignore"), _gitignore_default);
7027
+ await Bun.write(join9(destination, "biome.jsonc"), biome_jsonc_default);
7028
+ await Bun.write(join9(destination, "bunfig.toml"), bunfig_toml_default);
7029
+ await Bun.write(join9(destination, "nx.json"), nx_json_default);
7030
+ await Bun.write(join9(destination, "package.json"), packageContent);
7031
+ await Bun.write(join9(destination, "README.md"), README_md_default.replace(/{{NAME}}/g, kebabName));
7032
+ await Bun.write(join9(destination, "tsconfig.json"), tsconfig_json_default);
7033
+ await Bun.write(join9(destination, ".zed", "settings.json"), zed_settings_json_default);
7288
7034
  const makeModuleCommand = new MakeModuleCommand;
7289
7035
  await makeModuleCommand.run({
7290
7036
  name: "app",
@@ -7293,34 +7039,23 @@ class MakeAppCommand {
7293
7039
  skipMigrations: true,
7294
7040
  skipSeeds: true
7295
7041
  });
7296
- const appModulePackagePath = join7(destination, "modules", "app", "package.json");
7042
+ const appModulePackagePath = join9(destination, "modules", "app", "package.json");
7297
7043
  const appModulePackageJson = await Bun.file(appModulePackagePath).json();
7298
7044
  appModulePackageJson.scripts.dev = "docker compose up -d && bun --hot run ./src/index.ts";
7299
- appModulePackageJson.scripts["docker:stop"] = "docker compose down";
7045
+ appModulePackageJson.scripts.stop = "docker compose down";
7300
7046
  appModulePackageJson.scripts.build = "bun build ./src/index.ts --outdir ./dist --target bun";
7301
7047
  await Bun.write(appModulePackagePath, JSON.stringify(appModulePackageJson, null, 2));
7302
7048
  const envContent = env_default.replace(/^DATABASE_URL=/m, 'DATABASE_URL="postgresql://ooneex:ooneex@localhost:5432/ooneex"').replace(/^CACHE_REDIS_URL=/m, 'CACHE_REDIS_URL="redis://localhost:6379"').replace(/^PUBSUB_REDIS_URL=/m, 'PUBSUB_REDIS_URL="redis://localhost:6379"').replace(/^RATE_LIMIT_REDIS_URL=/m, 'RATE_LIMIT_REDIS_URL="redis://localhost:6379"').replace(/^DATABASE_REDIS_URL=/m, 'DATABASE_REDIS_URL="redis://localhost:6379"');
7303
- await Bun.write(join7(destination, "modules", "app", ".env"), envContent);
7304
- await Bun.write(join7(destination, "modules", "app", ".env.example"), env_default);
7305
- await Bun.write(join7(destination, "modules", "app", "src", "databases", "AppDatabase.ts"), app_database_default);
7306
- await Bun.write(join7(destination, "modules", "app", "src", "index.ts"), index_ts_default);
7049
+ await Bun.write(join9(destination, "modules", "app", ".env"), envContent);
7050
+ await Bun.write(join9(destination, "modules", "app", ".env.example"), env_default);
7051
+ await Bun.write(join9(destination, "modules", "app", "src", "databases", "AppDatabase.ts"), app_database_default);
7052
+ await Bun.write(join9(destination, "modules", "app", "src", "index.ts"), index_ts_default);
7307
7053
  const snakeName = toSnakeCase(name);
7308
7054
  const dockerComposeContent = docker_compose_yml_default.replace(/{{NAME}}/g, snakeName);
7309
- await Bun.write(join7(destination, "modules", "app", "docker-compose.yml"), dockerComposeContent);
7055
+ await Bun.write(join9(destination, "modules", "app", "docker-compose.yml"), dockerComposeContent);
7310
7056
  const dockerfileContent = Dockerfile_default.replace(/{{NAME}}/g, snakeName);
7311
- await Bun.write(join7(destination, "modules", "app", "Dockerfile"), dockerfileContent);
7312
- await Bun.write(join7(destination, "modules", "app", "var", ".gitkeep"), "");
7313
- const makeControllerCommand = new MakeControllerCommand;
7314
- await makeControllerCommand.run({
7315
- name: "HealthCheck",
7316
- isSocket: false,
7317
- module: "app",
7318
- route: {
7319
- name: "api.health.check",
7320
- path: "/healthcheck",
7321
- method: "GET"
7322
- }
7323
- });
7057
+ await Bun.write(join9(destination, "modules", "app", "Dockerfile"), dockerfileContent);
7058
+ await Bun.write(join9(destination, "modules", "app", "var", ".gitkeep"), "");
7324
7059
  const gitInit = Bun.spawn(["git", "init"], { cwd: destination, stdout: "ignore", stderr: "inherit" });
7325
7060
  await gitInit.exited;
7326
7061
  const addDeps = Bun.spawn([
@@ -7379,9 +7114,9 @@ class MakeAppCommand {
7379
7114
  await addDevDeps.exited;
7380
7115
  const huskyInit = Bun.spawn(["bunx", "husky", "init"], { cwd: destination, stdout: "ignore", stderr: "inherit" });
7381
7116
  await huskyInit.exited;
7382
- await Bun.write(join7(destination, ".husky", "pre-commit"), "lint-staged");
7383
- await Bun.write(join7(destination, ".husky", "commit-msg"), `bunx commitlint --edit "$1"`);
7384
- const logger = new TerminalLogger6;
7117
+ await Bun.write(join9(destination, ".husky", "pre-commit"), "lint-staged");
7118
+ await Bun.write(join9(destination, ".husky", "commit-msg"), `bunx commitlint --edit "$1"`);
7119
+ const logger = new TerminalLogger8;
7385
7120
  logger.success(`${kebabName} created successfully at ${destination}`, undefined, {
7386
7121
  showTimestamp: false,
7387
7122
  showArrow: false,
@@ -7390,512 +7125,978 @@ class MakeAppCommand {
7390
7125
  }
7391
7126
  }
7392
7127
  MakeAppCommand = __legacyDecorateClassTS([
7393
- decorator6.command()
7128
+ decorator9.command()
7394
7129
  ], MakeAppCommand);
7395
7130
  // src/commands/MakeCacheCommand.ts
7396
- import { join as join8 } from "path";
7397
- import { decorator as decorator7 } from "@ooneex/command";
7398
- import { TerminalLogger as TerminalLogger7 } from "@ooneex/logger";
7399
- import { toPascalCase as toPascalCase5 } from "@ooneex/utils";
7131
+ import { join as join10 } from "path";
7132
+ import { decorator as decorator10 } from "@ooneex/command";
7133
+ import { TerminalLogger as TerminalLogger9 } from "@ooneex/logger";
7134
+ import { toPascalCase as toPascalCase4 } from "@ooneex/utils";
7135
+
7136
+ // src/templates/cache.test.txt
7137
+ var cache_test_default = `import { describe, expect, test } from "bun:test";
7138
+ import { {{NAME}}Cache } from "@/cache/{{NAME}}Cache";
7139
+
7140
+ describe("{{NAME}}Cache", () => {
7141
+ test("should have class name ending with 'Cache'", () => {
7142
+ expect({{NAME}}Cache.name.endsWith("Cache")).toBe(true);
7143
+ });
7144
+
7145
+ test("should have 'get' method", () => {
7146
+ expect({{NAME}}Cache.prototype.get).toBeDefined();
7147
+ expect(typeof {{NAME}}Cache.prototype.get).toBe("function");
7148
+ });
7149
+
7150
+ test("should have 'set' method", () => {
7151
+ expect({{NAME}}Cache.prototype.set).toBeDefined();
7152
+ expect(typeof {{NAME}}Cache.prototype.set).toBe("function");
7153
+ });
7154
+
7155
+ test("should have 'delete' method", () => {
7156
+ expect({{NAME}}Cache.prototype.delete).toBeDefined();
7157
+ expect(typeof {{NAME}}Cache.prototype.delete).toBe("function");
7158
+ });
7159
+
7160
+ test("should have 'has' method", () => {
7161
+ expect({{NAME}}Cache.prototype.has).toBeDefined();
7162
+ expect(typeof {{NAME}}Cache.prototype.has).toBe("function");
7163
+ });
7164
+ });
7165
+ `;
7166
+
7167
+ // src/templates/cache.txt
7168
+ var cache_default = `import { CacheException, decorator } from "@ooneex/cache";
7169
+ import type { ICache } from "@ooneex/cache";
7170
+
7171
+ @decorator.cache()
7172
+ export class {{NAME}}Cache implements ICache {
7173
+ public async get<T = unknown>(key: string): Promise<T | undefined> {
7174
+ throw new CacheException(\`Failed to get key "\${key}": Not implemented\`);
7175
+ }
7176
+
7177
+ public async set<T = unknown>(key: string, value: T, ttl?: number): Promise<void> {
7178
+ throw new CacheException(\`Failed to set key "\${key}": Not implemented\`);
7179
+ }
7180
+
7181
+ public async delete(key: string): Promise<boolean> {
7182
+ throw new CacheException(\`Failed to delete key "\${key}": Not implemented\`);
7183
+ }
7184
+
7185
+ public async has(key: string): Promise<boolean> {
7186
+ throw new CacheException(\`Failed to check if key "\${key}" exists: Not implemented\`);
7187
+ }
7188
+ }
7189
+ `;
7190
+
7191
+ // src/commands/MakeCacheCommand.ts
7192
+ class MakeCacheCommand {
7193
+ getName() {
7194
+ return "make:cache";
7195
+ }
7196
+ getDescription() {
7197
+ return "Generate a new cache class";
7198
+ }
7199
+ async run(options) {
7200
+ let { name, module } = options;
7201
+ if (!name) {
7202
+ name = await askName({ message: "Enter cache name" });
7203
+ }
7204
+ name = toPascalCase4(name).replace(/Cache$/, "");
7205
+ const content = cache_default.replace(/{{NAME}}/g, name);
7206
+ if (module) {
7207
+ await ensureModule(module);
7208
+ }
7209
+ const base = module ? join10("modules", module) : ".";
7210
+ const cacheLocalDir = join10(base, "src", "cache");
7211
+ const cacheDir = join10(process.cwd(), cacheLocalDir);
7212
+ const filePath = join10(cacheDir, `${name}Cache.ts`);
7213
+ await Bun.write(filePath, content);
7214
+ const testContent = cache_test_default.replace(/{{NAME}}/g, name);
7215
+ const testsLocalDir = join10(base, "tests", "cache");
7216
+ const testsDir = join10(process.cwd(), testsLocalDir);
7217
+ const testFilePath = join10(testsDir, `${name}Cache.spec.ts`);
7218
+ await Bun.write(testFilePath, testContent);
7219
+ const logger = new TerminalLogger9;
7220
+ logger.success(`${join10(cacheLocalDir, name)}Cache.ts created successfully`, undefined, {
7221
+ showTimestamp: false,
7222
+ showArrow: false,
7223
+ useSymbol: true
7224
+ });
7225
+ logger.success(`${join10(testsLocalDir, name)}Cache.spec.ts created successfully`, undefined, {
7226
+ showTimestamp: false,
7227
+ showArrow: false,
7228
+ useSymbol: true
7229
+ });
7230
+ const packageJsonPath = join10(process.cwd(), "package.json");
7231
+ const packageJson = await Bun.file(packageJsonPath).json();
7232
+ const deps = packageJson.dependencies ?? {};
7233
+ const devDeps = packageJson.devDependencies ?? {};
7234
+ if (!deps["@ooneex/cache"] && !devDeps["@ooneex/cache"]) {
7235
+ const install = Bun.spawn(["bun", "add", "@ooneex/cache"], {
7236
+ cwd: process.cwd(),
7237
+ stdout: "ignore",
7238
+ stderr: "inherit"
7239
+ });
7240
+ await install.exited;
7241
+ }
7242
+ }
7243
+ }
7244
+ MakeCacheCommand = __legacyDecorateClassTS([
7245
+ decorator10.command()
7246
+ ], MakeCacheCommand);
7247
+ // src/commands/MakeClaudeSkillCommand.ts
7248
+ import { join as join11 } from "path";
7249
+ import { decorator as decorator11 } from "@ooneex/command";
7250
+ import { TerminalLogger as TerminalLogger10 } from "@ooneex/logger";
7251
+
7252
+ // src/templates/claude/skills/commit.md.txt
7253
+ var commit_md_default = `---
7254
+ name: commit
7255
+ description: Create commit messages grouped by module. Analyzes git changes, groups files under modules/ by module name, and creates separate commits following commitlint conventions. Uses common scope for non-module changes.
7256
+ ---
7257
+
7258
+ # Commit by Module
7259
+
7260
+ Create separate commits for each modified module, following the project's commitlint conventions.
7261
+
7262
+ ## Workflow
7263
+
7264
+ 1. **Analyze staged/unstaged changes**
7265
+ - Run \`git status --porcelain\` to get all modified files
7266
+ - Group changes by module (files under \`modules/<module-name>/\`)
7267
+ - Identify non-module changes (files not in \`modules/\`) as scope \`common\`
7268
+
7269
+ 2. **For each module with changes**
7270
+ - Stage only that module's files: \`git add modules/<module-name>/\`
7271
+ - Determine the appropriate commit type based on changes
7272
+ - Create a commit with proper format: \`type(scope): Subject\`
7273
+ - The scope is the module name in lower-case
7274
+ - Repeat for next module
7275
+
7276
+ 3. **Handle non-module changes**
7277
+ - Files outside \`modules/\` (e.g., \`bun.lock\`, \`package.json\`, config files, \`packages/\` changes) use scope \`common\`
7278
+ - Stage and commit separately from module changes
7279
+
7280
+ ## Commit Message Format
7281
+
7282
+ \`\`\`
7283
+ type(scope): Subject line
7284
+ \`\`\`
7285
+
7286
+ ### Valid Types
7287
+ - \`feat\`: New feature
7288
+ - \`fix\`: Bug fix
7289
+ - \`refactor\`: Code refactoring (no new feature, no bug fix)
7290
+ - \`test\`: Adding/updating tests
7291
+ - \`chore\`: Maintenance tasks (dependencies, configs)
7292
+ - \`docs\`: Documentation changes
7293
+ - \`style\`: Code style changes (formatting, whitespace)
7294
+ - \`perf\`: Performance improvements
7295
+ - \`build\`: Build system changes
7296
+ - \`ci\`: CI configuration changes
7297
+ - \`revert\`: Revert previous commit
7298
+
7299
+ ### Scope Rules
7300
+ - For files under \`modules/<name>/\`: use the module name as scope (e.g., \`module\` scope matches the commitlint config)
7301
+ - For all other files: use \`common\`
7302
+ - Scope must be lower-case
7303
+ - Scope must never be empty
7304
+ - If the module name matches a valid commitlint scope, use it directly
7305
+ - If it does not match, use \`module\` as the scope
7306
+
7307
+ ### Subject Rules
7308
+ - Use sentence-case, start-case, pascal-case, or upper-case
7309
+ - No period at the end
7310
+ - Maximum 100 characters for entire header
7311
+ - Use imperative mood ("Add feature" not "Added feature")
7312
+
7313
+ ## Determining Commit Type
7314
+
7315
+ Analyze the changes to determine the appropriate type:
7316
+
7317
+ | Change Pattern | Type |
7318
+ |---------------|------|
7319
+ | New files with functionality | \`feat\` |
7320
+ | Bug fixes, error corrections | \`fix\` |
7321
+ | Code restructuring, renaming | \`refactor\` |
7322
+ | New/updated test files (\`*.spec.ts\`) | \`test\` |
7323
+ | Documentation files (\`*.md\`) | \`docs\` |
7324
+ | Dependency updates, lock files | \`chore\` |
7325
+ | Build configs, scripts | \`build\` |
7326
+ | CI/CD files | \`ci\` |
7327
+ | Formatting only | \`style\` |
7328
+ | Performance optimizations | \`perf\` |
7400
7329
 
7401
- // src/templates/cache.test.txt
7402
- var cache_test_default = `import { describe, expect, test } from "bun:test";
7403
- import { {{NAME}}Cache } from "@/cache/{{NAME}}Cache";
7330
+ ## Examples
7404
7331
 
7405
- describe("{{NAME}}Cache", () => {
7406
- test("should have class name ending with 'Cache'", () => {
7407
- expect({{NAME}}Cache.name.endsWith("Cache")).toBe(true);
7408
- });
7332
+ ### Example 1: Multiple Module Changes
7409
7333
 
7410
- test("should have 'get' method", () => {
7411
- expect({{NAME}}Cache.prototype.get).toBeDefined();
7412
- expect(typeof {{NAME}}Cache.prototype.get).toBe("function");
7413
- });
7334
+ Git status shows:
7335
+ \`\`\`
7336
+ M modules/user/src/services/UserService.ts
7337
+ A modules/user/src/services/AuthService.ts
7338
+ M modules/user/tests/UserService.spec.ts
7339
+ M modules/product/src/entities/Product.ts
7340
+ M modules/product/src/repositories/ProductRepository.ts
7341
+ M bun.lock
7342
+ M packages/cache/src/index.ts
7343
+ \`\`\`
7414
7344
 
7415
- test("should have 'set' method", () => {
7416
- expect({{NAME}}Cache.prototype.set).toBeDefined();
7417
- expect(typeof {{NAME}}Cache.prototype.set).toBe("function");
7418
- });
7345
+ Commands to execute:
7346
+ \`\`\`bash
7347
+ # Commit user module
7348
+ git add modules/user/
7349
+ git commit -m "feat(user): Add AuthService and update UserService"
7419
7350
 
7420
- test("should have 'delete' method", () => {
7421
- expect({{NAME}}Cache.prototype.delete).toBeDefined();
7422
- expect(typeof {{NAME}}Cache.prototype.delete).toBe("function");
7423
- });
7351
+ # Commit product module
7352
+ git add modules/product/
7353
+ git commit -m "refactor(product): Update Product entity and repository"
7424
7354
 
7425
- test("should have 'has' method", () => {
7426
- expect({{NAME}}Cache.prototype.has).toBeDefined();
7427
- expect(typeof {{NAME}}Cache.prototype.has).toBe("function");
7428
- });
7429
- });
7355
+ # Commit non-module changes
7356
+ git add bun.lock packages/cache/
7357
+ git commit -m "chore(common): Update dependencies and cache package"
7358
+ \`\`\`
7359
+
7360
+ ### Example 2: Single Module with Tests
7361
+
7362
+ Git status shows:
7363
+ \`\`\`
7364
+ A modules/payment/src/services/StripeService.ts
7365
+ A modules/payment/tests/StripeService.spec.ts
7366
+ \`\`\`
7367
+
7368
+ Command:
7369
+ \`\`\`bash
7370
+ git add modules/payment/
7371
+ git commit -m "feat(payment): Add StripeService with tests"
7372
+ \`\`\`
7373
+
7374
+ ### Example 3: Only Non-module Changes
7375
+
7376
+ Git status shows:
7377
+ \`\`\`
7378
+ M package.json
7379
+ M bun.lock
7380
+ M packages/logger/src/Logger.ts
7381
+ \`\`\`
7382
+
7383
+ Command:
7384
+ \`\`\`bash
7385
+ git add package.json bun.lock packages/logger/
7386
+ git commit -m "chore(common): Update dependencies and logger package"
7387
+ \`\`\`
7388
+
7389
+ ### Example 4: Module Refactoring with Deletions
7390
+
7391
+ Git status shows:
7392
+ \`\`\`
7393
+ D modules/order/src/services/OldOrderService.ts
7394
+ A modules/order/src/services/OrderService.ts
7395
+ M modules/order/src/index.ts
7396
+ \`\`\`
7397
+
7398
+ Command:
7399
+ \`\`\`bash
7400
+ git add modules/order/
7401
+ git commit -m "refactor(order): Replace OldOrderService with OrderService"
7402
+ \`\`\`
7403
+
7404
+ ## Subject Line Guidelines
7405
+
7406
+ Write clear, descriptive subjects:
7407
+
7408
+ | Good | Bad |
7409
+ |------|-----|
7410
+ | \`Add Stripe payment integration\` | \`stripe\` |
7411
+ | \`Fix null check in user validation\` | \`fix bug\` |
7412
+ | \`Update order processing flow\` | \`changes\` |
7413
+ | \`Remove deprecated auth methods\` | \`cleanup\` |
7414
+ | \`Rename UserAdapter to UserService\` | \`rename\` |
7415
+
7416
+ ## Pre-commit Checklist
7417
+
7418
+ Before committing, verify:
7419
+ 1. Changes are logically grouped by module
7420
+ 2. Tests pass for modified modules
7421
+ 3. No debug code or console.logs left in
7422
+ 4. Commit message follows the format exactly
7423
+ 5. For Entity class, if property is optional, add \`null\` to its type.
7424
+
7425
+ ## Handling Special Cases
7426
+
7427
+ ### Mixed Changes in One Module
7428
+ If a module has both new features and bug fixes, prioritize:
7429
+ 1. \`feat\` if primary change is new functionality
7430
+ 2. \`fix\` if primary change is a bug fix
7431
+ 3. Split into multiple commits if changes are truly independent
7432
+
7433
+ ### Deleted Files Only
7434
+ Use \`refactor\` for removing deprecated code:
7435
+ \`\`\`bash
7436
+ git add modules/user/
7437
+ git commit -m "refactor(user): Remove deprecated UserAdapter"
7438
+ \`\`\`
7439
+
7440
+ ### Renamed/Moved Files
7441
+ Use \`refactor\` for file reorganization:
7442
+ \`\`\`bash
7443
+ git add modules/product/
7444
+ git commit -m "refactor(product): Reorganize service file structure"
7445
+ \`\`\`
7430
7446
  `;
7431
7447
 
7432
- // src/templates/cache.txt
7433
- var cache_default = `import { CacheException, decorator } from "@ooneex/cache";
7434
- import type { ICache } from "@ooneex/cache";
7448
+ // src/templates/claude/skills/make.ai.md.txt
7449
+ var make_ai_md_default = '---\nname: make:ai\ndescription: Generate a new AI class with its test file, then complete the generated code. Use when creating a new AI chat class that uses OpenAI via the @ooneex/ai package.\n---\n\n# Make AI Class\n\nGenerate a new AI class and its test file using the `make:ai` CLI command, then complete the generated code with proper implementation.\n\n## Coding Conventions\n\n- Always explicitly show visibility on class methods and properties (`private`, `public`, `protected`)\n- Always use arrow functions when possible, but NOT for class methods (class methods use regular method syntax)\n- End type names with the `Type` suffix (e.g., `ExceptionStackFrameType`, `ServiceDataType`)\n- Start interface names with the `I` prefix (e.g., `IException`, `IService`, `IAiChat`)\n- For Entity class, if property is optional, add `null` to its type.\n- For dependencies, use `inject` from `@ooneex/container`. For example:\n\n```typescript\nimport { inject } from "@ooneex/container";\nimport { decorator, type ISeed } from "@ooneex/seeds";\n\n@decorator.seed()\nexport class BookSeed implements ISeed {\n constructor(\n @inject(BookRepository)\n private readonly repository: BookRepository,\n ) {}\n}\n```\n\n## Steps\n\n### 1. Run the generator\n\nRun the following command to scaffold the AI class and test files:\n\n```bash\nbunx @ooneex/cli@latest make:ai --name=<name> --module=<module>\n```\n\nWhere `<name>` is the name provided by the user. The `--module` option is optional \u2014 if provided, files are generated under `modules/<module>/` instead of the project root. The command will generate:\n- `src/ai/<Name>Ai.ts` - The AI class file (or `modules/<module>/src/ai/<Name>Ai.ts` with `--module`)\n- `tests/ai/<Name>Ai.spec.ts` - The test file (or `modules/<module>/tests/ai/<Name>Ai.spec.ts` with `--module`)\n\n### 2. Read the generated files\n\nRead both generated files to understand the scaffolded code.\n\n### 3. Complete the AI class\n\nEdit `src/ai/<Name>Ai.ts` to complete the implementation:\n\n- Update the prompt in the `run` method from `"My prompt"` to a meaningful prompt based on the class purpose\n- Update the prompt in the `runStream` method from `"My prompt"` to a meaningful prompt based on the class purpose\n- Add any additional configuration or methods relevant to the AI class purpose\n- Ensure proper typing for the `run<T>()` generic return type\n\nThe generated class structure follows this pattern:\n\n```typescript\nimport { decorator, type IAiChat, OpenAi, type OpenAiConfigType } from "@ooneex/ai";\nimport { inject } from "@ooneex/container";\n\n@decorator.ai()\nexport class <Name>Ai implements IAiChat<OpenAiConfigType> {\n constructor(@inject(OpenAi) private readonly ai: OpenAi) {}\n\n public async run<T>(prompt?: string, config?: Omit<OpenAiConfigType, "prompt">): Promise<T> {\n return this.ai.run<T>(prompt || "My prompt", config);\n }\n\n public async *runStream(\n prompt?: string,\n config?: Omit<OpenAiConfigType, "prompt" | "output">,\n ): AsyncGenerator<string, void, unknown> {\n yield* this.ai.runStream(prompt || "My prompt", config);\n }\n}\n```\n\n### 4. Complete the test file\n\nEdit `tests/ai/<Name>Ai.spec.ts` to add meaningful tests beyond the scaffolded ones:\n\n- Keep the existing scaffolded tests (class name, run method, runStream method)\n- Add tests relevant to the specific AI class behavior\n\nThe generated test structure follows this pattern:\n\n```typescript\nimport { describe, expect, test } from "bun:test";\nimport { <Name>Ai } from "@/ai/<Name>Ai";\n\ndescribe("<Name>Ai", () => {\n test("should have class name ending with \'Ai\'", () => {\n expect(<Name>Ai.name.endsWith("Ai")).toBe(true);\n });\n\n test("should have \'run\' method", () => {\n expect(<Name>Ai.prototype.run).toBeDefined();\n expect(typeof <Name>Ai.prototype.run).toBe("function");\n });\n\n test("should have \'runStream\' method", () => {\n expect(<Name>Ai.prototype.runStream).toBeDefined();\n expect(typeof <Name>Ai.prototype.runStream).toBe("function");\n });\n});\n```\n\n### 5. Lint and format\n\nRun linting and formatting on the generated files:\n\n```bash\nbunx biome check --fix src/ai/<Name>Ai.ts tests/ai/<Name>Ai.spec.ts\n```\n';
7435
7450
 
7436
- @decorator.cache()
7437
- export class {{NAME}}Cache implements ICache {
7438
- public async get<T = unknown>(key: string): Promise<T | undefined> {
7439
- throw new CacheException(\`Failed to get key "\${key}": Not implemented\`);
7440
- }
7451
+ // src/templates/claude/skills/make.analytics.md.txt
7452
+ var make_analytics_md_default = '---\nname: make:analytics\ndescription: Generate a new analytics class with its test file, then complete the generated code. Use when creating a new analytics tracking class that uses the @ooneex/analytics package.\n---\n\n# Make Analytics Class\n\nGenerate a new analytics class and its test file using the `make:analytics` CLI command, then complete the generated code with proper implementation.\n\n## Coding Conventions\n\n- Always explicitly show visibility on class methods and properties (`private`, `public`, `protected`)\n- Always use arrow functions when possible, but NOT for class methods (class methods use regular method syntax)\n- End type names with the `Type` suffix (e.g., `ExceptionStackFrameType`, `ServiceDataType`)\n- Start interface names with the `I` prefix (e.g., `IException`, `IService`, `IAiChat`)\n- For Entity class, if property is optional, add `null` to its type.\n- For dependencies, use `inject` from `@ooneex/container`. For example:\n\n```typescript\nimport { inject } from "@ooneex/container";\nimport { decorator, type ISeed } from "@ooneex/seeds";\n\n@decorator.seed()\nexport class BookSeed implements ISeed {\n constructor(\n @inject(BookRepository)\n private readonly repository: BookRepository,\n ) {}\n}\n```\n\n## Steps\n\n### 1. Run the generator\n\nRun the following command to scaffold the analytics class and test files:\n\n```bash\nbunx @ooneex/cli@latest make:analytics --name=<name> --module=<module>\n```\n\nWhere `<name>` is the name provided by the user. The `--module` option is optional \u2014 if provided, files are generated under `modules/<module>/` instead of the project root. The command will generate:\n- `src/analytics/<Name>Analytics.ts` - The analytics class file (or `modules/<module>/src/analytics/<Name>Analytics.ts` with `--module`)\n- `tests/analytics/<Name>Analytics.spec.ts` - The test file (or `modules/<module>/tests/analytics/<Name>Analytics.spec.ts` with `--module`)\n\n### 2. Read the generated files\n\nRead both generated files to understand the scaffolded code.\n\n### 3. Complete the analytics class\n\nEdit `src/analytics/<Name>Analytics.ts` to complete the implementation:\n\n- Implement the `capture` method with actual analytics tracking logic\n- Define a proper type for `CaptureOptionsType` instead of `Record<string, unknown>` based on the analytics purpose\n- Add any additional methods or properties relevant to the analytics class purpose\n\nThe generated class structure follows this pattern:\n\n```typescript\nimport { type IAnalytics, decorator } from "@ooneex/analytics";\n\ntype CaptureOptionsType = Record<string, unknown>;\n\n@decorator.analytics()\nexport class <Name>Analytics<T extends CaptureOptionsType = CaptureOptionsType> implements IAnalytics<T> {\n public capture(options: T): void {\n // console.log("Analytics captured:", options);\n }\n}\n```\n\n### 4. Complete the test file\n\nEdit `tests/analytics/<Name>Analytics.spec.ts` to add meaningful tests beyond the scaffolded ones:\n\n- Keep the existing scaffolded tests (class name, capture method)\n- Add tests relevant to the specific analytics class behavior\n\nThe generated test structure follows this pattern:\n\n```typescript\nimport { describe, expect, test } from "bun:test";\nimport { <Name>Analytics } from "@/analytics/<Name>Analytics";\n\ndescribe("<Name>Analytics", () => {\n test("should have class name ending with \'Analytics\'", () => {\n expect(<Name>Analytics.name.endsWith("Analytics")).toBe(true);\n });\n\n test("should have \'capture\' method", () => {\n expect(<Name>Analytics.prototype.capture).toBeDefined();\n expect(typeof <Name>Analytics.prototype.capture).toBe("function");\n });\n});\n```\n\n### 5. Lint and format\n\nRun linting and formatting on the generated files:\n\n```bash\nbunx biome check --fix src/analytics/<Name>Analytics.ts tests/analytics/<Name>Analytics.spec.ts\n```\n';
7441
7453
 
7442
- public async set<T = unknown>(key: string, value: T, ttl?: number): Promise<void> {
7443
- throw new CacheException(\`Failed to set key "\${key}": Not implemented\`);
7444
- }
7454
+ // src/templates/claude/skills/make.cache.md.txt
7455
+ var make_cache_md_default = '---\nname: make:cache\ndescription: Generate a new cache adapter class with its test file, then complete the generated code. Use when creating a new cache adapter that implements the ICache interface from @ooneex/cache.\n---\n\n# Make Cache Class\n\nGenerate a new cache class and its test file using the `make:cache` CLI command, then complete the generated code with proper implementation.\n\n## Coding Conventions\n\n- Always explicitly show visibility on class methods and properties (`private`, `public`, `protected`)\n- Always use arrow functions when possible, but NOT for class methods (class methods use regular method syntax)\n- End type names with the `Type` suffix (e.g., `ExceptionStackFrameType`, `ServiceDataType`)\n- Start interface names with the `I` prefix (e.g., `IException`, `IService`, `IAiChat`)\n- For Entity class, if property is optional, add `null` to its type.\n- For dependencies, use `inject` from `@ooneex/container`. For example:\n\n```typescript\nimport { inject } from "@ooneex/container";\nimport { decorator, type ISeed } from "@ooneex/seeds";\n\n@decorator.seed()\nexport class BookSeed implements ISeed {\n constructor(\n @inject(BookRepository)\n private readonly repository: BookRepository,\n ) {}\n}\n```\n\n## Steps\n\n### 1. Run the generator\n\nRun the following command to scaffold the cache class and test files:\n\n```bash\nbunx @ooneex/cli@latest make:cache --name=<name> --module=<module>\n```\n\nWhere `<name>` is the name provided by the user. The `--module` option is optional \u2014 if provided, files are generated under `modules/<module>/` instead of the project root. The command will generate:\n- `src/cache/<Name>Cache.ts` - The cache class file (or `modules/<module>/src/cache/<Name>Cache.ts` with `--module`)\n- `tests/cache/<Name>Cache.spec.ts` - The test file (or `modules/<module>/tests/cache/<Name>Cache.spec.ts` with `--module`)\n\n### 2. Read the generated files\n\nRead both generated files to understand the scaffolded code.\n\n### 3. Complete the cache class\n\nEdit `src/cache/<Name>Cache.ts` to complete the implementation:\n\n- Implement the `get` method to retrieve cached values by key\n- Implement the `set` method to store values with optional TTL\n- Implement the `delete` method to remove cached entries\n- Implement the `has` method to check key existence\n- Replace the `CacheException` throws with actual cache logic\n- Inject any required dependencies (e.g., Redis client) via the constructor\n\nThe generated class structure follows this pattern:\n\n```typescript\nimport { CacheException, type ICache, decorator } from "@ooneex/cache";\n\n@decorator.cache()\nexport class <Name>Cache implements ICache {\n public async get<T = unknown>(key: string): Promise<T | undefined> {\n throw new CacheException(`Failed to get key "${key}": Not implemented`);\n }\n\n public async set<T = unknown>(key: string, value: T, ttl?: number): Promise<void> {\n throw new CacheException(`Failed to set key "${key}": Not implemented`);\n }\n\n public async delete(key: string): Promise<boolean> {\n throw new CacheException(`Failed to delete key "${key}": Not implemented`);\n }\n\n public async has(key: string): Promise<boolean> {\n throw new CacheException(`Failed to check if key "${key}" exists: Not implemented`);\n }\n}\n```\n\n### 4. Complete the test file\n\nEdit `tests/cache/<Name>Cache.spec.ts` to add meaningful tests beyond the scaffolded ones:\n\n- Keep the existing scaffolded tests (class name, get, set, delete, has methods)\n- Add tests relevant to the specific cache class behavior\n\nThe generated test structure follows this pattern:\n\n```typescript\nimport { describe, expect, test } from "bun:test";\nimport { <Name>Cache } from "@/cache/<Name>Cache";\n\ndescribe("<Name>Cache", () => {\n test("should have class name ending with \'Cache\'", () => {\n expect(<Name>Cache.name.endsWith("Cache")).toBe(true);\n });\n\n test("should have \'get\' method", () => {\n expect(<Name>Cache.prototype.get).toBeDefined();\n expect(typeof <Name>Cache.prototype.get).toBe("function");\n });\n\n test("should have \'set\' method", () => {\n expect(<Name>Cache.prototype.set).toBeDefined();\n expect(typeof <Name>Cache.prototype.set).toBe("function");\n });\n\n test("should have \'delete\' method", () => {\n expect(<Name>Cache.prototype.delete).toBeDefined();\n expect(typeof <Name>Cache.prototype.delete).toBe("function");\n });\n\n test("should have \'has\' method", () => {\n expect(<Name>Cache.prototype.has).toBeDefined();\n expect(typeof <Name>Cache.prototype.has).toBe("function");\n });\n});\n```\n\n### 5. Lint and format\n\nRun linting and formatting on the generated files:\n\n```bash\nbunx biome check --fix src/cache/<Name>Cache.ts tests/cache/<Name>Cache.spec.ts\n```\n';
7445
7456
 
7446
- public async delete(key: string): Promise<boolean> {
7447
- throw new CacheException(\`Failed to delete key "\${key}": Not implemented\`);
7448
- }
7457
+ // src/templates/claude/skills/make.controller.md.txt
7458
+ var make_controller_md_default = "---\nname: make:controller\ndescription: Generate a new controller class with route type and test file, then complete the generated code. Use when creating a new HTTP or WebSocket controller with routing, validation, and role-based access.\n---\n\n# Make Controller Class\n\nGenerate a new controller class, its route type, and test file using the `make:controller` CLI command, then complete the generated code with proper implementation.\n\n## Coding Conventions\n\n- Always explicitly show visibility on class methods and properties (`private`, `public`, `protected`)\n- Always use arrow functions when possible, but NOT for class methods (class methods use regular method syntax)\n- End type names with the `Type` suffix (e.g., `ExceptionStackFrameType`, `ServiceDataType`)\n- Start interface names with the `I` prefix (e.g., `IException`, `IService`, `IAiChat`)\n- For Entity class, if property is optional, add `null` to its type.\n- For dependencies, use `inject` from `@ooneex/container`. For example:\n\n```typescript\nimport { inject } from \"@ooneex/container\";\nimport { decorator, type ISeed } from \"@ooneex/seeds\";\n\n@decorator.seed()\nexport class BookSeed implements ISeed {\n constructor(\n @inject(BookRepository)\n private readonly repository: BookRepository,\n ) {}\n}\n```\n\n## Steps\n\n### 1. Run the generator\n\nRun the following command to scaffold the controller class and related files:\n\n```bash\nbunx @ooneex/cli@latest make:controller --name=<name> --module=<module> --is-socket=<true|false> --route-name=<route.name> --route-path=<route.path> --route-method=<route.method>\n```\n\nWhere:\n- `<name>` is the controller name provided by the user\n- `--module` is optional \u2014 if provided, files are generated under `modules/<module>/` instead of the project root\n- `--is-socket` determines HTTP vs WebSocket controller (defaults to `false`)\n- `--route-name` is the route name using dot notation: `<resource>.<action>` (e.g., `user.create`, `book.list`, `flashcard.delete`)\n- `--route-path` is the route path (e.g., `/api/users`)\n- `--route-method` is the HTTP method (e.g., `get`, `post`, `put`, `patch`, `delete`) \u2014 only for HTTP controllers\n\nThe command will generate (paths prefixed with `modules/<module>/` when `--module` is provided):\n- `src/controllers/<Name>Controller.ts` - The controller class file\n- `src/types/routes/<route.name>.ts` - The route type file (will be moved into the controller file \u2014 see step 3)\n- `tests/controllers/<Name>Controller.spec.ts` - The test file\n\n### 2. Read the generated files\n\nRead all three generated files to understand the scaffolded code.\n\n### 3. Complete the route type\n\n**IMPORTANT: Keep the route type inside the controller file**, not in a separate type file. Define the type directly in `src/controllers/<Name>Controller.ts` above the class definition. Delete the generated `src/types/routes/<route.name>.ts` file if it was created.\n\n**Remove unnecessary `params`, `payload`, and `queries` \u2014 only include what the route actually needs:**\n\n- **`params`** \u2014 Include only when the route path contains dynamic segments (e.g., `/api/users/:id`). Remove entirely for routes with no URL parameters (e.g., `/api/users`).\n- **`payload`** \u2014 Include only for methods that accept a request body (`post`, `put`, `patch`). Remove entirely for `get` and `delete` routes.\n- **`queries`** \u2014 Include only when the route supports query string filtering, pagination, or sorting (e.g., list/search endpoints). Remove entirely when not needed.\n- **`response`** \u2014 Always include.\n\nThe route type structure follows this pattern (remove unused sections):\n\n```typescript\n// Example: GET /api/users (list) \u2014 no params, no payload, has queries\ntype <TypeName>RouteType = {\n queries: {\n\n },\n response: {\n\n },\n};\n\n// Example: POST /api/users (create) \u2014 no params, has payload, no queries\ntype <TypeName>RouteType = {\n payload: {\n\n },\n response: {\n\n },\n};\n\n// Example: GET /api/users/:id (detail) \u2014 has params, no payload, no queries\ntype <TypeName>RouteType = {\n params: {\n\n },\n response: {\n\n },\n};\n\n// Example: PUT /api/users/:id (update) \u2014 has params, has payload, no queries\ntype <TypeName>RouteType = {\n params: {\n\n },\n payload: {\n\n },\n response: {\n\n },\n};\n```\n\n### 4. Complete the controller class\n\nEdit `src/controllers/<Name>Controller.ts` to complete the implementation:\n\n- Set appropriate `roles` for access control\n- Add a meaningful `description` for the route that explains what the endpoint does (e.g., `\"Create a new user account\"`, `\"List all books with pagination\"`)\n- **Keep the controller thin** \u2014 do NOT put business logic in the controller. Put all logic in the corresponding service and inject the service into the controller via the constructor. The controller's `index` method should only delegate to the service.\n- **Remove unnecessary `params`, `payload`, and `queries`** from both the route type and the `@Route` decorator \u2014 only include what the route actually needs (see step 3 rules).\n\n**Add or remove `params`, `payload`, and `queries` in the `@Route` decorator to match the route type (step 3):**\n\n- **`params`** \u2014 Include with `Assert()` validators only when the route has URL parameters. Remove the `params` key entirely otherwise.\n- **`payload`** \u2014 Include with `Assert({...})` only for `post`, `put`, `patch` methods. Remove the `payload` key entirely for `get` and `delete`.\n- **`queries`** \u2014 Include with `Assert({...})` only when query parameters are expected. Remove the `queries` key entirely otherwise.\n- **`response`** \u2014 Always include with `Assert({...})`.\n\n**HTTP controller** generated structure (remove `params`, `payload`, `queries` as needed \u2014 see rules above):\n\n```typescript\nimport type { ContextType } from \"@ooneex/controller\";\nimport { ERole } from \"@ooneex/role\";\nimport { Route } from \"@ooneex/routing\";\nimport { Assert } from \"@ooneex/validation\";\n\ntype <TypeName>RouteType = {\n // Only include params, payload, queries as needed (see step 3)\n response: {\n\n },\n};\n\n@Route.<method>(\"<route.path>\", {\n name: \"<route.name>\",\n version: 1,\n description: \"\",\n // Only include params, payload, queries as needed (see step 3)\n response: Assert({\n\n }),\n roles: [ERole.USER],\n})\nexport class <Name>Controller {\n public async index(context: ContextType<<TypeName>RouteType>) {\n return context.response.json({\n\n });\n }\n}\n```\n\n**Socket controller** generated structure (remove `params`, `payload`, `queries` as needed \u2014 see rules above):\n\n```typescript\nimport type { ContextType } from \"@ooneex/socket\";\nimport { ERole } from \"@ooneex/role\";\nimport { Route } from \"@ooneex/routing\";\nimport { Assert } from \"@ooneex/validation\";\n\ntype <TypeName>RouteType = {\n // Only include params, payload, queries as needed (see step 3)\n response: {\n\n },\n};\n\n@Route.socket(\"<route.path>\", {\n name: \"<route.name>\",\n version: 1,\n description: \"\",\n // Only include params, payload, queries as needed (see step 3)\n response: Assert({\n\n }),\n roles: [ERole.USER],\n})\nexport class <Name>Controller {\n public async index(context: ContextType<<TypeName>RouteType>) {\n return context.response.json({\n\n });\n }\n}\n```\n\n### 5. Complete the test file\n\nEdit `tests/controllers/<Name>Controller.spec.ts` to add meaningful tests beyond the scaffolded ones:\n\n- Keep the existing scaffolded tests (class name, index method)\n- Add tests relevant to the specific controller behavior\n\nThe generated test structure follows this pattern:\n\n```typescript\nimport { describe, expect, test } from \"bun:test\";\nimport { <Name>Controller } from \"@/controllers/<Name>Controller\";\n\ndescribe(\"<Name>Controller\", () => {\n test(\"should have class name ending with 'Controller'\", () => {\n expect(<Name>Controller.name.endsWith(\"Controller\")).toBe(true);\n });\n\n test(\"should have 'index' method\", () => {\n expect(<Name>Controller.prototype.index).toBeDefined();\n expect(typeof <Name>Controller.prototype.index).toBe(\"function\");\n });\n});\n```\n\n### 6. Register the controller in the module\n\nAdd the new controller to the module's `controllers` array in `src/<PascalModuleName>Module.ts` (e.g., `src/BookModule.ts` for the `book` module):\n\n```typescript\nimport type { ModuleType } from \"@ooneex/module\";\nimport { <Name>Controller } from \"./controllers/<Name>Controller\";\n\nexport const <PascalModuleName>Module: ModuleType = {\n controllers: [<Name>Controller],\n entities: [],\n middlewares: [],\n cronJobs: [],\n events: [],\n};\n```\n\nThe module file uses PascalCase naming: `<PascalModuleName>Module.ts` with export `<PascalModuleName>Module`.\n\nIf the module already has other controllers registered, append the new controller to the existing `controllers` array and add the import alongside existing imports.\n\n### 7. Lint and format\n\nRun linting and formatting on the generated files:\n\n```bash\nbunx biome check --fix src/controllers/<Name>Controller.ts tests/controllers/<Name>Controller.spec.ts\n```\n\n### 8. Create the service\n\nAfter the controller is created, generate a service class for the controller's business logic using the `make:service` skill:\n\n```\n/make:service --name=<Name>\n```\n\nWhere `<Name>` matches the controller name (e.g., if the controller is `CreateUserController`, the service is `CreateUserService`).\n\n### 9. Create the pubsub event\n\nAfter the service is created, generate a pubsub event class for the controller's domain events using the `make:pubsub` skill:\n\n```\n/make:pubsub --name=<Name> --channel=<resource>.<action>\n```\n\nWhere:\n- `<Name>` matches the controller name (e.g., if the controller is `CreateUserController`, the event is `CreateUserEvent`)\n- `<resource>.<action>` follows the same dot notation as the route name (e.g., `user.create`, `book.delete`)\n\nOnce the event is created:\n- Inject the **service** into the **event** via the constructor, and call the service's `execute` method from the event's `handler` method.\n- Inject the **event** into the **controller** via the constructor, and publish the event from the controller's `index` method.\n";
7449
7459
 
7450
- public async has(key: string): Promise<boolean> {
7451
- throw new CacheException(\`Failed to check if key "\${key}" exists: Not implemented\`);
7460
+ // src/templates/claude/skills/make.cron.md.txt
7461
+ var make_cron_md_default = '---\nname: make:cron\ndescription: Generate a new cron job class with its test file, then complete the generated code. Use when creating a new scheduled task that extends the Cron base class from @ooneex/cron.\n---\n\n# Make Cron Class\n\nGenerate a new cron class and its test file using the `make:cron` CLI command, then complete the generated code with proper implementation.\n\n## Coding Conventions\n\n- Always explicitly show visibility on class methods and properties (`private`, `public`, `protected`)\n- Always use arrow functions when possible, but NOT for class methods (class methods use regular method syntax)\n- End type names with the `Type` suffix (e.g., `ExceptionStackFrameType`, `ServiceDataType`)\n- Start interface names with the `I` prefix (e.g., `IException`, `IService`, `IAiChat`)\n- For Entity class, if property is optional, add `null` to its type.\n- For dependencies, use `inject` from `@ooneex/container`. For example:\n\n```typescript\nimport { inject } from "@ooneex/container";\nimport { decorator, type ISeed } from "@ooneex/seeds";\n\n@decorator.seed()\nexport class BookSeed implements ISeed {\n constructor(\n @inject(BookRepository)\n private readonly repository: BookRepository,\n ) {}\n}\n```\n\n## Steps\n\n### 1. Run the generator\n\nRun the following command to scaffold the cron class and test files:\n\n```bash\nbunx @ooneex/cli@latest make:cron --name=<name> --module=<module>\n```\n\nWhere `<name>` is the name provided by the user. The `--module` option is optional \u2014 if provided, files are generated under `modules/<module>/` instead of the project root. The command will generate:\n- `src/cron/<Name>Cron.ts` - The cron class file (or `modules/<module>/src/cron/<Name>Cron.ts` with `--module`)\n- `tests/cron/<Name>Cron.spec.ts` - The test file (or `modules/<module>/tests/cron/<Name>Cron.spec.ts` with `--module`)\n\n### 2. Read the generated files\n\nRead both generated files to understand the scaffolded code.\n\n### 3. Complete the cron class\n\nEdit `src/cron/<Name>Cron.ts` to complete the implementation:\n\n- Set the appropriate cron schedule in `getTime()` (e.g., `"every 5 minutes"`, `"every 1 hours"`, `"every 30 seconds"`)\n- Set the timezone in `getTimeZone()` if needed, or keep `null` for server timezone\n- Implement the `handler()` method with the actual cron job logic\n- Inject any required dependencies via the constructor\n\nThe generated class structure follows this pattern:\n\n```typescript\nimport type { TimeZoneType } from "@ooneex/country";\nimport { Cron, type CronTimeType, decorator } from "@ooneex/cron";\n\n@decorator.cron()\nexport class <Name>Cron extends Cron {\n public getTime(): CronTimeType {\n // Examples: "every 5 minutes", "every 1 hours", "every 30 seconds"\n return "every 1 hours";\n }\n\n public getTimeZone(): TimeZoneType | null {\n // Return null to use server timezone, or specify a timezone like "Europe/Paris"\n return null;\n }\n\n public async handler(): Promise<void> {\n // Implement your cron handler logic here\n // console.log("<Name>Cron handler executed");\n }\n}\n```\n\n### 4. Complete the test file\n\nEdit `tests/cron/<Name>Cron.spec.ts` to add meaningful tests beyond the scaffolded ones:\n\n- Keep the existing scaffolded tests (class name, getTime, getTimeZone, handler methods)\n- Add tests relevant to the specific cron class behavior\n\nThe generated test structure follows this pattern:\n\n```typescript\nimport { describe, expect, test } from "bun:test";\nimport { <Name>Cron } from "@/cron/<Name>Cron";\n\ndescribe("<Name>Cron", () => {\n test("should have class name ending with \'Cron\'", () => {\n expect(<Name>Cron.name.endsWith("Cron")).toBe(true);\n });\n\n test("should have \'getTime\' method", () => {\n expect(<Name>Cron.prototype.getTime).toBeDefined();\n expect(typeof <Name>Cron.prototype.getTime).toBe("function");\n });\n\n test("should have \'getTimeZone\' method", () => {\n expect(<Name>Cron.prototype.getTimeZone).toBeDefined();\n expect(typeof <Name>Cron.prototype.getTimeZone).toBe("function");\n });\n\n test("should have \'handler\' method", () => {\n expect(<Name>Cron.prototype.handler).toBeDefined();\n expect(typeof <Name>Cron.prototype.handler).toBe("function");\n });\n});\n```\n\n### 5. Register the cron job in the module\n\nAdd the new cron job to the module\'s `cronJobs` array in `src/<PascalModuleName>Module.ts` (e.g., `src/BookModule.ts` for the `book` module):\n\n```typescript\nimport type { ModuleType } from "@ooneex/module";\nimport { <Name>Cron } from "./cron/<Name>Cron";\n\nexport const <PascalModuleName>Module: ModuleType = {\n controllers: [],\n entities: [],\n middlewares: [],\n cronJobs: [<Name>Cron],\n events: [],\n};\n```\n\nThe module file uses PascalCase naming: `<PascalModuleName>Module.ts` with export `<PascalModuleName>Module`.\n\nIf the module already has other cron jobs registered, append the new cron job to the existing `cronJobs` array and add the import alongside existing imports.\n\n### 6. Lint and format\n\nRun linting and formatting on the generated files:\n\n```bash\nbunx biome check --fix src/cron/<Name>Cron.ts tests/cron/<Name>Cron.spec.ts\n```\n';
7462
+
7463
+ // src/templates/claude/skills/make.database.md.txt
7464
+ var make_database_md_default = '---\nname: make:database\ndescription: Generate a new database class with its test file, then complete the generated code. Use when creating a new database adapter that extends TypeormDatabase from @ooneex/database.\n---\n\n# Make Database Class\n\nGenerate a new database class and its test file using the `make:database` CLI command, then complete the generated code with proper implementation.\n\n## Coding Conventions\n\n- Always explicitly show visibility on class methods and properties (`private`, `public`, `protected`)\n- Always use arrow functions when possible, but NOT for class methods (class methods use regular method syntax)\n- End type names with the `Type` suffix (e.g., `ExceptionStackFrameType`, `ServiceDataType`)\n- Start interface names with the `I` prefix (e.g., `IException`, `IService`, `IAiChat`)\n- For Entity class, if property is optional, add `null` to its type.\n- For dependencies, use `inject` from `@ooneex/container`. For example:\n\n```typescript\nimport { inject } from "@ooneex/container";\nimport { decorator, type ISeed } from "@ooneex/seeds";\n\n@decorator.seed()\nexport class BookSeed implements ISeed {\n constructor(\n @inject(BookRepository)\n private readonly repository: BookRepository,\n ) {}\n}\n```\n\n## Steps\n\n### 1. Run the generator\n\nRun the following command to scaffold the database class and test files:\n\n```bash\nbunx @ooneex/cli@latest make:database --name=<name> --module=<module>\n```\n\nWhere `<name>` is the name provided by the user. The `--module` option is optional \u2014 if provided, files are generated under `modules/<module>/` instead of the project root. The command will generate:\n- `src/databases/<Name>Database.ts` - The database class file (or `modules/<module>/src/databases/<Name>Database.ts` with `--module`)\n- `tests/databases/<Name>Database.spec.ts` - The test file (or `modules/<module>/tests/databases/<Name>Database.spec.ts` with `--module`)\n\n### 2. Read the generated files\n\nRead both generated files to understand the scaffolded code.\n\n### 3. Complete the database class\n\nEdit `src/databases/<Name>Database.ts` to complete the implementation:\n\n- Add entity imports and register them in the `entities` array\n- Adjust the database path if needed (default is `"var/db"`)\n- Configure DataSource options as appropriate (type, synchronize, etc.)\n- Inject any required dependencies via the constructor\n\nThe generated class structure follows this pattern:\n\n```typescript\nimport { DataSource } from "typeorm";\nimport { TypeormDatabase, DatabaseException, decorator } from "@ooneex/database";\n\n@decorator.database()\nexport class <Name>Database extends TypeormDatabase {\n public getSource(database?: string): DataSource {\n database = database || "var/db";\n\n this.source = new DataSource({\n synchronize: false,\n entities: [\n // TODO: Load your entities here\n ],\n enableWAL: true,\n busyErrorRetry: 2000,\n busyTimeout: 30_000,\n database,\n type: "sqlite",\n });\n\n return this.source;\n }\n}\n```\n\n### 4. Complete the test file\n\nEdit `tests/databases/<Name>Database.spec.ts` to add meaningful tests beyond the scaffolded ones:\n\n- Keep the existing scaffolded tests (class name, getSource method)\n- Add tests relevant to the specific database class behavior\n\nThe generated test structure follows this pattern:\n\n```typescript\nimport { describe, expect, test } from "bun:test";\nimport { <Name>Database } from "@/databases/<Name>Database";\n\ndescribe("<Name>Database", () => {\n test("should have class name ending with \'Database\'", () => {\n expect(<Name>Database.name.endsWith("Database")).toBe(true);\n });\n\n test("should have \'getSource\' method", () => {\n expect(<Name>Database.prototype.getSource).toBeDefined();\n expect(typeof <Name>Database.prototype.getSource).toBe("function");\n });\n});\n```\n\n### 5. Lint and format\n\nRun linting and formatting on the generated files:\n\n```bash\nbunx biome check --fix src/databases/<Name>Database.ts tests/databases/<Name>Database.spec.ts\n```\n';
7465
+
7466
+ // src/templates/claude/skills/make.entity.md.txt
7467
+ var make_entity_md_default = "---\nname: make:entity\ndescription: Generate a new TypeORM entity class with its test file, then complete the generated code. Use when creating a new database entity with columns, relations, and table mapping.\n---\n\n# Make Entity Class\n\nGenerate a new entity class and its test file using the `make:entity` CLI command, then complete the generated code with proper implementation.\n\n## Coding Conventions\n\n- Always explicitly show visibility on class methods and properties (`private`, `public`, `protected`)\n- Always use arrow functions when possible, but NOT for class methods (class methods use regular method syntax)\n- End type names with the `Type` suffix (e.g., `ExceptionStackFrameType`, `ServiceDataType`)\n- Start interface names with the `I` prefix (e.g., `IException`, `IService`, `IAiChat`)\n- For Entity class, if property is optional, add `null` to its type.\n- Avoid non-null assertions (`!`). For example, `public name!: string;` should be `public name: string;`\n- For dependencies, use `inject` from `@ooneex/container`. For example:\n\n```typescript\nimport { inject } from \"@ooneex/container\";\nimport { decorator, type ISeed } from \"@ooneex/seeds\";\n\n@decorator.seed()\nexport class BookSeed implements ISeed {\n constructor(\n @inject(BookRepository)\n private readonly repository: BookRepository,\n ) {}\n}\n```\n\n## Steps\n\n### 1. Run the generator\n\nRun the following command to scaffold the entity class and test files:\n\n```bash\nbunx @ooneex/cli@latest make:entity --name=<name> --module=<module> --table-name=<table_name>\n```\n\nWhere `<name>` is the name provided by the user. The `--table-name` option is optional \u2014 if omitted, it defaults to the snake_case pluralized form of the name (e.g., `UserProfile` becomes `user_profiles`). The `--module` option is optional \u2014 if provided, files are generated under `modules/<module>/` instead of the project root. The command will generate:\n- `src/entities/<Name>Entity.ts` - The entity class file (or `modules/<module>/src/entities/<Name>Entity.ts` with `--module`)\n- `tests/entities/<Name>Entity.spec.ts` - The test file (or `modules/<module>/tests/entities/<Name>Entity.spec.ts` with `--module`)\n\n### 2. Read the generated files\n\nRead both generated files to understand the scaffolded code.\n\n### 3. Complete the entity class\n\nEdit `src/entities/<Name>Entity.ts` to complete the implementation:\n\n- Add entity-specific columns with appropriate TypeORM decorators (`@Column`)\n- Add relations if needed (`@ManyToOne`, `@OneToMany`, `@ManyToMany`, etc.)\n- Remove any scaffolded columns that are not relevant to the entity\n- Adjust column types, lengths, and constraints as needed\n\nThe generated class structure follows this pattern:\n\n```typescript\nimport type { LocaleType } from \"@ooneex/translation\";\nimport { random } from \"@ooneex/utils\";\nimport { Column, CreateDateColumn, DeleteDateColumn, PrimaryColumn, UpdateDateColumn } from \"typeorm\";\n\n@Entity({\n name: \"<table_name>\",\n})\nexport class <Name>Entity extends BaseEntity {\n @PrimaryColumn({ name: \"id\", type: \"varchar\", length: 25 })\n id: string = random.nanoid(25);\n\n @Column({\n name: \"is_locked\",\n type: \"boolean\",\n default: false,\n nullable: true,\n })\n isLocked?: boolean;\n\n @Column({ name: \"locked_at\", type: \"timestamptz\", nullable: true })\n lockedAt?: Date;\n\n @Column({\n name: \"is_blocked\",\n type: \"boolean\",\n default: false,\n nullable: true,\n })\n isBlocked?: boolean;\n\n @Column({ name: \"blocked_at\", type: \"timestamptz\", nullable: true })\n blockedAt?: Date;\n\n @Column({ name: \"block_reason\", type: \"text\", nullable: true })\n blockReason?: string;\n\n @Column({ name: \"is_public\", type: \"boolean\", default: true, nullable: true })\n isPublic?: boolean;\n\n @Column({ name: \"lang\", type: \"varchar\", length: 10, nullable: true })\n lang?: LocaleType;\n\n @CreateDateColumn({ name: \"created_at\" })\n createdAt?: Date;\n\n @UpdateDateColumn({ name: \"updated_at\" })\n updatedAt?: Date;\n\n @DeleteDateColumn({ name: \"deleted_at\" })\n deletedAt?: Date;\n}\n```\n\n### 4. Complete the test file\n\nEdit `tests/entities/<Name>Entity.spec.ts` to add meaningful tests beyond the scaffolded ones:\n\n- Keep the existing scaffolded tests (class name, id, default columns)\n- Add tests for any new entity-specific columns and relations\n- Update tests if scaffolded columns were removed\n\nThe generated test structure follows this pattern:\n\n```typescript\nimport { describe, expect, test } from \"bun:test\";\nimport { <Name>Entity } from \"@/entities/<Name>Entity\";\n\ndescribe(\"<Name>Entity\", () => {\n test(\"should have class name ending with 'Entity'\", () => {\n expect(<Name>Entity.name.endsWith(\"Entity\")).toBe(true);\n });\n\n test(\"should have 'id' property with default nanoid\", () => {\n const entity = new <Name>Entity();\n expect(entity.id).toBeDefined();\n expect(typeof entity.id).toBe(\"string\");\n expect(entity.id.length).toBe(25);\n });\n\n test(\"should have 'isLocked' property\", () => {\n const entity = new <Name>Entity();\n expect(\"isLocked\" in entity).toBe(true);\n });\n\n // ... additional property tests\n});\n```\n\n### 5. Register the entity in the module\n\nAdd the new entity to the module's `entities` array in `src/<PascalModuleName>Module.ts` (e.g., `src/BookModule.ts` for the `book` module):\n\n```typescript\nimport type { ModuleType } from \"@ooneex/module\";\nimport { <Name>Entity } from \"./entities/<Name>Entity\";\n\nexport const <PascalModuleName>Module: ModuleType = {\n controllers: [],\n entities: [<Name>Entity],\n middlewares: [],\n cronJobs: [],\n events: [],\n};\n```\n\nThe module file uses PascalCase naming: `<PascalModuleName>Module.ts` with export `<PascalModuleName>Module`.\n\nIf the module already has other entities registered, append the new entity to the existing `entities` array and add the import alongside existing imports.\n\n### 6. Create a migration for the entity\n\nAfter creating or updating an entity, generate a migration to apply the corresponding schema changes to the database.\n\nRun the generator:\n\n```bash\nbunx @ooneex/cli@latest make:migration\n```\n\nThen read the generated migration file in `src/migrations/` and complete it:\n\n- In the `up` method, write the SQL to create the table (or alter it if updating an existing entity). Include all columns, types, constraints, defaults, and indexes matching the entity definition.\n- In the `down` method, write the reverse SQL to undo the changes (e.g., `DROP TABLE` or `ALTER TABLE DROP COLUMN`).\n- If the migration depends on another migration (e.g., a foreign key referencing another table), add that migration class to the `getDependencies()` return array.\n\nExample `up` method for a new entity:\n\n```typescript\npublic async up(tx: TransactionSQL): Promise<void> {\n await tx`\n CREATE TABLE IF NOT EXISTS <table_name> (\n id VARCHAR(25) PRIMARY KEY,\n is_locked BOOLEAN DEFAULT false,\n locked_at TIMESTAMPTZ,\n is_blocked BOOLEAN DEFAULT false,\n blocked_at TIMESTAMPTZ,\n block_reason TEXT,\n is_public BOOLEAN DEFAULT true,\n lang VARCHAR(10),\n created_at TIMESTAMPTZ DEFAULT NOW(),\n updated_at TIMESTAMPTZ DEFAULT NOW(),\n deleted_at TIMESTAMPTZ\n )\n `;\n}\n```\n\n### 7. Create a repository for the entity\n\nAfter creating the entity, generate a repository to handle database operations for it.\n\nRun the generator:\n\n```bash\nbunx @ooneex/cli@latest make:repository --name=<name>\n```\n\nWhere `<name>` is the same name used for the entity. The command will generate:\n- `src/repositories/<Name>Repository.ts` - The repository class\n- `tests/repositories/<Name>Repository.spec.ts` - The test file\n\nThen read the generated files and complete the repository implementation:\n\n- Adjust search fields in the `find()` method to match the entity's searchable columns\n- Customize relations loading in `findOne`/`findOneBy` if the entity has relations\n- Add any domain-specific methods relevant to the entity\n- Remove methods that don't apply to the entity\n- Update tests to match the final repository methods\n\n### 8. Lint and format\n\nRun linting and formatting on all generated files:\n\n```bash\nbunx biome check --fix src/entities/<Name>Entity.ts tests/entities/<Name>Entity.spec.ts src/repositories/<Name>Repository.ts tests/repositories/<Name>Repository.spec.ts src/migrations/\n```\n";
7468
+
7469
+ // src/templates/claude/skills/make.logger.md.txt
7470
+ var make_logger_md_default = '---\nname: make:logger\ndescription: Generate a new logger class with its test file, then complete the generated code. Use when creating a new logger that implements the ILogger interface from @ooneex/logger.\n---\n\n# Make Logger Class\n\nGenerate a new logger class and its test file using the `make:logger` CLI command, then complete the generated code with proper implementation.\n\n## Coding Conventions\n\n- Always explicitly show visibility on class methods and properties (`private`, `public`, `protected`)\n- Always use arrow functions when possible, but NOT for class methods (class methods use regular method syntax)\n- End type names with the `Type` suffix (e.g., `ExceptionStackFrameType`, `ServiceDataType`)\n- Start interface names with the `I` prefix (e.g., `IException`, `IService`, `IAiChat`)\n- For Entity class, if property is optional, add `null` to its type.\n- For dependencies, use `inject` from `@ooneex/container`. For example:\n\n```typescript\nimport { inject } from "@ooneex/container";\nimport { decorator, type ISeed } from "@ooneex/seeds";\n\n@decorator.seed()\nexport class BookSeed implements ISeed {\n constructor(\n @inject(BookRepository)\n private readonly repository: BookRepository,\n ) {}\n}\n```\n\n## Steps\n\n### 1. Run the generator\n\nRun the following command to scaffold the logger class and test files:\n\n```bash\nbunx @ooneex/cli@latest make:logger --name=<name> --module=<module>\n```\n\nWhere `<name>` is the name provided by the user. The `--module` option is optional \u2014 if provided, files are generated under `modules/<module>/` instead of the project root. The command will generate:\n- `src/loggers/<Name>Logger.ts` - The logger class file (or `modules/<module>/src/loggers/<Name>Logger.ts` with `--module`)\n- `tests/loggers/<Name>Logger.spec.ts` - The test file (or `modules/<module>/tests/loggers/<Name>Logger.spec.ts` with `--module`)\n\n### 2. Read the generated files\n\nRead both generated files to understand the scaffolded code.\n\n### 3. Complete the logger class\n\nEdit `src/loggers/<Name>Logger.ts` to complete the implementation:\n\n- Implement the `init()` method to set up the logger (e.g., open file handles, configure transports)\n- Implement `log`, `debug`, `info`, `success`, `warn`, and `error` methods with actual logging logic\n- Inject any required dependencies via the constructor\n\nThe generated class structure follows this pattern:\n\n```typescript\nimport type { IException } from "@ooneex/exception";\nimport { type ILogger, decorator } from "@ooneex/logger";\nimport type { ScalarType } from "@ooneex/types";\n\n@decorator.logger()\nexport class <Name>Logger implements ILogger {\n public async init(): Promise<void> {\n // Initialize your logger here\n }\n\n public log(message: string, data?: Record<string, ScalarType>): void {\n // Handle general logging\n }\n\n public debug(message: string, data?: Record<string, ScalarType>): void {\n // Handle debug logging\n }\n\n public info(message: string, data?: Record<string, ScalarType>): void {\n // Handle info logging\n }\n\n public success(message: string, data?: Record<string, ScalarType>): void {\n // Handle success logging\n }\n\n public warn(message: string, data?: Record<string, ScalarType>): void {\n // Handle warning logging\n }\n\n public error(message: string | IException, data?: Record<string, ScalarType>): void {\n // Handle error logging\n }\n}\n```\n\n### 4. Complete the test file\n\nEdit `tests/loggers/<Name>Logger.spec.ts` to add meaningful tests beyond the scaffolded ones:\n\n- Keep the existing scaffolded tests (class name, init, log, debug, info, success, warn, error methods)\n- Add tests relevant to the specific logger class behavior\n\nThe generated test structure follows this pattern:\n\n```typescript\nimport { describe, expect, test } from "bun:test";\nimport { <Name>Logger } from "@/loggers/<Name>Logger";\n\ndescribe("<Name>Logger", () => {\n test("should have class name ending with \'Logger\'", () => {\n expect(<Name>Logger.name.endsWith("Logger")).toBe(true);\n });\n\n test("should have \'init\' method", () => {\n expect(<Name>Logger.prototype.init).toBeDefined();\n expect(typeof <Name>Logger.prototype.init).toBe("function");\n });\n\n test("should have \'log\' method", () => {\n expect(<Name>Logger.prototype.log).toBeDefined();\n expect(typeof <Name>Logger.prototype.log).toBe("function");\n });\n\n test("should have \'debug\' method", () => {\n expect(<Name>Logger.prototype.debug).toBeDefined();\n expect(typeof <Name>Logger.prototype.debug).toBe("function");\n });\n\n test("should have \'info\' method", () => {\n expect(<Name>Logger.prototype.info).toBeDefined();\n expect(typeof <Name>Logger.prototype.info).toBe("function");\n });\n\n test("should have \'success\' method", () => {\n expect(<Name>Logger.prototype.success).toBeDefined();\n expect(typeof <Name>Logger.prototype.success).toBe("function");\n });\n\n test("should have \'warn\' method", () => {\n expect(<Name>Logger.prototype.warn).toBeDefined();\n expect(typeof <Name>Logger.prototype.warn).toBe("function");\n });\n\n test("should have \'error\' method", () => {\n expect(<Name>Logger.prototype.error).toBeDefined();\n expect(typeof <Name>Logger.prototype.error).toBe("function");\n });\n});\n```\n\n### 5. Lint and format\n\nRun linting and formatting on the generated files:\n\n```bash\nbunx biome check --fix src/loggers/<Name>Logger.ts tests/loggers/<Name>Logger.spec.ts\n```\n';
7471
+
7472
+ // src/templates/claude/skills/make.mailer.md.txt
7473
+ var make_mailer_md_default = '---\nname: make:mailer\ndescription: Generate a new mailer class with its template and test files, then complete the generated code. Use when creating a new email sender with JSX template using @ooneex/mailer.\n---\n\n# Make Mailer Class\n\nGenerate a new mailer class, its JSX template, and test files using the `make:mailer` CLI command, then complete the generated code with proper implementation.\n\n## Coding Conventions\n\n- Always explicitly show visibility on class methods and properties (`private`, `public`, `protected`)\n- Always use arrow functions when possible, but NOT for class methods (class methods use regular method syntax)\n- End type names with the `Type` suffix (e.g., `ExceptionStackFrameType`, `ServiceDataType`)\n- Start interface names with the `I` prefix (e.g., `IException`, `IService`, `IAiChat`)\n- For Entity class, if property is optional, add `null` to its type.\n- For dependencies, use `inject` from `@ooneex/container`. For example:\n\n```typescript\nimport { inject } from "@ooneex/container";\nimport { decorator, type ISeed } from "@ooneex/seeds";\n\n@decorator.seed()\nexport class BookSeed implements ISeed {\n constructor(\n @inject(BookRepository)\n private readonly repository: BookRepository,\n ) {}\n}\n```\n\n## Steps\n\n### 1. Run the generator\n\nRun the following command to scaffold the mailer class and test files:\n\n```bash\nbunx @ooneex/cli@latest make:mailer --name=<name> --module=<module>\n```\n\nWhere `<name>` is the name provided by the user. The `--module` option is optional \u2014 if provided, files are generated under `modules/<module>/` instead of the project root. The command will generate (paths prefixed with `modules/<module>/` when `--module` is provided):\n- `src/mailers/<Name>Mailer.ts` - The mailer class file\n- `src/mailers/<Name>MailerTemplate.tsx` - The JSX email template\n- `tests/mailers/<Name>Mailer.spec.ts` - The mailer test file\n- `tests/mailers/<Name>MailerTemplate.spec.ts` - The template test file\n\n### 2. Read the generated files\n\nRead all four generated files to understand the scaffolded code.\n\n### 3. Complete the mailer class\n\nEdit `src/mailers/<Name>Mailer.ts` to complete the implementation:\n\n- Adjust the `send` method config type if additional parameters are needed\n- Add any pre-send logic (validation, data transformation, etc.)\n\nThe generated class structure follows this pattern:\n\n```typescript\nimport { inject } from "@ooneex/container";\nimport type { IMailer } from "@ooneex/mailer";\nimport { type <Name>MailerPropsType, <Name>MailerTemplate } from "./<Name>MailerTemplate";\n\nexport class <Name>Mailer implements IMailer {\n constructor(\n @inject("mailer")\n private readonly mailer: IMailer,\n ) {}\n\n public send = async (config: {\n to: string[];\n subject: string;\n from?: { name: string; address: string };\n data?: <Name>MailerPropsType;\n }): Promise<void> => {\n const { data, ...rest } = config;\n\n await this.mailer.send({\n ...rest,\n content: <Name>MailerTemplate(data),\n });\n };\n}\n```\n\n### 4. Complete the mailer template\n\nEdit `src/mailers/<Name>MailerTemplate.tsx` to complete the implementation:\n\n- Update `<Name>MailerPropsType` with the actual props needed for the email\n- Build the email body using `MailerLayout` components (Header, Body, Footer)\n- Add email content, styling, and dynamic data rendering\n\nThe generated template structure follows this pattern:\n\n```tsx\nimport { MailerLayout } from "@ooneex/mailer";\n\nexport type <Name>MailerPropsType = {\n link: string;\n};\n\nexport const <Name>MailerTemplate = (props?: <Name>MailerPropsType) => (\n <MailerLayout>\n <MailerLayout.Header />\n <MailerLayout.Body>\n <a href={props?.link}>Login</a>\n </MailerLayout.Body>\n <MailerLayout.Footer />\n </MailerLayout>\n);\n```\n\n### 5. Complete the test files\n\nEdit `tests/mailers/<Name>Mailer.spec.ts` and `tests/mailers/<Name>MailerTemplate.spec.ts` to add meaningful tests beyond the scaffolded ones.\n\n### 6. Lint and format\n\nRun linting and formatting on the generated files:\n\n```bash\nbunx biome check --fix src/mailers/<Name>Mailer.ts src/mailers/<Name>MailerTemplate.tsx tests/mailers/<Name>Mailer.spec.ts tests/mailers/<Name>MailerTemplate.spec.ts\n```\n';
7474
+
7475
+ // src/templates/claude/skills/make.middleware.md.txt
7476
+ var make_middleware_md_default = '---\nname: make:middleware\ndescription: Generate a new middleware class with its test file, then complete the generated code. Use when creating a new HTTP or WebSocket middleware that implements IMiddleware from @ooneex/middleware.\n---\n\n# Make Middleware Class\n\nGenerate a new middleware class and its test file using the `make:middleware` CLI command, then complete the generated code with proper implementation.\n\n## Coding Conventions\n\n- Always explicitly show visibility on class methods and properties (`private`, `public`, `protected`)\n- Always use arrow functions when possible, but NOT for class methods (class methods use regular method syntax)\n- End type names with the `Type` suffix (e.g., `ExceptionStackFrameType`, `ServiceDataType`)\n- Start interface names with the `I` prefix (e.g., `IException`, `IService`, `IAiChat`)\n- For Entity class, if property is optional, add `null` to its type.\n- For dependencies, use `inject` from `@ooneex/container`. For example:\n\n```typescript\nimport { inject } from "@ooneex/container";\nimport { decorator, type ISeed } from "@ooneex/seeds";\n\n@decorator.seed()\nexport class BookSeed implements ISeed {\n constructor(\n @inject(BookRepository)\n private readonly repository: BookRepository,\n ) {}\n}\n```\n\n## Steps\n\n### 1. Run the generator\n\nRun the following command to scaffold the middleware class and test files:\n\n```bash\nbunx @ooneex/cli@latest make:middleware --name=<name> --module=<module> --is-socket=<true|false>\n```\n\nWhere `<name>` is the name provided by the user. The `--module` option is optional \u2014 if provided, files are generated under `modules/<module>/` instead of the project root. The `--is-socket` option determines whether to generate an HTTP middleware or a WebSocket middleware (defaults to `false` if omitted). The command will generate:\n- `src/middlewares/<Name>Middleware.ts` - The middleware class file (or `modules/<module>/src/middlewares/<Name>Middleware.ts` with `--module`)\n- `tests/middlewares/<Name>Middleware.spec.ts` - The test file (or `modules/<module>/tests/middlewares/<Name>Middleware.spec.ts` with `--module`)\n\n### 2. Read the generated files\n\nRead both generated files to understand the scaffolded code.\n\n### 3. Complete the middleware class\n\nEdit `src/middlewares/<Name>Middleware.ts` to complete the implementation:\n\n- Implement the `handler` method with actual middleware logic\n- Add request/response transformations, authentication checks, logging, etc.\n- Inject any required dependencies via the constructor\n\n**HTTP middleware** generated structure:\n\n```typescript\nimport type { ContextType } from "@ooneex/controller";\nimport { decorator, type IMiddleware } from "@ooneex/middleware";\n\n@decorator.middleware()\nexport class <Name>Middleware implements IMiddleware {\n public async handler(context: ContextType): Promise<ContextType> {\n // Example: Add custom header\n // context.response.header("X-Custom-Header", "value");\n\n return context\n }\n}\n```\n\n**Socket middleware** generated structure:\n\n```typescript\nimport type { ContextType } from "@ooneex/socket";\nimport { decorator, type IMiddleware } from "@ooneex/middleware";\n\n@decorator.middleware()\nexport class <Name>Middleware implements IMiddleware {\n public async handler(context: ContextType): Promise<ContextType> {\n // Example: Add custom header\n // context.response.header("X-Custom-Header", "value");\n\n return context\n }\n}\n```\n\n### 4. Complete the test file\n\nEdit `tests/middlewares/<Name>Middleware.spec.ts` to add meaningful tests beyond the scaffolded ones:\n\n- Keep the existing scaffolded tests (class name, handler method)\n- Add tests relevant to the specific middleware behavior\n\nThe generated test structure follows this pattern:\n\n```typescript\nimport { describe, expect, test } from "bun:test";\nimport { <Name>Middleware } from "@/middlewares/<Name>Middleware";\n\ndescribe("<Name>Middleware", () => {\n test("should have class name ending with \'Middleware\'", () => {\n expect(<Name>Middleware.name.endsWith("Middleware")).toBe(true);\n });\n\n test("should have \'handler\' method", () => {\n expect(<Name>Middleware.prototype.handler).toBeDefined();\n expect(typeof <Name>Middleware.prototype.handler).toBe("function");\n });\n});\n```\n\n### 5. Register the middleware in the module\n\nAdd the new middleware to the module\'s `middlewares` array in `src/<PascalModuleName>Module.ts` (e.g., `src/BookModule.ts` for the `book` module):\n\n```typescript\nimport type { ModuleType } from "@ooneex/module";\nimport { <Name>Middleware } from "./middlewares/<Name>Middleware";\n\nexport const <PascalModuleName>Module: ModuleType = {\n controllers: [],\n entities: [],\n middlewares: [<Name>Middleware],\n cronJobs: [],\n events: [],\n};\n```\n\nThe module file uses PascalCase naming: `<PascalModuleName>Module.ts` with export `<PascalModuleName>Module`.\n\nIf the module already has other middlewares registered, append the new middleware to the existing `middlewares` array and add the import alongside existing imports.\n\n### 6. Lint and format\n\nRun linting and formatting on the generated files:\n\n```bash\nbunx biome check --fix src/middlewares/<Name>Middleware.ts tests/middlewares/<Name>Middleware.spec.ts\n```\n';
7477
+
7478
+ // src/templates/claude/skills/make.migration.md.txt
7479
+ var make_migration_md_default = '---\nname: make:migration\ndescription: Generate a new database migration file with its test file, then complete the generated code. Use when creating a new database migration for schema changes using @ooneex/migrations.\n---\n\n# Make Migration\n\nGenerate a new migration file and its test file using the `make:migration` CLI command, then complete the generated code with proper implementation.\n\n## Coding Conventions\n\n- Always explicitly show visibility on class methods and properties (`private`, `public`, `protected`)\n- Always use arrow functions when possible, but NOT for class methods (class methods use regular method syntax)\n- End type names with the `Type` suffix (e.g., `ExceptionStackFrameType`, `ServiceDataType`)\n- Start interface names with the `I` prefix (e.g., `IException`, `IService`, `IAiChat`)\n- For Entity class, if property is optional, add `null` to its type.\n- For dependencies, use `inject` from `@ooneex/container`. For example:\n\n```typescript\nimport { inject } from "@ooneex/container";\nimport { decorator, type ISeed } from "@ooneex/seeds";\n\n@decorator.seed()\nexport class BookSeed implements ISeed {\n constructor(\n @inject(BookRepository)\n private readonly repository: BookRepository,\n ) {}\n}\n```\n\n## Steps\n\n### 1. Run the generator\n\nRun the following command to scaffold the migration file and test files:\n\n```bash\nbunx @ooneex/cli@latest make:migration --module=<module>\n```\n\nThe `--module` option is optional \u2014 if provided, files are generated under `modules/<module>/` instead of the project root. The command will generate:\n- `src/migrations/Migration<version>.ts` - The migration class file (or `modules/<module>/src/migrations/Migration<version>.ts` with `--module`)\n- `tests/migrations/Migration<version>.spec.ts` - The test file (or `modules/<module>/tests/migrations/Migration<version>.spec.ts` with `--module`)\n\nIt will also:\n- Generate a `migrations.ts` root export file in the migrations directory\n\n### 2. Read the generated files\n\nRead both generated files to understand the scaffolded code.\n\n### 3. Complete the migration\n\nEdit `src/migrations/Migration<version>.ts` to implement:\n\n- The `up` method with the schema changes (create tables, add columns, create indexes, etc.)\n- The `down` method with the reverse operations to undo the migration\n\n### 4. Complete the test file\n\nEdit `tests/migrations/Migration<version>.spec.ts` to add meaningful tests beyond the scaffolded ones:\n\n- Keep the existing scaffolded tests (class name, up, down, getVersion, getDependencies methods)\n- Add tests relevant to the specific migration behavior\n\nThe generated test structure follows this pattern:\n\n```typescript\nimport { describe, expect, test } from "bun:test";\nimport { Migration<version> } from "@/migrations/Migration<version>";\n\ndescribe("Migration<version>", () => {\n test("should have class name starting with \'Migration\'", () => {\n expect(Migration<version>.name.startsWith("Migration")).toBe(true);\n });\n\n test("should have \'up\' method", () => {\n expect(Migration<version>.prototype.up).toBeDefined();\n expect(typeof Migration<version>.prototype.up).toBe("function");\n });\n\n test("should have \'down\' method", () => {\n expect(Migration<version>.prototype.down).toBeDefined();\n expect(typeof Migration<version>.prototype.down).toBe("function");\n });\n\n test("should have \'getVersion\' method", () => {\n expect(Migration<version>.prototype.getVersion).toBeDefined();\n expect(typeof Migration<version>.prototype.getVersion).toBe("function");\n });\n\n test("should have \'getDependencies\' method", () => {\n expect(Migration<version>.prototype.getDependencies).toBeDefined();\n expect(typeof Migration<version>.prototype.getDependencies).toBe("function");\n });\n});\n```\n\n### 5. Lint and format\n\nRun linting and formatting on the generated files:\n\n```bash\nbunx biome check --fix src/migrations/Migration<version>.ts tests/migrations/Migration<version>.spec.ts\n```\n';
7480
+
7481
+ // src/templates/claude/skills/make.permission.md.txt
7482
+ var make_permission_md_default = '---\nname: make:permission\ndescription: Generate a new permission class with its test file, then complete the generated code. Use when creating a new permission that extends Permission from @ooneex/permission.\n---\n\n# Make Permission Class\n\nGenerate a new permission class and its test file using the `make:permission` CLI command, then complete the generated code with proper implementation.\n\n## Coding Conventions\n\n- Always explicitly show visibility on class methods and properties (`private`, `public`, `protected`)\n- Always use arrow functions when possible, but NOT for class methods (class methods use regular method syntax)\n- End type names with the `Type` suffix (e.g., `ExceptionStackFrameType`, `ServiceDataType`)\n- Start interface names with the `I` prefix (e.g., `IException`, `IService`, `IAiChat`)\n- For Entity class, if property is optional, add `null` to its type.\n- For dependencies, use `inject` from `@ooneex/container`. For example:\n\n```typescript\nimport { inject } from "@ooneex/container";\nimport { decorator, type ISeed } from "@ooneex/seeds";\n\n@decorator.seed()\nexport class BookSeed implements ISeed {\n constructor(\n @inject(BookRepository)\n private readonly repository: BookRepository,\n ) {}\n}\n```\n\n## Steps\n\n### 1. Run the generator\n\nRun the following command to scaffold the permission class and test files:\n\n```bash\nbunx @ooneex/cli@latest make:permission --name=<name> --module=<module>\n```\n\nWhere `<name>` is the name provided by the user. The `--module` option is optional \u2014 if provided, files are generated under `modules/<module>/` instead of the project root. The command will generate:\n- `src/permissions/<Name>Permission.ts` - The permission class file (or `modules/<module>/src/permissions/<Name>Permission.ts` with `--module`)\n- `tests/permissions/<Name>Permission.spec.ts` - The test file (or `modules/<module>/tests/permissions/<Name>Permission.spec.ts` with `--module`)\n\n### 2. Read the generated files\n\nRead both generated files to understand the scaffolded code.\n\n### 3. Complete the permission class\n\nEdit `src/permissions/<Name>Permission.ts` to complete the implementation:\n\n- Implement the `allow()` method with permission rules using `this.ability.can()`\n- Implement the `setUserPermissions()` method with role-based permission logic\n- Define which actions (read, create, update, delete, manage) are allowed on which entities\n\nThe generated class structure follows this pattern:\n\n```typescript\nimport { decorator, Permission } from "@ooneex/permission";\nimport type { IUser } from "@ooneex/user";\n\n@decorator.permission()\nexport class <Name>Permission extends Permission {\n public allow(): this {\n // Example: Add permissions using this.ability.can()\n // this.ability.can("read", "YourEntity");\n // this.ability.can(["read", "update"], "YourEntity", { userId: user.id });\n\n return this;\n }\n\n public setUserPermissions(user: IUser | null): this {\n if (!user) {\n return this;\n }\n\n // Example: Grant full access to admins\n // const { roles } = user;\n // if (roles.includes(ERole.ADMIN)) {\n // this.ability.can("manage", "all");\n // return this;\n // }\n\n // Example: Grant specific permissions based on roles\n // for (const role of roles) {\n // if (role === ERole.USER) {\n // this.ability.can(["read", "update"], "YourEntity", { userId: user.id });\n // }\n //\n // if (role === ERole.GUEST) {\n // this.ability.can("read", "YourEntity", { public: true });\n // }\n // }\n\n return this;\n }\n}\n```\n\n### 4. Complete the test file\n\nEdit `tests/permissions/<Name>Permission.spec.ts` to add meaningful tests beyond the scaffolded ones:\n\n- Keep the existing scaffolded tests (class name, extends Permission, allow, setUserPermissions methods)\n- Add tests relevant to the specific permission rules\n\nThe generated test structure follows this pattern:\n\n```typescript\nimport { describe, expect, test } from "bun:test";\nimport { Permission } from "@ooneex/permission";\nimport { <Name>Permission } from "@/permissions/<Name>Permission";\n\ndescribe("<Name>Permission", () => {\n test("should have class name ending with \'Permission\'", () => {\n expect(<Name>Permission.name.endsWith("Permission")).toBe(true);\n });\n\n test("should extend Permission", () => {\n const permission = new <Name>Permission();\n expect(permission).toBeInstanceOf(Permission);\n });\n\n test("should have \'allow\' method", () => {\n expect(<Name>Permission.prototype.allow).toBeDefined();\n expect(typeof <Name>Permission.prototype.allow).toBe("function");\n });\n\n test("should have \'setUserPermissions\' method", () => {\n expect(<Name>Permission.prototype.setUserPermissions).toBeDefined();\n expect(typeof <Name>Permission.prototype.setUserPermissions).toBe("function");\n });\n});\n```\n\n### 5. Register the permission in the module\n\nThe permission class is standalone and does not need to be registered in a module.\n\n### 6. Lint and format\n\nRun linting and formatting on the generated files:\n\n```bash\nbunx biome check --fix src/permissions/<Name>Permission.ts tests/permissions/<Name>Permission.spec.ts\n```\n';
7483
+
7484
+ // src/templates/claude/skills/make.pubsub.md.txt
7485
+ var make_pubsub_md_default = '---\nname: make:pubsub\ndescription: Generate a new PubSub event class with its test file, then complete the generated code. Use when creating a new publish/subscribe event that extends PubSub from @ooneex/pub-sub.\n---\n\n# Make PubSub Event Class\n\nGenerate a new PubSub event class and its test file using the `make:pubsub` CLI command, then complete the generated code with proper implementation.\n\n## Coding Conventions\n\n- Always explicitly show visibility on class methods and properties (`private`, `public`, `protected`)\n- Always use arrow functions when possible, but NOT for class methods (class methods use regular method syntax)\n- End type names with the `Type` suffix (e.g., `ExceptionStackFrameType`, `ServiceDataType`)\n- Start interface names with the `I` prefix (e.g., `IException`, `IService`, `IAiChat`)\n- For Entity class, if property is optional, add `null` to its type.\n- For dependencies, use `inject` from `@ooneex/container`. For example:\n\n```typescript\nimport { inject } from "@ooneex/container";\nimport { decorator, type ISeed } from "@ooneex/seeds";\n\n@decorator.seed()\nexport class BookSeed implements ISeed {\n constructor(\n @inject(BookRepository)\n private readonly repository: BookRepository,\n ) {}\n}\n```\n\n## Steps\n\n### 1. Run the generator\n\nRun the following command to scaffold the PubSub event class and test files:\n\n```bash\nbunx @ooneex/cli@latest make:pubsub --name=<name> --module=<module> --channel=<channel>\n```\n\nWhere `<name>` is the name provided by the user. The `--module` option is optional \u2014 if provided, files are generated under `modules/<module>/` instead of the project root. The `--channel` option is optional \u2014 if omitted, it defaults to the kebab-case form of the name (e.g., `UserCreated` becomes `user-created`). The command will generate:\n- `src/events/<Name>Event.ts` - The event class file (or `modules/<module>/src/events/<Name>Event.ts` with `--module`)\n- `tests/events/<Name>Event.spec.ts` - The test file (or `modules/<module>/tests/events/<Name>Event.spec.ts` with `--module`)\n\n### 2. Read the generated files\n\nRead both generated files to understand the scaffolded code.\n\n### 3. Complete the PubSub event class\n\nEdit `src/events/<Name>Event.ts` to complete the implementation:\n\n- Define a proper data type instead of `Record<string, ScalarType>` for the event payload\n- Implement the `handler()` method with actual event handling logic\n- Set the appropriate channel name in `getChannel()`\n\nThe generated class structure follows this pattern:\n\n```typescript\nimport { inject } from "@ooneex/container";\nimport type { ScalarType } from "@ooneex/types";\nimport { decorator, PubSub, RedisPubSub } from "@ooneex/pub-sub";\n\n@decorator.pubSub()\nexport class <Name>Event<Data extends Record<string, ScalarType> = Record<string, ScalarType>> extends PubSub<Data> {\n constructor(\n @inject(RedisPubSub)\n client: RedisPubSub<Data>,\n ) {\n super(client);\n }\n\n public getChannel(): string {\n return "<channel>";\n }\n\n public async handler(context: { data: Data; channel: string }): Promise<void> {\n console.log(context);\n // TODO: Implement handler logic here\n }\n}\n```\n\n### 4. Complete the test file\n\nEdit `tests/events/<Name>Event.spec.ts` to add meaningful tests beyond the scaffolded ones:\n\n- Keep the existing scaffolded tests (class name, getChannel, handler, publish, subscribe, unsubscribe, unsubscribeAll methods)\n- Add tests relevant to the specific event behavior\n\nThe generated test structure follows this pattern:\n\n```typescript\nimport { describe, expect, test } from "bun:test";\nimport { <Name>PubSub } from "@/pubsub/<Name>PubSub";\n\ndescribe("<Name>PubSub", () => {\n test("should have class name ending with \'PubSub\'", () => {\n expect(<Name>PubSub.name.endsWith("PubSub")).toBe(true);\n });\n\n test("should have \'getChannel\' method", () => {\n expect(<Name>PubSub.prototype.getChannel).toBeDefined();\n expect(typeof <Name>PubSub.prototype.getChannel).toBe("function");\n });\n\n test("should have \'handler\' method", () => {\n expect(<Name>PubSub.prototype.handler).toBeDefined();\n expect(typeof <Name>PubSub.prototype.handler).toBe("function");\n });\n\n test("should have \'publish\' method", () => {\n expect(<Name>PubSub.prototype.publish).toBeDefined();\n expect(typeof <Name>PubSub.prototype.publish).toBe("function");\n });\n\n test("should have \'subscribe\' method", () => {\n expect(<Name>PubSub.prototype.subscribe).toBeDefined();\n expect(typeof <Name>PubSub.prototype.subscribe).toBe("function");\n });\n\n test("should have \'unsubscribe\' method", () => {\n expect(<Name>PubSub.prototype.unsubscribe).toBeDefined();\n expect(typeof <Name>PubSub.prototype.unsubscribe).toBe("function");\n });\n\n test("should have \'unsubscribeAll\' method", () => {\n expect(<Name>PubSub.prototype.unsubscribeAll).toBeDefined();\n expect(typeof <Name>PubSub.prototype.unsubscribeAll).toBe("function");\n });\n});\n```\n\n### 5. Register the event in the module\n\nAdd the new event to the module\'s `events` array in `src/<PascalModuleName>Module.ts` (e.g., `src/BookModule.ts` for the `book` module):\n\n```typescript\nimport type { ModuleType } from "@ooneex/module";\nimport { <Name>Event } from "./events/<Name>Event";\n\nexport const <PascalModuleName>Module: ModuleType = {\n controllers: [],\n entities: [],\n middlewares: [],\n cronJobs: [],\n events: [<Name>Event],\n};\n```\n\nThe module file uses PascalCase naming: `<PascalModuleName>Module.ts` with export `<PascalModuleName>Module`.\n\nIf the module already has other events registered, append the new event to the existing `events` array and add the import alongside existing imports.\n\n### 6. Lint and format\n\nRun linting and formatting on the generated files:\n\n```bash\nbunx biome check --fix src/events/<Name>Event.ts tests/events/<Name>Event.spec.ts\n```\n';
7486
+
7487
+ // src/templates/claude/skills/make.repository.md.txt
7488
+ var make_repository_md_default = '---\nname: make:repository\ndescription: Generate a new repository class with its test file, then complete the generated code. Use when creating a new TypeORM repository for database operations on an entity.\n---\n\n# Make Repository Class\n\nGenerate a new repository class and its test file using the `make:repository` CLI command, then complete the generated code with proper implementation.\n\n## Coding Conventions\n\n- Always explicitly show visibility on class methods and properties (`private`, `public`, `protected`)\n- Always use arrow functions when possible, but NOT for class methods (class methods use regular method syntax)\n- End type names with the `Type` suffix (e.g., `ExceptionStackFrameType`, `ServiceDataType`)\n- Start interface names with the `I` prefix (e.g., `IException`, `IService`, `IAiChat`)\n- For Entity class, if property is optional, add `null` to its type.\n- For dependencies, use `inject` from `@ooneex/container`. For example:\n\n```typescript\nimport { inject } from "@ooneex/container";\nimport { decorator, type ISeed } from "@ooneex/seeds";\n\n@decorator.seed()\nexport class BookSeed implements ISeed {\n constructor(\n @inject(BookRepository)\n private readonly repository: BookRepository,\n ) {}\n}\n```\n\n## Steps\n\n### 1. Run the generator\n\nRun the following command to scaffold the repository class and test files:\n\n```bash\nbunx @ooneex/cli@latest make:repository --name=<name> --module=<module>\n```\n\nWhere `<name>` is the name provided by the user. The `--module` option is optional \u2014 if provided, files are generated under `modules/<module>/` instead of the project root. The command will generate:\n- `src/repositories/<Name>Repository.ts` - The repository class file (or `modules/<module>/src/repositories/<Name>Repository.ts` with `--module`)\n- `tests/repositories/<Name>Repository.spec.ts` - The test file (or `modules/<module>/tests/repositories/<Name>Repository.spec.ts` with `--module`)\n\n### 2. Read the generated files\n\nRead both generated files to understand the scaffolded code.\n\n### 3. Complete the repository class\n\nEdit `src/repositories/<Name>Repository.ts` to complete the implementation:\n\n- Verify the entity import path matches the actual entity location\n- Adjust the `find` method\'s search fields (default searches `name` with `ILike`)\n- Customize relations loading in `findOne`/`findOneBy` if needed\n\n#### Adding methods\n\nLook at the entity\'s fields, relations, and business context to determine if custom domain-specific methods are needed. For example:\n- A `SessionRepository` might need `revokeSession(sessionId: string)` and `revokeAllUserSessions(userId: string)`\n- A `NotificationRepository` might need `markAsRead(id: string)` and `markAllAsRead(userId: string)`\n- Entities with status fields may need methods like `archive(id: string)` or `activate(id: string)`\n\nRead related entities, services, or actions in the module to understand what operations the repository should support, then add the appropriate methods.\n\n#### Removing methods\n\nRemove scaffolded methods that don\'t make sense for the entity\'s context:\n- Remove `createMany`/`updateMany` if the entity is always managed individually (e.g., user profiles, settings)\n- Remove `delete` if the entity uses soft deletes only (use a custom `softDelete` or `archive` method instead)\n- Remove `find` if the entity is only ever accessed by ID or specific criteria (e.g., singleton config entities)\n- Remove `count` if there\'s no use case for counting records of this entity\n\nThe generated class structure follows this pattern:\n\n```typescript\nimport { inject } from "@ooneex/container";\nimport type { ITypeormDatabase } from "@ooneex/database";\nimport { decorator } from "@ooneex/repository";\nimport type { FilterResultType } from "@ooneex/types";\nimport type { FindManyOptions, FindOptionsWhere, Repository, SaveOptions, UpdateResult } from "typeorm";\nimport { ILike } from "typeorm";\nimport { <Name>Entity } from "../entities/<Name>Entity";\n\n@decorator.repository()\nexport class <Name>Repository {\n constructor(\n @inject("database")\n private readonly database: ITypeormDatabase,\n ) {}\n\n public async open(): Promise<Repository<<Name>Entity>> {\n return await this.database.open(<Name>Entity);\n }\n\n public async close(): Promise<void> {\n await this.database.close();\n }\n\n public async find(\n criteria: FindManyOptions<<Name>Entity> & { page?: number; limit?: number; q?: string },\n ): Promise<FilterResultType<<Name>Entity>> {\n // ... pagination and search logic\n }\n\n public async findOne(id: string): Promise<<Name>Entity | null> { ... }\n public async findOneBy(criteria: FindOptionsWhere<<Name>Entity>): Promise<<Name>Entity | null> { ... }\n public async create(entity: <Name>Entity, options?: SaveOptions): Promise<<Name>Entity> { ... }\n public async createMany(entities: <Name>Entity[], options?: SaveOptions): Promise<<Name>Entity[]> { ... }\n public async update(entity: <Name>Entity, options?: SaveOptions): Promise<<Name>Entity> { ... }\n public async updateMany(entities: <Name>Entity[], options?: SaveOptions): Promise<<Name>Entity[]> { ... }\n public async delete(criteria: FindOptionsWhere<<Name>Entity> | FindOptionsWhere<<Name>Entity>[]): Promise<UpdateResult> { ... }\n public async count(criteria?: FindOptionsWhere<<Name>Entity> | FindOptionsWhere<<Name>Entity>[]): Promise<number> { ... }\n}\n```\n\n### 4. Complete the test file\n\nEdit `tests/repositories/<Name>Repository.spec.ts` to add meaningful tests beyond the scaffolded ones:\n\n- Keep scaffolded tests for methods that remain in the repository (remove tests for methods that were removed)\n- Add tests for any custom domain-specific methods added to the repository\n- Add tests relevant to the specific repository behavior\n\n### 5. Lint and format\n\nRun linting and formatting on the generated files:\n\n```bash\nbunx biome check --fix src/repositories/<Name>Repository.ts tests/repositories/<Name>Repository.spec.ts\n```\n';
7489
+
7490
+ // src/templates/claude/skills/make.seed.md.txt
7491
+ var make_seed_md_default = '---\nname: make:seed\ndescription: Generate a new database seed file with its test file, then complete the generated code. Use when creating seed data for populating the database using @ooneex/seeds.\n---\n\n# Make Seed\n\nGenerate a new seed file and its test file using the `make:seed` CLI command, then complete the generated code with proper implementation.\n\n## Coding Conventions\n\n- Always explicitly show visibility on class methods and properties (`private`, `public`, `protected`)\n- Always use arrow functions when possible, but NOT for class methods (class methods use regular method syntax)\n- End type names with the `Type` suffix (e.g., `ExceptionStackFrameType`, `ServiceDataType`)\n- Start interface names with the `I` prefix (e.g., `IException`, `IService`, `IAiChat`)\n- For Entity class, if property is optional, add `null` to its type.\n- For dependencies, use `inject` from `@ooneex/container`. For example:\n\n```typescript\nimport { inject } from "@ooneex/container";\nimport { decorator, type ISeed } from "@ooneex/seeds";\n\n@decorator.seed()\nexport class BookSeed implements ISeed {\n constructor(\n @inject(BookRepository)\n private readonly repository: BookRepository,\n ) {}\n}\n```\n\n## Steps\n\n### 1. Run the generator\n\nRun the following command to scaffold the seed file and test files:\n\n```bash\nbunx @ooneex/cli@latest make:seed --name=<name> --module=<module>\n```\n\nWhere `<name>` is the name provided by the user. The `--module` option is optional \u2014 if provided, files are generated under `modules/<module>/` instead of the project root. The command will generate:\n- `src/seeds/<Name>Seed.ts` - The seed class file (or `modules/<module>/src/seeds/<Name>Seed.ts` with `--module`)\n- `src/seeds/<name-seed>.yml` - The seed data file in kebab-case (or `modules/<module>/src/seeds/<name-seed>.yml` with `--module`)\n- `tests/seeds/<Name>Seed.spec.ts` - The test file (or `modules/<module>/tests/seeds/<Name>Seed.spec.ts` with `--module`)\n\nIt will also:\n- Generate a `seeds.ts` root export file in the seeds directory\n\n### 2. Read the generated files\n\nRead all generated files to understand the scaffolded code.\n\n### 3. Complete the seed data\n\nEdit `src/seeds/<name-seed>.yml` to add the seed data entries. Each entry should have hardcoded nanoid values for `id` fields (generate via `bun -e "import { random } from \'@ooneex/utils\'; console.log(random.nanoid())"`)\n\n- Do NOT use sequential IDs like `"item-1"`, `"item-2"`\n- Ensure the same entity uses the same ID everywhere it appears\n\n### 4. Complete the seed class\n\nEdit `src/seeds/<Name>Seed.ts` to implement:\n\n- Import the relevant entity classes and repository\n- Use `resolve()` from `@ooneex/container` to get the repository instance\n- Map the imported YAML data to entity instances\n- Use the repository to persist the entities\n\n### 5. Complete the test file\n\nEdit `tests/seeds/<Name>Seed.spec.ts` to add meaningful tests beyond the scaffolded ones:\n\n- Keep the existing scaffolded tests (class name, run, isActive, getDependencies, data yml file)\n- Add tests relevant to the specific seed behavior\n\nThe generated test structure follows this pattern:\n\n```typescript\nimport { existsSync } from "node:fs";\nimport { join } from "node:path";\nimport { describe, expect, test } from "bun:test";\nimport { <Name>Seed } from "@/seeds/<Name>Seed";\n\ndescribe("<Name>Seed", () => {\n test("should have class name ending with \'Seed\'", () => {\n expect(<Name>Seed.name.endsWith("Seed")).toBe(true);\n });\n\n test("should have \'run\' method", () => {\n expect(<Name>Seed.prototype.run).toBeDefined();\n expect(typeof <Name>Seed.prototype.run).toBe("function");\n });\n\n test("should have \'isActive\' method", () => {\n expect(<Name>Seed.prototype.isActive).toBeDefined();\n expect(typeof <Name>Seed.prototype.isActive).toBe("function");\n });\n\n test("should have \'getDependencies\' method", () => {\n expect(<Name>Seed.prototype.getDependencies).toBeDefined();\n expect(typeof <Name>Seed.prototype.getDependencies).toBe("function");\n });\n\n test("should have a data yml file", () => {\n const dataFile = join(__dirname, "..", "src", "seeds", "<name-seed>.yml");\n expect(existsSync(dataFile)).toBe(true);\n });\n});\n```\n\n### 6. Lint and format\n\nRun linting and formatting on the generated files:\n\n```bash\nbunx biome check --fix src/seeds/<Name>Seed.ts src/seeds/<name-seed>.yml tests/seeds/<Name>Seed.spec.ts\n```\n';
7492
+
7493
+ // src/templates/claude/skills/make.service.md.txt
7494
+ var make_service_md_default = '---\nname: make:service\ndescription: Generate a new service class with its test file, then complete the generated code. Use when creating a new business logic service that implements IService from @ooneex/service.\n---\n\n# Make Service Class\n\nGenerate a new service class and its test file using the `make:service` CLI command, then complete the generated code with proper implementation.\n\n## Coding Conventions\n\n- Always explicitly show visibility on class methods and properties (`private`, `public`, `protected`)\n- Always use arrow functions when possible, but NOT for class methods (class methods use regular method syntax)\n- End type names with the `Type` suffix (e.g., `ExceptionStackFrameType`, `ServiceDataType`)\n- Start interface names with the `I` prefix (e.g., `IException`, `IService`, `IAiChat`)\n- For Entity class, if property is optional, add `null` to its type.\n- For dependencies, use `inject` from `@ooneex/container`. For example:\n\n```typescript\nimport { inject } from "@ooneex/container";\nimport { decorator, type ISeed } from "@ooneex/seeds";\n\n@decorator.seed()\nexport class BookSeed implements ISeed {\n constructor(\n @inject(BookRepository)\n private readonly repository: BookRepository,\n ) {}\n}\n```\n\n## Steps\n\n### 1. Run the generator\n\nRun the following command to scaffold the service class and test files:\n\n```bash\nbunx @ooneex/cli@latest make:service --name=<name> --module=<module>\n```\n\nWhere `<name>` is the name provided by the user. The `--module` option is optional \u2014 if provided, files are generated under `modules/<module>/` instead of the project root. The command will generate:\n- `src/services/<Name>Service.ts` - The service class file (or `modules/<module>/src/services/<Name>Service.ts` with `--module`)\n- `tests/services/<Name>Service.spec.ts` - The test file (or `modules/<module>/tests/services/<Name>Service.spec.ts` with `--module`)\n\n### 2. Read the generated files\n\nRead both generated files to understand the scaffolded code.\n\n### 3. Complete the service class\n\nEdit `src/services/<Name>Service.ts` to complete the implementation:\n\n- Define a proper type for `ServiceDataType` instead of `Record<string, unknown>`\n- Implement the `execute()` method with actual business logic\n- Inject any required dependencies (repositories, other services, etc.) via the constructor\n\nThe generated class structure follows this pattern:\n\n```typescript\nimport { type IService, decorator } from "@ooneex/service";\n\ntype ServiceDataType = Record<string, unknown>;\n\n@decorator.service()\nexport class <Name>Service implements IService {\n public async execute(data?: ServiceDataType): Promise<void> {\n // TODO: Implement service logic\n }\n}\n```\n\n### 4. Complete the test file\n\nEdit `tests/services/<Name>Service.spec.ts` to add meaningful tests beyond the scaffolded ones:\n\n- Keep the existing scaffolded tests (class name, execute method)\n- Add tests relevant to the specific service behavior\n\nThe generated test structure follows this pattern:\n\n```typescript\nimport { describe, expect, test } from "bun:test";\nimport { <Name>Service } from "@/services/<Name>Service";\n\ndescribe("<Name>Service", () => {\n test("should have class name ending with \'Service\'", () => {\n expect(<Name>Service.name.endsWith("Service")).toBe(true);\n });\n\n test("should have \'execute\' method", () => {\n expect(<Name>Service.prototype.execute).toBeDefined();\n expect(typeof <Name>Service.prototype.execute).toBe("function");\n });\n});\n```\n\n### 5. Lint and format\n\nRun linting and formatting on the generated files:\n\n```bash\nbunx biome check --fix src/services/<Name>Service.ts tests/services/<Name>Service.spec.ts\n```\n';
7495
+
7496
+ // src/templates/claude/skills/make.storage.md.txt
7497
+ var make_storage_md_default = '---\nname: make:storage\ndescription: Generate a new storage class with its test file, then complete the generated code. Use when creating a new S3-compatible storage adapter that extends Storage from @ooneex/storage.\n---\n\n# Make Storage Class\n\nGenerate a new storage class and its test file using the `make:storage` CLI command, then complete the generated code with proper implementation.\n\n## Coding Conventions\n\n- Always explicitly show visibility on class methods and properties (`private`, `public`, `protected`)\n- Always use arrow functions when possible, but NOT for class methods (class methods use regular method syntax)\n- End type names with the `Type` suffix (e.g., `ExceptionStackFrameType`, `ServiceDataType`)\n- Start interface names with the `I` prefix (e.g., `IException`, `IService`, `IAiChat`)\n- For Entity class, if property is optional, add `null` to its type.\n- For dependencies, use `inject` from `@ooneex/container`. For example:\n\n```typescript\nimport { inject } from "@ooneex/container";\nimport { decorator, type ISeed } from "@ooneex/seeds";\n\n@decorator.seed()\nexport class BookSeed implements ISeed {\n constructor(\n @inject(BookRepository)\n private readonly repository: BookRepository,\n ) {}\n}\n```\n\n## Steps\n\n### 1. Run the generator\n\nRun the following command to scaffold the storage class and test files:\n\n```bash\nbunx @ooneex/cli@latest make:storage --name=<name> --module=<module>\n```\n\nWhere `<name>` is the name provided by the user. The `--module` option is optional \u2014 if provided, files are generated under `modules/<module>/` instead of the project root. The command will generate:\n- `src/storage/<Name>Storage.ts` - The storage class file (or `modules/<module>/src/storage/<Name>Storage.ts` with `--module`)\n- `tests/storage/<Name>Storage.spec.ts` - The test file (or `modules/<module>/tests/storage/<Name>Storage.spec.ts` with `--module`)\n\n### 2. Read the generated files\n\nRead both generated files to understand the scaffolded code.\n\n### 3. Complete the storage class\n\nEdit `src/storage/<Name>Storage.ts` to complete the implementation:\n\n- Set the `bucket` property to the appropriate bucket name\n- Verify the environment variable names match the project configuration (`STORAGE_<NAME_UPPER>_ACCESS_KEY`, `STORAGE_<NAME_UPPER>_SECRET_KEY`, `STORAGE_<NAME_UPPER>_ENDPOINT`, `STORAGE_<NAME_UPPER>_REGION`)\n- Add any additional storage-specific methods if needed\n\nThe generated class structure follows this pattern:\n\n```typescript\nimport { Storage, decorator, StorageException } from "@ooneex/storage";\nimport type { S3Options } from "bun";\n\n@decorator.storage()\nexport class <Name>Storage extends Storage {\n protected bucket: string;\n private readonly accessKey: string;\n private readonly secretKey: string;\n private readonly endpoint: string;\n private readonly region: string;\n\n constructor(options?: {\n accessKey?: string;\n secretKey?: string;\n endpoint?: string;\n region?: string;\n }) {\n super();\n\n const accessKey = options?.accessKey || Bun.env.STORAGE_<NAME_UPPER>_ACCESS_KEY;\n const secretKey = options?.secretKey || Bun.env.STORAGE_<NAME_UPPER>_SECRET_KEY;\n const endpoint = options?.endpoint || Bun.env.STORAGE_<NAME_UPPER>_ENDPOINT;\n\n // ... validation throws StorageException if missing ...\n\n this.accessKey = accessKey;\n this.secretKey = secretKey;\n this.endpoint = endpoint;\n this.region = options?.region || Bun.env.STORAGE_<NAME_UPPER>_REGION || "auto";\n }\n\n public getOptions(): S3Options {\n return {\n accessKeyId: this.accessKey,\n secretAccessKey: this.secretKey,\n endpoint: this.endpoint,\n bucket: this.bucket,\n region: this.region,\n };\n }\n}\n```\n\n### 4. Complete the test file\n\nEdit `tests/storage/<Name>Storage.spec.ts` to add meaningful tests beyond the scaffolded ones:\n\n- Keep the existing scaffolded tests (class name, bucket field, getOptions method)\n- Add tests relevant to the specific storage class behavior\n\nThe generated test structure follows this pattern:\n\n```typescript\nimport { describe, expect, test } from "bun:test";\nimport { <Name>StorageAdapter } from "@/storage/<Name>StorageAdapter";\n\ndescribe("<Name>StorageAdapter", () => {\n test("should have class name ending with \'StorageAdapter\'", () => {\n expect(<Name>StorageAdapter.name.endsWith("StorageAdapter")).toBe(true);\n });\n\n test("should have \'bucket\' field", () => {\n expect("bucket" in <Name>StorageAdapter.prototype || "bucket" in Object.getOwnPropertyNames(<Name>StorageAdapter.prototype)).toBe(true);\n });\n\n test("should have \'getOptions\' method", () => {\n expect(<Name>StorageAdapter.prototype.getOptions).toBeDefined();\n expect(typeof <Name>StorageAdapter.prototype.getOptions).toBe("function");\n });\n});\n```\n\n### 5. Lint and format\n\nRun linting and formatting on the generated files:\n\n```bash\nbunx biome check --fix src/storage/<Name>Storage.ts tests/storage/<Name>Storage.spec.ts\n```\n';
7498
+
7499
+ // src/templates/claude/skills/make.vector-database.md.txt
7500
+ var make_vector_database_md_default = '---\nname: make:vector-database\ndescription: Generate a new vector database class with its test file, then complete the generated code. Use when creating a new vector database that extends VectorDatabase from @ooneex/rag.\n---\n\n# Make Vector Database Class\n\nGenerate a new vector database class and its test file using the `make:vector-database` CLI command, then complete the generated code with proper implementation.\n\n## Coding Conventions\n\n- Always explicitly show visibility on class methods and properties (`private`, `public`, `protected`)\n- Always use arrow functions when possible, but NOT for class methods (class methods use regular method syntax)\n- End type names with the `Type` suffix (e.g., `ExceptionStackFrameType`, `ServiceDataType`)\n- Start interface names with the `I` prefix (e.g., `IException`, `IService`, `IAiChat`)\n- For Entity class, if property is optional, add `null` to its type.\n- For dependencies, use `inject` from `@ooneex/container`. For example:\n\n```typescript\nimport { inject } from "@ooneex/container";\nimport { decorator, type ISeed } from "@ooneex/seeds";\n\n@decorator.seed()\nexport class BookSeed implements ISeed {\n constructor(\n @inject(BookRepository)\n private readonly repository: BookRepository,\n ) {}\n}\n```\n\n## Steps\n\n### 1. Run the generator\n\nRun the following command to scaffold the vector database class and test files:\n\n```bash\nbunx @ooneex/cli@latest make:vector-database --name=<name> --module=<module>\n```\n\nWhere `<name>` is the name provided by the user. The `--module` option is optional \u2014 if provided, files are generated under `modules/<module>/` instead of the project root. The command will generate:\n- `src/databases/<Name>VectorDatabase.ts` - The vector database class file (or `modules/<module>/src/databases/<Name>VectorDatabase.ts` with `--module`)\n- `tests/databases/<Name>VectorDatabase.spec.ts` - The test file (or `modules/<module>/tests/databases/<Name>VectorDatabase.spec.ts` with `--module`)\n\n### 2. Read the generated files\n\nRead both generated files to understand the scaffolded code.\n\n### 3. Complete the vector database class\n\nEdit `src/databases/<Name>VectorDatabase.ts` to complete the implementation:\n\n- Set the `getDatabaseUri()` return value to the actual LanceDB database path\n- Configure the embedding provider and model in `getEmbeddingModel()`\n- Define the custom data fields in `DataType` and map them in `getSchema()`\n- Import the appropriate Apache Arrow types for your schema fields\n\nThe generated class structure follows this pattern:\n\n```typescript\nimport { type EmbeddingModelType, type EmbeddingProviderType, type FieldValueType, VectorDatabase, decorator } from "@ooneex/rag";\nimport { Utf8 } from "apache-arrow";\n\ntype DataType = {\n name: string;\n};\n\n@decorator.vectorDatabase()\nexport class <Name>VectorDatabase extends VectorDatabase<DataType> {\n public getDatabaseUri(): string {\n return "";\n }\n\n public getEmbeddingModel(): { provider: EmbeddingProviderType; model: EmbeddingModelType["model"] } {\n return { provider: "openai", model: "text-embedding-ada-002" };\n }\n\n public getSchema(): { [K in keyof DataType]: FieldValueType } {\n return {\n name: new Utf8(),\n };\n }\n}\n```\n\n### 4. Complete the test file\n\nEdit `tests/databases/<Name>VectorDatabase.spec.ts` to add meaningful tests beyond the scaffolded ones:\n\n- Keep the existing scaffolded tests (class name, getDatabaseUri, getEmbeddingModel, getSchema methods)\n- Add tests relevant to the specific vector database class behavior\n\nThe generated test structure follows this pattern:\n\n```typescript\nimport { describe, expect, test } from "bun:test";\nimport { <Name>VectorDatabase } from "@/databases/<Name>VectorDatabase";\n\ndescribe("<Name>VectorDatabase", () => {\n test("should have class name ending with \'VectorDatabase\'", () => {\n expect(<Name>VectorDatabase.name.endsWith("VectorDatabase")).toBe(true);\n });\n\n test("should have \'getDatabaseUri\' method", () => {\n expect(<Name>VectorDatabase.prototype.getDatabaseUri).toBeDefined();\n expect(typeof <Name>VectorDatabase.prototype.getDatabaseUri).toBe("function");\n });\n\n test("should have \'getEmbeddingModel\' method", () => {\n expect(<Name>VectorDatabase.prototype.getEmbeddingModel).toBeDefined();\n expect(typeof <Name>VectorDatabase.prototype.getEmbeddingModel).toBe("function");\n });\n\n test("should have \'getSchema\' method", () => {\n expect(<Name>VectorDatabase.prototype.getSchema).toBeDefined();\n expect(typeof <Name>VectorDatabase.prototype.getSchema).toBe("function");\n });\n});\n```\n\n### 5. Lint and format\n\nRun linting and formatting on the generated files:\n\n```bash\nbunx biome check --fix src/databases/<Name>VectorDatabase.ts tests/databases/<Name>VectorDatabase.spec.ts\n```\n';
7501
+
7502
+ // src/templates/claude/skills/optimize.md.txt
7503
+ var optimize_md_default = "---\nname: optimize\ndescription: Optimize a module's codebase for quality, performance, and clean conventions. Enforces arrow functions (except class methods), type/interface naming, removes duplication, and ensures only important tests remain.\n---\n\n# Optimize Codebase\n\nOptimize a module's codebase for quality, performance, and clean conventions.\n\n## Coding Conventions\n\nApply these conventions across all files in the target module:\n\n- **Arrow functions everywhere** \u2014 use arrow functions for all function expressions, callbacks, standalone functions, and variable declarations. The ONLY exception is class methods, which must use regular method syntax.\n- **Type naming** \u2014 all type aliases must end with the `Type` suffix (e.g., `UserDataType`, `ConfigOptionsType`). Rename any that don't comply.\n- **Interface naming** \u2014 all interface names must start with the `I` prefix (e.g., `IUser`, `IService`, `IRepository`). Rename any that don't comply.\n- **No definite assignment assertions** \u2014 never use `!` on class properties (e.g., `email!: string`). Use a default value or make the property optional (`?`) instead.\n- Always explicitly show visibility on class methods and properties (`private`, `public`, `protected`).\n- For Entity class, if property is optional, add `null` to its type.\n- For dependencies, use `inject` from `@ooneex/container`. For example:\n\n```typescript\nimport { inject } from \"@ooneex/container\";\nimport { decorator, type ISeed } from \"@ooneex/seeds\";\n\n@decorator.seed()\nexport class BookSeed implements ISeed {\n constructor(\n @inject(BookRepository)\n private readonly repository: BookRepository,\n ) {}\n}\n```\n\n## Steps\n\n### 1. Identify the target\n\nDetermine the module to optimize. If the user specifies a module name, work in `modules/<module>/`. If no module is specified, ask the user which module to optimize.\n\n### 2. Read and analyze the module\n\nRead all source files (`src/**/*.ts`) and test files (`tests/**/*.ts`) in the target module. Build a full picture of:\n\n- All types, interfaces, classes, and functions\n- Dependencies between files\n- Existing test coverage\n\n### 3. Enforce naming conventions\n\nScan every file and fix:\n\n- **Types not ending with `Type`** \u2014 rename the type and update all references across the module\n- **Interfaces not starting with `I`** \u2014 rename the interface and update all references across the module\n- **Non-arrow functions** \u2014 convert all function expressions, callbacks, and standalone functions to arrow functions. Do NOT convert class methods.\n- **Missing visibility modifiers** \u2014 add explicit `public`, `private`, or `protected` to all class methods and properties\n\n### 4. Remove code duplication\n\nIdentify duplicated or near-duplicated code within the module:\n\n- Extract shared logic into well-named helper arrow functions or base classes\n- Consolidate repeated type definitions\n- Merge similar utility functions\n- Remove dead code (unused imports, unreachable branches, unused variables)\n\n### 5. Optimize for performance\n\nReview and improve:\n\n- Replace inefficient loops or repeated iterations with single-pass approaches where possible\n- Use `Map`/`Set` instead of arrays for lookups when appropriate\n- Avoid unnecessary object spreads or deep clones\n- Prefer early returns to reduce nesting\n- Remove unnecessary `async`/`await` where a direct return suffices\n- Eliminate redundant null/undefined checks when the type system already guarantees the value\n\n### 6. Optimize tests\n\nReview all test files and restructure:\n\n- **Remove trivial tests** \u2014 delete tests that only check obvious things (e.g., \"class name ends with X\", \"method exists\") unless they serve as smoke tests for generated code\n- **Keep and improve important tests** \u2014 focus on tests that verify actual business logic, edge cases, error handling, and integration behavior\n- **Write fewer but more meaningful tests** \u2014 each test should validate a real scenario or invariant, not just existence checks\n- **Consolidate redundant test cases** \u2014 merge tests that cover the same code path with slightly different inputs into parameterized patterns\n- **Ensure critical paths are covered** \u2014 every public method with logic should have at least one test covering its happy path and one covering its error/edge case\n\n### 7. Final cleanup\n\n- Remove all unused imports\n- Remove empty files or files with no exports\n- Ensure consistent formatting\n\n### 8. Lint and format\n\nRun linting and formatting on all modified files:\n\n```bash\nbunx biome check --fix <list of modified files>\n```\n\n### 9. Run tests\n\nRun the module's tests to verify nothing is broken:\n\n```bash\nbun test <module test directory>\n```\n\nIf any test fails, fix the issue and re-run until all tests pass.\n";
7504
+
7505
+ // src/commands/MakeClaudeSkillCommand.ts
7506
+ var skills = {
7507
+ "make.ai": make_ai_md_default,
7508
+ "make.analytics": make_analytics_md_default,
7509
+ "make.cache": make_cache_md_default,
7510
+ "make.controller": make_controller_md_default,
7511
+ "make.cron": make_cron_md_default,
7512
+ "make.database": make_database_md_default,
7513
+ "make.entity": make_entity_md_default,
7514
+ "make.logger": make_logger_md_default,
7515
+ "make.mailer": make_mailer_md_default,
7516
+ "make.middleware": make_middleware_md_default,
7517
+ "make.migration": make_migration_md_default,
7518
+ "make.permission": make_permission_md_default,
7519
+ "make.pubsub": make_pubsub_md_default,
7520
+ "make.repository": make_repository_md_default,
7521
+ "make.seed": make_seed_md_default,
7522
+ "make.service": make_service_md_default,
7523
+ "make.storage": make_storage_md_default,
7524
+ "make.vector-database": make_vector_database_md_default,
7525
+ commit: commit_md_default,
7526
+ optimize: optimize_md_default
7527
+ };
7528
+
7529
+ class MakeClaudeSkillCommand {
7530
+ getName() {
7531
+ return "make:claude:skill";
7532
+ }
7533
+ getDescription() {
7534
+ return "Generate Claude skills from templates";
7535
+ }
7536
+ async run() {
7537
+ const skillsLocalDir = join11(".claude", "skills");
7538
+ const skillsDir = join11(process.cwd(), skillsLocalDir);
7539
+ const logger = new TerminalLogger10;
7540
+ for (const [skillName, content] of Object.entries(skills)) {
7541
+ const dirName = skillName.replace(/\./g, "-");
7542
+ const filePath = join11(skillsDir, dirName, "SKILL.md");
7543
+ await Bun.write(filePath, content);
7544
+ logger.success(`${join11(skillsLocalDir, dirName, "SKILL.md")} created successfully`, undefined, {
7545
+ showTimestamp: false,
7546
+ showArrow: false,
7547
+ useSymbol: true
7548
+ });
7549
+ }
7452
7550
  }
7453
7551
  }
7454
- `;
7455
-
7456
- // src/commands/MakeCacheCommand.ts
7457
- class MakeCacheCommand {
7552
+ MakeClaudeSkillCommand = __legacyDecorateClassTS([
7553
+ decorator11.command()
7554
+ ], MakeClaudeSkillCommand);
7555
+ // src/commands/MakeCommandCommand.ts
7556
+ import { join as join12 } from "path";
7557
+ import { commandCreate, decorator as decorator12 } from "@ooneex/command";
7558
+ import { TerminalLogger as TerminalLogger11 } from "@ooneex/logger";
7559
+ class MakeCommandCommand {
7458
7560
  getName() {
7459
- return "make:cache";
7561
+ return "make:command";
7460
7562
  }
7461
7563
  getDescription() {
7462
- return "Generate a new cache class";
7564
+ return "Generate a new command class";
7463
7565
  }
7464
7566
  async run(options) {
7465
7567
  let { name, module } = options;
7466
7568
  if (!name) {
7467
- name = await askName({ message: "Enter cache name" });
7569
+ name = await askName({ message: "Enter name" });
7570
+ }
7571
+ if (module) {
7572
+ await ensureModule(module);
7573
+ }
7574
+ const base = module ? join12("modules", module) : ".";
7575
+ const { commandPath, testPath } = await commandCreate({
7576
+ name,
7577
+ commandDir: join12(base, "src", "commands"),
7578
+ testsDir: join12(base, "tests", "commands")
7579
+ });
7580
+ if (module && module !== "app") {
7581
+ const appCommandsRootPath = join12(process.cwd(), "modules", "app", "src", "commands", "commands.ts");
7582
+ const appCommandsRootFile = Bun.file(appCommandsRootPath);
7583
+ const importLine = `import "@${module}/commands/commands";`;
7584
+ if (await appCommandsRootFile.exists()) {
7585
+ const appCommandsContent = await appCommandsRootFile.text();
7586
+ if (!appCommandsContent.includes(importLine)) {
7587
+ await Bun.write(appCommandsRootPath, `${appCommandsContent.trimEnd()}
7588
+ ${importLine}
7589
+ `);
7590
+ }
7591
+ } else {
7592
+ await Bun.write(appCommandsRootPath, `${importLine}
7593
+ `);
7594
+ }
7468
7595
  }
7469
- name = toPascalCase5(name).replace(/Cache$/, "");
7470
- const content = cache_default.replace(/{{NAME}}/g, name);
7471
- if (module) {
7472
- await ensureModule(module);
7596
+ const binCommandRunPath = join12(process.cwd(), "modules", "app", "bin", "command", "run.ts");
7597
+ const binCommandRunFile = Bun.file(binCommandRunPath);
7598
+ if (!await binCommandRunFile.exists()) {
7599
+ await Bun.write(binCommandRunPath, command_run_default);
7473
7600
  }
7474
- const base = module ? join8("modules", module) : ".";
7475
- const cacheLocalDir = join8(base, "src", "cache");
7476
- const cacheDir = join8(process.cwd(), cacheLocalDir);
7477
- const filePath = join8(cacheDir, `${name}Cache.ts`);
7478
- await Bun.write(filePath, content);
7479
- const testContent = cache_test_default.replace(/{{NAME}}/g, name);
7480
- const testsLocalDir = join8(base, "tests", "cache");
7481
- const testsDir = join8(process.cwd(), testsLocalDir);
7482
- const testFilePath = join8(testsDir, `${name}Cache.spec.ts`);
7483
- await Bun.write(testFilePath, testContent);
7484
- const logger = new TerminalLogger7;
7485
- logger.success(`${join8(cacheLocalDir, name)}Cache.ts created successfully`, undefined, {
7601
+ const packageJsonPath = join12(process.cwd(), "modules", "app", "package.json");
7602
+ const packageJsonFile = Bun.file(packageJsonPath);
7603
+ if (await packageJsonFile.exists()) {
7604
+ const packageJson = await packageJsonFile.json();
7605
+ packageJson.scripts = packageJson.scripts || {};
7606
+ packageJson.scripts.command = "bun ./bin/command/run.ts";
7607
+ await Bun.write(packageJsonPath, JSON.stringify(packageJson, null, 2));
7608
+ }
7609
+ const logger = new TerminalLogger11;
7610
+ logger.success(`${commandPath} created successfully`, undefined, {
7486
7611
  showTimestamp: false,
7487
7612
  showArrow: false,
7488
7613
  useSymbol: true
7489
7614
  });
7490
- logger.success(`${join8(testsLocalDir, name)}Cache.spec.ts created successfully`, undefined, {
7615
+ logger.success(`${testPath} created successfully`, undefined, {
7491
7616
  showTimestamp: false,
7492
7617
  showArrow: false,
7493
7618
  useSymbol: true
7494
7619
  });
7495
- const packageJsonPath = join8(process.cwd(), "package.json");
7496
- const packageJson = await Bun.file(packageJsonPath).json();
7497
- const deps = packageJson.dependencies ?? {};
7498
- const devDeps = packageJson.devDependencies ?? {};
7499
- if (!deps["@ooneex/cache"] && !devDeps["@ooneex/cache"]) {
7500
- const install = Bun.spawn(["bun", "add", "@ooneex/cache"], {
7501
- cwd: process.cwd(),
7502
- stdout: "ignore",
7503
- stderr: "inherit"
7504
- });
7505
- await install.exited;
7506
- }
7620
+ logger.info("Run 'bun run command' to execute commands", undefined, {
7621
+ showTimestamp: false,
7622
+ showArrow: true,
7623
+ showLevel: false
7624
+ });
7507
7625
  }
7508
7626
  }
7509
- MakeCacheCommand = __legacyDecorateClassTS([
7510
- decorator7.command()
7511
- ], MakeCacheCommand);
7512
- // src/commands/MakeClaudeSkillCommand.ts
7513
- import { join as join9 } from "path";
7514
- import { decorator as decorator8 } from "@ooneex/command";
7515
- import { TerminalLogger as TerminalLogger8 } from "@ooneex/logger";
7516
-
7517
- // src/templates/claude/skills/commit.md.txt
7518
- var commit_md_default = `---
7519
- name: commit
7520
- description: Create commit messages grouped by module. Analyzes git changes, groups files under modules/ by module name, and creates separate commits following commitlint conventions. Uses common scope for non-module changes.
7521
- ---
7522
-
7523
- # Commit by Module
7524
-
7525
- Create separate commits for each modified module, following the project's commitlint conventions.
7526
-
7527
- ## Workflow
7528
-
7529
- 1. **Analyze staged/unstaged changes**
7530
- - Run \`git status --porcelain\` to get all modified files
7531
- - Group changes by module (files under \`modules/<module-name>/\`)
7532
- - Identify non-module changes (files not in \`modules/\`) as scope \`common\`
7533
-
7534
- 2. **For each module with changes**
7535
- - Stage only that module's files: \`git add modules/<module-name>/\`
7536
- - Determine the appropriate commit type based on changes
7537
- - Create a commit with proper format: \`type(scope): Subject\`
7538
- - The scope is the module name in lower-case
7539
- - Repeat for next module
7540
-
7541
- 3. **Handle non-module changes**
7542
- - Files outside \`modules/\` (e.g., \`bun.lock\`, \`package.json\`, config files, \`packages/\` changes) use scope \`common\`
7543
- - Stage and commit separately from module changes
7544
-
7545
- ## Commit Message Format
7546
-
7547
- \`\`\`
7548
- type(scope): Subject line
7549
- \`\`\`
7550
-
7551
- ### Valid Types
7552
- - \`feat\`: New feature
7553
- - \`fix\`: Bug fix
7554
- - \`refactor\`: Code refactoring (no new feature, no bug fix)
7555
- - \`test\`: Adding/updating tests
7556
- - \`chore\`: Maintenance tasks (dependencies, configs)
7557
- - \`docs\`: Documentation changes
7558
- - \`style\`: Code style changes (formatting, whitespace)
7559
- - \`perf\`: Performance improvements
7560
- - \`build\`: Build system changes
7561
- - \`ci\`: CI configuration changes
7562
- - \`revert\`: Revert previous commit
7563
-
7564
- ### Scope Rules
7565
- - For files under \`modules/<name>/\`: use the module name as scope (e.g., \`module\` scope matches the commitlint config)
7566
- - For all other files: use \`common\`
7567
- - Scope must be lower-case
7568
- - Scope must never be empty
7569
- - If the module name matches a valid commitlint scope, use it directly
7570
- - If it does not match, use \`module\` as the scope
7571
-
7572
- ### Subject Rules
7573
- - Use sentence-case, start-case, pascal-case, or upper-case
7574
- - No period at the end
7575
- - Maximum 100 characters for entire header
7576
- - Use imperative mood ("Add feature" not "Added feature")
7577
-
7578
- ## Determining Commit Type
7579
-
7580
- Analyze the changes to determine the appropriate type:
7581
-
7582
- | Change Pattern | Type |
7583
- |---------------|------|
7584
- | New files with functionality | \`feat\` |
7585
- | Bug fixes, error corrections | \`fix\` |
7586
- | Code restructuring, renaming | \`refactor\` |
7587
- | New/updated test files (\`*.spec.ts\`) | \`test\` |
7588
- | Documentation files (\`*.md\`) | \`docs\` |
7589
- | Dependency updates, lock files | \`chore\` |
7590
- | Build configs, scripts | \`build\` |
7591
- | CI/CD files | \`ci\` |
7592
- | Formatting only | \`style\` |
7593
- | Performance optimizations | \`perf\` |
7594
-
7595
- ## Examples
7596
-
7597
- ### Example 1: Multiple Module Changes
7598
-
7599
- Git status shows:
7600
- \`\`\`
7601
- M modules/user/src/services/UserService.ts
7602
- A modules/user/src/services/AuthService.ts
7603
- M modules/user/tests/UserService.spec.ts
7604
- M modules/product/src/entities/Product.ts
7605
- M modules/product/src/repositories/ProductRepository.ts
7606
- M bun.lock
7607
- M packages/cache/src/index.ts
7608
- \`\`\`
7609
-
7610
- Commands to execute:
7611
- \`\`\`bash
7612
- # Commit user module
7613
- git add modules/user/
7614
- git commit -m "feat(user): Add AuthService and update UserService"
7627
+ MakeCommandCommand = __legacyDecorateClassTS([
7628
+ decorator12.command()
7629
+ ], MakeCommandCommand);
7630
+ // src/commands/MakeControllerCommand.ts
7631
+ import { basename, join as join13 } from "path";
7632
+ import { decorator as decorator13 } from "@ooneex/command";
7633
+ import { TerminalLogger as TerminalLogger12 } from "@ooneex/logger";
7634
+ import { toKebabCase as toKebabCase3, toPascalCase as toPascalCase5, trim } from "@ooneex/utils";
7615
7635
 
7616
- # Commit product module
7617
- git add modules/product/
7618
- git commit -m "refactor(product): Update Product entity and repository"
7636
+ // src/prompts/askConfirm.ts
7637
+ var import_enquirer3 = __toESM(require_enquirer(), 1);
7638
+ var askConfirm = async (config) => {
7639
+ const response = await import_enquirer3.prompt({
7640
+ type: "confirm",
7641
+ name: "confirm",
7642
+ message: config.message,
7643
+ initial: config.initial
7644
+ });
7645
+ return response.confirm;
7646
+ };
7619
7647
 
7620
- # Commit non-module changes
7621
- git add bun.lock packages/cache/
7622
- git commit -m "chore(common): Update dependencies and cache package"
7623
- \`\`\`
7648
+ // ../types/dist/index.js
7649
+ var HTTP_METHODS = ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"];
7624
7650
 
7625
- ### Example 2: Single Module with Tests
7651
+ // src/prompts/askRouteMethod.ts
7652
+ var import_enquirer4 = __toESM(require_enquirer(), 1);
7626
7653
 
7627
- Git status shows:
7628
- \`\`\`
7629
- A modules/payment/src/services/StripeService.ts
7630
- A modules/payment/tests/StripeService.spec.ts
7631
- \`\`\`
7654
+ // src/constraints/AssertRouteMethod.ts
7655
+ import { Assert as Assert2, Validation as Validation2 } from "@ooneex/validation";
7632
7656
 
7633
- Command:
7634
- \`\`\`bash
7635
- git add modules/payment/
7636
- git commit -m "feat(payment): Add StripeService with tests"
7637
- \`\`\`
7657
+ class AssertRouteMethod extends Validation2 {
7658
+ getConstraint() {
7659
+ return Assert2("string >= 3");
7660
+ }
7661
+ getErrorMessage() {
7662
+ return `Route method must be one of: ${HTTP_METHODS.join(", ")}`;
7663
+ }
7664
+ validate(data, constraint) {
7665
+ const basicValidation = super.validate(data, constraint);
7666
+ if (!basicValidation.isValid) {
7667
+ return basicValidation;
7668
+ }
7669
+ const method = data;
7670
+ if (method.trim() !== method) {
7671
+ return {
7672
+ isValid: false,
7673
+ message: this.getErrorMessage() || "Invalid route method format"
7674
+ };
7675
+ }
7676
+ const upperMethod = method.toUpperCase();
7677
+ if (!HTTP_METHODS.includes(upperMethod)) {
7678
+ return {
7679
+ isValid: false,
7680
+ message: this.getErrorMessage() || "Invalid route method"
7681
+ };
7682
+ }
7683
+ return {
7684
+ isValid: true
7685
+ };
7686
+ }
7687
+ }
7638
7688
 
7639
- ### Example 3: Only Non-module Changes
7689
+ // src/prompts/askRouteMethod.ts
7690
+ var askRouteMethod = async (config) => {
7691
+ const response = await import_enquirer4.prompt({
7692
+ type: "select",
7693
+ name: "method",
7694
+ message: config.message,
7695
+ initial: config.initial ?? 0,
7696
+ choices: HTTP_METHODS.map((method) => method),
7697
+ validate: (value) => {
7698
+ const constraint = new AssertRouteMethod;
7699
+ const result = constraint.validate(value);
7700
+ if (!result.isValid) {
7701
+ return result.message || "Route method is invalid";
7702
+ }
7703
+ return true;
7704
+ }
7705
+ });
7706
+ return response.method;
7707
+ };
7640
7708
 
7641
- Git status shows:
7642
- \`\`\`
7643
- M package.json
7644
- M bun.lock
7645
- M packages/logger/src/Logger.ts
7646
- \`\`\`
7709
+ // src/prompts/askRouteName.ts
7710
+ var import_enquirer5 = __toESM(require_enquirer(), 1);
7647
7711
 
7648
- Command:
7649
- \`\`\`bash
7650
- git add package.json bun.lock packages/logger/
7651
- git commit -m "chore(common): Update dependencies and logger package"
7652
- \`\`\`
7712
+ // src/constraints/AssertRouteName.ts
7713
+ import { Assert as Assert3, Validation as Validation3 } from "@ooneex/validation";
7714
+ var ROUTE_NAME_REGEX = /^[a-zA-Z0-9]+\.[a-zA-Z0-9]+\.[a-zA-Z0-9]+$/;
7653
7715
 
7654
- ### Example 4: Module Refactoring with Deletions
7716
+ class AssertRouteName extends Validation3 {
7717
+ getConstraint() {
7718
+ return Assert3("string >= 7");
7719
+ }
7720
+ getErrorMessage() {
7721
+ return "Route name must follow format: namespace.resource.action (e.g., 'api.users.list')";
7722
+ }
7723
+ validate(data, constraint) {
7724
+ const basicValidation = super.validate(data, constraint);
7725
+ if (!basicValidation.isValid) {
7726
+ return basicValidation;
7727
+ }
7728
+ const routeName = data;
7729
+ if (routeName.trim() !== routeName) {
7730
+ return {
7731
+ isValid: false,
7732
+ message: this.getErrorMessage() || "Invalid route name format"
7733
+ };
7734
+ }
7735
+ if (!ROUTE_NAME_REGEX.test(routeName)) {
7736
+ return {
7737
+ isValid: false,
7738
+ message: this.getErrorMessage() || "Invalid route name format"
7739
+ };
7740
+ }
7741
+ const segments = routeName.split(".");
7742
+ if (segments.length !== 3) {
7743
+ return {
7744
+ isValid: false,
7745
+ message: this.getErrorMessage() || "Invalid route name format"
7746
+ };
7747
+ }
7748
+ const [namespace, resource, action] = segments;
7749
+ if (!namespace || !resource || !action) {
7750
+ return {
7751
+ isValid: false,
7752
+ message: this.getErrorMessage() || "Invalid route name format"
7753
+ };
7754
+ }
7755
+ return {
7756
+ isValid: true
7757
+ };
7758
+ }
7759
+ }
7655
7760
 
7656
- Git status shows:
7657
- \`\`\`
7658
- D modules/order/src/services/OldOrderService.ts
7659
- A modules/order/src/services/OrderService.ts
7660
- M modules/order/src/index.ts
7661
- \`\`\`
7761
+ // src/prompts/askRouteName.ts
7762
+ var askRouteName = async (config) => {
7763
+ const response = await import_enquirer5.prompt({
7764
+ type: "input",
7765
+ name: "routeName",
7766
+ message: config.message,
7767
+ validate: (value) => {
7768
+ const constraint = new AssertRouteName;
7769
+ const result = constraint.validate(value);
7770
+ if (!result.isValid) {
7771
+ return result.message || "Route name is invalid";
7772
+ }
7773
+ return true;
7774
+ }
7775
+ });
7776
+ return response.routeName;
7777
+ };
7662
7778
 
7663
- Command:
7664
- \`\`\`bash
7665
- git add modules/order/
7666
- git commit -m "refactor(order): Replace OldOrderService with OrderService"
7667
- \`\`\`
7779
+ // src/prompts/askRoutePath.ts
7780
+ var import_enquirer6 = __toESM(require_enquirer(), 1);
7668
7781
 
7669
- ## Subject Line Guidelines
7782
+ // src/constraints/AssertRoutePath.ts
7783
+ import { Assert as Assert4, Validation as Validation4 } from "@ooneex/validation";
7784
+ var ROUTE_PATH_MIN_LENGTH = 1;
7785
+ var ROUTE_PATH_REGEX = /^\/[\w\-/:]*$/;
7786
+ var VALID_SEGMENT_REGEX = /^[a-zA-Z0-9\-_]+$/;
7787
+ var PARAM_SEGMENT_REGEX = /^:[a-zA-Z][a-zA-Z0-9]*$/;
7670
7788
 
7671
- Write clear, descriptive subjects:
7789
+ class AssertRoutePath extends Validation4 {
7790
+ getConstraint() {
7791
+ return Assert4(`string >= ${ROUTE_PATH_MIN_LENGTH}`);
7792
+ }
7793
+ getErrorMessage() {
7794
+ return "Route path must start with '/' and contain only valid segments (e.g., '/users', '/api/users/:id')";
7795
+ }
7796
+ validate(data, constraint) {
7797
+ const basicValidation = super.validate(data, constraint);
7798
+ if (!basicValidation.isValid) {
7799
+ return basicValidation;
7800
+ }
7801
+ const path = data;
7802
+ if (path.trim() !== path) {
7803
+ return {
7804
+ isValid: false,
7805
+ message: this.getErrorMessage() || "Invalid route path format"
7806
+ };
7807
+ }
7808
+ if (!path.startsWith("/")) {
7809
+ return {
7810
+ isValid: false,
7811
+ message: "Route path must start with '/'"
7812
+ };
7813
+ }
7814
+ if (path.length > 1 && path.endsWith("/")) {
7815
+ return {
7816
+ isValid: false,
7817
+ message: "Route path cannot end with '/' (except for root path)"
7818
+ };
7819
+ }
7820
+ if (!ROUTE_PATH_REGEX.test(path)) {
7821
+ return {
7822
+ isValid: false,
7823
+ message: this.getErrorMessage() || "Invalid route path format"
7824
+ };
7825
+ }
7826
+ if (path === "/") {
7827
+ return {
7828
+ isValid: true
7829
+ };
7830
+ }
7831
+ const segments = path.slice(1).split("/");
7832
+ for (const segment of segments) {
7833
+ if (!segment) {
7834
+ return {
7835
+ isValid: false,
7836
+ message: "Route path cannot contain empty segments (double slashes)"
7837
+ };
7838
+ }
7839
+ if (segment.startsWith(":")) {
7840
+ if (!PARAM_SEGMENT_REGEX.test(segment)) {
7841
+ return {
7842
+ isValid: false,
7843
+ message: `Invalid parameter segment '${segment}'. Parameters must follow format ':paramName' with alphanumeric characters only`
7844
+ };
7845
+ }
7846
+ } else {
7847
+ if (!VALID_SEGMENT_REGEX.test(segment)) {
7848
+ return {
7849
+ isValid: false,
7850
+ message: `Invalid path segment '${segment}'. Segments must contain only letters, numbers, hyphens, and underscores`
7851
+ };
7852
+ }
7853
+ }
7854
+ }
7855
+ return {
7856
+ isValid: true
7857
+ };
7858
+ }
7859
+ }
7672
7860
 
7673
- | Good | Bad |
7674
- |------|-----|
7675
- | \`Add Stripe payment integration\` | \`stripe\` |
7676
- | \`Fix null check in user validation\` | \`fix bug\` |
7677
- | \`Update order processing flow\` | \`changes\` |
7678
- | \`Remove deprecated auth methods\` | \`cleanup\` |
7679
- | \`Rename UserAdapter to UserService\` | \`rename\` |
7861
+ // src/prompts/askRoutePath.ts
7862
+ var askRoutePath = async (config) => {
7863
+ const response = await import_enquirer6.prompt({
7864
+ type: "input",
7865
+ name: "path",
7866
+ message: config.message,
7867
+ initial: config.initial ?? "/",
7868
+ validate: (value) => {
7869
+ const constraint = new AssertRoutePath;
7870
+ const result = constraint.validate(value);
7871
+ if (!result.isValid) {
7872
+ return result.message || "Route path is invalid";
7873
+ }
7874
+ return true;
7875
+ }
7876
+ });
7877
+ return response.path;
7878
+ };
7680
7879
 
7681
- ## Pre-commit Checklist
7880
+ // src/templates/controller.socket.txt
7881
+ var controller_socket_default = `import type { ContextType } from "@ooneex/socket";
7882
+ import { ERole } from "@ooneex/role";
7883
+ import { Route } from "@ooneex/routing";
7884
+ import { Assert } from "@ooneex/validation";
7682
7885
 
7683
- Before committing, verify:
7684
- 1. Changes are logically grouped by module
7685
- 2. Tests pass for modified modules
7686
- 3. No debug code or console.logs left in
7687
- 4. Commit message follows the format exactly
7688
- 5. For Entity class, if property is optional, add \`null\` to its type.
7886
+ export type {{TYPE_NAME}}RouteType = {
7887
+ params: {
7689
7888
 
7690
- ## Handling Special Cases
7889
+ },
7890
+ payload: {
7691
7891
 
7692
- ### Mixed Changes in One Module
7693
- If a module has both new features and bug fixes, prioritize:
7694
- 1. \`feat\` if primary change is new functionality
7695
- 2. \`fix\` if primary change is a bug fix
7696
- 3. Split into multiple commits if changes are truly independent
7892
+ },
7893
+ queries: {
7697
7894
 
7698
- ### Deleted Files Only
7699
- Use \`refactor\` for removing deprecated code:
7700
- \`\`\`bash
7701
- git add modules/user/
7702
- git commit -m "refactor(user): Remove deprecated UserAdapter"
7703
- \`\`\`
7895
+ },
7896
+ response: {
7704
7897
 
7705
- ### Renamed/Moved Files
7706
- Use \`refactor\` for file reorganization:
7707
- \`\`\`bash
7708
- git add modules/product/
7709
- git commit -m "refactor(product): Reorganize service file structure"
7710
- \`\`\`
7711
- `;
7898
+ },
7899
+ };
7712
7900
 
7713
- // src/templates/claude/skills/make.ai.md.txt
7714
- var make_ai_md_default = "---\nname: make:ai\ndescription: Generate a new AI class with its test file, then complete the generated code. Use when creating a new AI chat class that uses OpenAI via the @ooneex/ai package.\n---\n\n# Make AI Class\n\nGenerate a new AI class and its test file using the `make:ai` CLI command, then complete the generated code with proper implementation.\n\n## Coding Conventions\n\n- Always explicitly show visibility on class methods and properties (`private`, `public`, `protected`)\n- Always use arrow functions when possible, but NOT for class methods (class methods use regular method syntax)\n- End type names with the `Type` suffix (e.g., `ExceptionStackFrameType`, `ServiceDataType`)\n- Start interface names with the `I` prefix (e.g., `IException`, `IService`, `IAiChat`)\n- For Entity class, if property is optional, add `null` to its type.\n\n## Steps\n\n### 1. Run the generator\n\nRun the following command to scaffold the AI class and test files:\n\n```bash\nbunx @ooneex/cli@latest make:ai --name=<name> --module=<module>\n```\n\nWhere `<name>` is the name provided by the user. The `--module` option is optional \u2014 if provided, files are generated under `modules/<module>/` instead of the project root. The command will generate:\n- `src/ai/<Name>Ai.ts` - The AI class file (or `modules/<module>/src/ai/<Name>Ai.ts` with `--module`)\n- `tests/ai/<Name>Ai.spec.ts` - The test file (or `modules/<module>/tests/ai/<Name>Ai.spec.ts` with `--module`)\n\n### 2. Read the generated files\n\nRead both generated files to understand the scaffolded code.\n\n### 3. Complete the AI class\n\nEdit `src/ai/<Name>Ai.ts` to complete the implementation:\n\n- Update the prompt in the `run` method from `\"My prompt\"` to a meaningful prompt based on the class purpose\n- Update the prompt in the `runStream` method from `\"My prompt\"` to a meaningful prompt based on the class purpose\n- Add any additional configuration or methods relevant to the AI class purpose\n- Ensure proper typing for the `run<T>()` generic return type\n\nThe generated class structure follows this pattern:\n\n```typescript\nimport { decorator, type IAiChat, OpenAi, type OpenAiConfigType } from \"@ooneex/ai\";\nimport { inject } from \"@ooneex/container\";\n\n@decorator.ai()\nexport class <Name>Ai implements IAiChat<OpenAiConfigType> {\n constructor(@inject(OpenAi) private readonly ai: OpenAi) {}\n\n public async run<T>(prompt?: string, config?: Omit<OpenAiConfigType, \"prompt\">): Promise<T> {\n return this.ai.run<T>(prompt || \"My prompt\", config);\n }\n\n public async *runStream(\n prompt?: string,\n config?: Omit<OpenAiConfigType, \"prompt\" | \"output\">,\n ): AsyncGenerator<string, void, unknown> {\n yield* this.ai.runStream(prompt || \"My prompt\", config);\n }\n}\n```\n\n### 4. Complete the test file\n\nEdit `tests/ai/<Name>Ai.spec.ts` to add meaningful tests beyond the scaffolded ones:\n\n- Keep the existing scaffolded tests (class name, run method, runStream method)\n- Add tests relevant to the specific AI class behavior\n\nThe generated test structure follows this pattern:\n\n```typescript\nimport { describe, expect, test } from \"bun:test\";\nimport { <Name>Ai } from \"@/ai/<Name>Ai\";\n\ndescribe(\"<Name>Ai\", () => {\n test(\"should have class name ending with 'Ai'\", () => {\n expect(<Name>Ai.name.endsWith(\"Ai\")).toBe(true);\n });\n\n test(\"should have 'run' method\", () => {\n expect(<Name>Ai.prototype.run).toBeDefined();\n expect(typeof <Name>Ai.prototype.run).toBe(\"function\");\n });\n\n test(\"should have 'runStream' method\", () => {\n expect(<Name>Ai.prototype.runStream).toBeDefined();\n expect(typeof <Name>Ai.prototype.runStream).toBe(\"function\");\n });\n});\n```\n\n### 5. Lint and format\n\nRun linting and formatting on the generated files:\n\n```bash\nbunx biome check --fix src/ai/<Name>Ai.ts tests/ai/<Name>Ai.spec.ts\n```\n";
7901
+ @Route.socket("{{ROUTE_PATH}}", {
7902
+ name: "{{ROUTE_NAME}}",
7903
+ version: 1,
7904
+ description: "",
7905
+ params: {
7906
+ // id: Assert("string"),
7907
+ },
7908
+ payload: Assert({
7715
7909
 
7716
- // src/templates/claude/skills/make.analytics.md.txt
7717
- var make_analytics_md_default = "---\nname: make:analytics\ndescription: Generate a new analytics class with its test file, then complete the generated code. Use when creating a new analytics tracking class that uses the @ooneex/analytics package.\n---\n\n# Make Analytics Class\n\nGenerate a new analytics class and its test file using the `make:analytics` CLI command, then complete the generated code with proper implementation.\n\n## Coding Conventions\n\n- Always explicitly show visibility on class methods and properties (`private`, `public`, `protected`)\n- Always use arrow functions when possible, but NOT for class methods (class methods use regular method syntax)\n- End type names with the `Type` suffix (e.g., `ExceptionStackFrameType`, `ServiceDataType`)\n- Start interface names with the `I` prefix (e.g., `IException`, `IService`, `IAiChat`)\n- For Entity class, if property is optional, add `null` to its type.\n\n## Steps\n\n### 1. Run the generator\n\nRun the following command to scaffold the analytics class and test files:\n\n```bash\nbunx @ooneex/cli@latest make:analytics --name=<name> --module=<module>\n```\n\nWhere `<name>` is the name provided by the user. The `--module` option is optional \u2014 if provided, files are generated under `modules/<module>/` instead of the project root. The command will generate:\n- `src/analytics/<Name>Analytics.ts` - The analytics class file (or `modules/<module>/src/analytics/<Name>Analytics.ts` with `--module`)\n- `tests/analytics/<Name>Analytics.spec.ts` - The test file (or `modules/<module>/tests/analytics/<Name>Analytics.spec.ts` with `--module`)\n\n### 2. Read the generated files\n\nRead both generated files to understand the scaffolded code.\n\n### 3. Complete the analytics class\n\nEdit `src/analytics/<Name>Analytics.ts` to complete the implementation:\n\n- Implement the `capture` method with actual analytics tracking logic\n- Define a proper type for `CaptureOptionsType` instead of `Record<string, unknown>` based on the analytics purpose\n- Add any additional methods or properties relevant to the analytics class purpose\n\nThe generated class structure follows this pattern:\n\n```typescript\nimport { type IAnalytics, decorator } from \"@ooneex/analytics\";\n\ntype CaptureOptionsType = Record<string, unknown>;\n\n@decorator.analytics()\nexport class <Name>Analytics<T extends CaptureOptionsType = CaptureOptionsType> implements IAnalytics<T> {\n public capture(options: T): void {\n // console.log(\"Analytics captured:\", options);\n }\n}\n```\n\n### 4. Complete the test file\n\nEdit `tests/analytics/<Name>Analytics.spec.ts` to add meaningful tests beyond the scaffolded ones:\n\n- Keep the existing scaffolded tests (class name, capture method)\n- Add tests relevant to the specific analytics class behavior\n\nThe generated test structure follows this pattern:\n\n```typescript\nimport { describe, expect, test } from \"bun:test\";\nimport { <Name>Analytics } from \"@/analytics/<Name>Analytics\";\n\ndescribe(\"<Name>Analytics\", () => {\n test(\"should have class name ending with 'Analytics'\", () => {\n expect(<Name>Analytics.name.endsWith(\"Analytics\")).toBe(true);\n });\n\n test(\"should have 'capture' method\", () => {\n expect(<Name>Analytics.prototype.capture).toBeDefined();\n expect(typeof <Name>Analytics.prototype.capture).toBe(\"function\");\n });\n});\n```\n\n### 5. Lint and format\n\nRun linting and formatting on the generated files:\n\n```bash\nbunx biome check --fix src/analytics/<Name>Analytics.ts tests/analytics/<Name>Analytics.spec.ts\n```\n";
7910
+ }),
7911
+ queries: Assert({
7718
7912
 
7719
- // src/templates/claude/skills/make.cache.md.txt
7720
- var make_cache_md_default = "---\nname: make:cache\ndescription: Generate a new cache adapter class with its test file, then complete the generated code. Use when creating a new cache adapter that implements the ICache interface from @ooneex/cache.\n---\n\n# Make Cache Class\n\nGenerate a new cache class and its test file using the `make:cache` CLI command, then complete the generated code with proper implementation.\n\n## Coding Conventions\n\n- Always explicitly show visibility on class methods and properties (`private`, `public`, `protected`)\n- Always use arrow functions when possible, but NOT for class methods (class methods use regular method syntax)\n- End type names with the `Type` suffix (e.g., `ExceptionStackFrameType`, `ServiceDataType`)\n- Start interface names with the `I` prefix (e.g., `IException`, `IService`, `IAiChat`)\n- For Entity class, if property is optional, add `null` to its type.\n\n## Steps\n\n### 1. Run the generator\n\nRun the following command to scaffold the cache class and test files:\n\n```bash\nbunx @ooneex/cli@latest make:cache --name=<name> --module=<module>\n```\n\nWhere `<name>` is the name provided by the user. The `--module` option is optional \u2014 if provided, files are generated under `modules/<module>/` instead of the project root. The command will generate:\n- `src/cache/<Name>Cache.ts` - The cache class file (or `modules/<module>/src/cache/<Name>Cache.ts` with `--module`)\n- `tests/cache/<Name>Cache.spec.ts` - The test file (or `modules/<module>/tests/cache/<Name>Cache.spec.ts` with `--module`)\n\n### 2. Read the generated files\n\nRead both generated files to understand the scaffolded code.\n\n### 3. Complete the cache class\n\nEdit `src/cache/<Name>Cache.ts` to complete the implementation:\n\n- Implement the `get` method to retrieve cached values by key\n- Implement the `set` method to store values with optional TTL\n- Implement the `delete` method to remove cached entries\n- Implement the `has` method to check key existence\n- Replace the `CacheException` throws with actual cache logic\n- Inject any required dependencies (e.g., Redis client) via the constructor\n\nThe generated class structure follows this pattern:\n\n```typescript\nimport { CacheException, type ICache, decorator } from \"@ooneex/cache\";\n\n@decorator.cache()\nexport class <Name>Cache implements ICache {\n public async get<T = unknown>(key: string): Promise<T | undefined> {\n throw new CacheException(`Failed to get key \"${key}\": Not implemented`);\n }\n\n public async set<T = unknown>(key: string, value: T, ttl?: number): Promise<void> {\n throw new CacheException(`Failed to set key \"${key}\": Not implemented`);\n }\n\n public async delete(key: string): Promise<boolean> {\n throw new CacheException(`Failed to delete key \"${key}\": Not implemented`);\n }\n\n public async has(key: string): Promise<boolean> {\n throw new CacheException(`Failed to check if key \"${key}\" exists: Not implemented`);\n }\n}\n```\n\n### 4. Complete the test file\n\nEdit `tests/cache/<Name>Cache.spec.ts` to add meaningful tests beyond the scaffolded ones:\n\n- Keep the existing scaffolded tests (class name, get, set, delete, has methods)\n- Add tests relevant to the specific cache class behavior\n\nThe generated test structure follows this pattern:\n\n```typescript\nimport { describe, expect, test } from \"bun:test\";\nimport { <Name>Cache } from \"@/cache/<Name>Cache\";\n\ndescribe(\"<Name>Cache\", () => {\n test(\"should have class name ending with 'Cache'\", () => {\n expect(<Name>Cache.name.endsWith(\"Cache\")).toBe(true);\n });\n\n test(\"should have 'get' method\", () => {\n expect(<Name>Cache.prototype.get).toBeDefined();\n expect(typeof <Name>Cache.prototype.get).toBe(\"function\");\n });\n\n test(\"should have 'set' method\", () => {\n expect(<Name>Cache.prototype.set).toBeDefined();\n expect(typeof <Name>Cache.prototype.set).toBe(\"function\");\n });\n\n test(\"should have 'delete' method\", () => {\n expect(<Name>Cache.prototype.delete).toBeDefined();\n expect(typeof <Name>Cache.prototype.delete).toBe(\"function\");\n });\n\n test(\"should have 'has' method\", () => {\n expect(<Name>Cache.prototype.has).toBeDefined();\n expect(typeof <Name>Cache.prototype.has).toBe(\"function\");\n });\n});\n```\n\n### 5. Lint and format\n\nRun linting and formatting on the generated files:\n\n```bash\nbunx biome check --fix src/cache/<Name>Cache.ts tests/cache/<Name>Cache.spec.ts\n```\n";
7913
+ }),
7914
+ response: Assert({
7721
7915
 
7722
- // src/templates/claude/skills/make.controller.md.txt
7723
- var make_controller_md_default = "---\nname: make:controller\ndescription: Generate a new controller class with route type and test file, then complete the generated code. Use when creating a new HTTP or WebSocket controller with routing, validation, and role-based access.\n---\n\n# Make Controller Class\n\nGenerate a new controller class, its route type, and test file using the `make:controller` CLI command, then complete the generated code with proper implementation.\n\n## Coding Conventions\n\n- Always explicitly show visibility on class methods and properties (`private`, `public`, `protected`)\n- Always use arrow functions when possible, but NOT for class methods (class methods use regular method syntax)\n- End type names with the `Type` suffix (e.g., `ExceptionStackFrameType`, `ServiceDataType`)\n- Start interface names with the `I` prefix (e.g., `IException`, `IService`, `IAiChat`)\n- For Entity class, if property is optional, add `null` to its type.\n\n## Steps\n\n### 1. Run the generator\n\nRun the following command to scaffold the controller class and related files:\n\n```bash\nbunx @ooneex/cli@latest make:controller --name=<name> --module=<module> --is-socket=<true|false> --route-name=<route.name> --route-path=<route.path> --route-method=<route.method>\n```\n\nWhere:\n- `<name>` is the controller name provided by the user\n- `--module` is optional \u2014 if provided, files are generated under `modules/<module>/` instead of the project root\n- `--is-socket` determines HTTP vs WebSocket controller (defaults to `false`)\n- `--route-name` is the route name using dot notation: `<resource>.<action>` (e.g., `user.create`, `book.list`, `flashcard.delete`)\n- `--route-path` is the route path (e.g., `/api/users`)\n- `--route-method` is the HTTP method (e.g., `get`, `post`, `put`, `patch`, `delete`) \u2014 only for HTTP controllers\n\nThe command will generate (paths prefixed with `modules/<module>/` when `--module` is provided):\n- `src/controllers/<Name>Controller.ts` - The controller class file\n- `src/types/routes/<route.name>.ts` - The route type file (will be moved into the controller file \u2014 see step 3)\n- `tests/controllers/<Name>Controller.spec.ts` - The test file\n\n### 2. Read the generated files\n\nRead all three generated files to understand the scaffolded code.\n\n### 3. Complete the route type\n\n**IMPORTANT: Keep the route type inside the controller file**, not in a separate type file. Define the type directly in `src/controllers/<Name>Controller.ts` above the class definition. Delete the generated `src/types/routes/<route.name>.ts` file if it was created.\n\n**Remove unnecessary `params`, `payload`, and `queries` \u2014 only include what the route actually needs:**\n\n- **`params`** \u2014 Include only when the route path contains dynamic segments (e.g., `/api/users/:id`). Remove entirely for routes with no URL parameters (e.g., `/api/users`).\n- **`payload`** \u2014 Include only for methods that accept a request body (`post`, `put`, `patch`). Remove entirely for `get` and `delete` routes.\n- **`queries`** \u2014 Include only when the route supports query string filtering, pagination, or sorting (e.g., list/search endpoints). Remove entirely when not needed.\n- **`response`** \u2014 Always include.\n\nThe route type structure follows this pattern (remove unused sections):\n\n```typescript\n// Example: GET /api/users (list) \u2014 no params, no payload, has queries\ntype <TypeName>RouteType = {\n queries: {\n\n },\n response: {\n\n },\n};\n\n// Example: POST /api/users (create) \u2014 no params, has payload, no queries\ntype <TypeName>RouteType = {\n payload: {\n\n },\n response: {\n\n },\n};\n\n// Example: GET /api/users/:id (detail) \u2014 has params, no payload, no queries\ntype <TypeName>RouteType = {\n params: {\n\n },\n response: {\n\n },\n};\n\n// Example: PUT /api/users/:id (update) \u2014 has params, has payload, no queries\ntype <TypeName>RouteType = {\n params: {\n\n },\n payload: {\n\n },\n response: {\n\n },\n};\n```\n\n### 4. Complete the controller class\n\nEdit `src/controllers/<Name>Controller.ts` to complete the implementation:\n\n- Set appropriate `roles` for access control\n- Add a meaningful `description` for the route that explains what the endpoint does (e.g., `\"Create a new user account\"`, `\"List all books with pagination\"`)\n- **Keep the controller thin** \u2014 do NOT put business logic in the controller. Put all logic in the corresponding service and inject the service into the controller via the constructor. The controller's `index` method should only delegate to the service.\n- **Remove unnecessary `params`, `payload`, and `queries`** from both the route type and the `@Route` decorator \u2014 only include what the route actually needs (see step 3 rules).\n\n**Add or remove `params`, `payload`, and `queries` in the `@Route` decorator to match the route type (step 3):**\n\n- **`params`** \u2014 Include with `Assert()` validators only when the route has URL parameters. Remove the `params` key entirely otherwise.\n- **`payload`** \u2014 Include with `Assert({...})` only for `post`, `put`, `patch` methods. Remove the `payload` key entirely for `get` and `delete`.\n- **`queries`** \u2014 Include with `Assert({...})` only when query parameters are expected. Remove the `queries` key entirely otherwise.\n- **`response`** \u2014 Always include with `Assert({...})`.\n\n**HTTP controller** generated structure (remove `params`, `payload`, `queries` as needed \u2014 see rules above):\n\n```typescript\nimport type { ContextType } from \"@ooneex/controller\";\nimport { ERole } from \"@ooneex/role\";\nimport { Route } from \"@ooneex/routing\";\nimport { Assert } from \"@ooneex/validation\";\n\ntype <TypeName>RouteType = {\n // Only include params, payload, queries as needed (see step 3)\n response: {\n\n },\n};\n\n@Route.<method>(\"<route.path>\", {\n name: \"<route.name>\",\n version: 1,\n description: \"\",\n // Only include params, payload, queries as needed (see step 3)\n response: Assert({\n\n }),\n roles: [ERole.USER],\n})\nexport class <Name>Controller {\n public async index(context: ContextType<<TypeName>RouteType>) {\n return context.response.json({\n\n });\n }\n}\n```\n\n**Socket controller** generated structure (remove `params`, `payload`, `queries` as needed \u2014 see rules above):\n\n```typescript\nimport type { ContextType } from \"@ooneex/socket\";\nimport { ERole } from \"@ooneex/role\";\nimport { Route } from \"@ooneex/routing\";\nimport { Assert } from \"@ooneex/validation\";\n\ntype <TypeName>RouteType = {\n // Only include params, payload, queries as needed (see step 3)\n response: {\n\n },\n};\n\n@Route.socket(\"<route.path>\", {\n name: \"<route.name>\",\n version: 1,\n description: \"\",\n // Only include params, payload, queries as needed (see step 3)\n response: Assert({\n\n }),\n roles: [ERole.USER],\n})\nexport class <Name>Controller {\n public async index(context: ContextType<<TypeName>RouteType>) {\n return context.response.json({\n\n });\n }\n}\n```\n\n### 5. Complete the test file\n\nEdit `tests/controllers/<Name>Controller.spec.ts` to add meaningful tests beyond the scaffolded ones:\n\n- Keep the existing scaffolded tests (class name, index method)\n- Add tests relevant to the specific controller behavior\n\nThe generated test structure follows this pattern:\n\n```typescript\nimport { describe, expect, test } from \"bun:test\";\nimport { <Name>Controller } from \"@/controllers/<Name>Controller\";\n\ndescribe(\"<Name>Controller\", () => {\n test(\"should have class name ending with 'Controller'\", () => {\n expect(<Name>Controller.name.endsWith(\"Controller\")).toBe(true);\n });\n\n test(\"should have 'index' method\", () => {\n expect(<Name>Controller.prototype.index).toBeDefined();\n expect(typeof <Name>Controller.prototype.index).toBe(\"function\");\n });\n});\n```\n\n### 6. Register the controller in the module\n\nAdd the new controller to the module's `controllers` array in `src/<PascalModuleName>Module.ts` (e.g., `src/BookModule.ts` for the `book` module):\n\n```typescript\nimport type { ModuleType } from \"@ooneex/module\";\nimport { <Name>Controller } from \"./controllers/<Name>Controller\";\n\nexport const <PascalModuleName>Module: ModuleType = {\n controllers: [<Name>Controller],\n entities: [],\n middlewares: [],\n cronJobs: [],\n events: [],\n};\n```\n\nThe module file uses PascalCase naming: `<PascalModuleName>Module.ts` with export `<PascalModuleName>Module`.\n\nIf the module already has other controllers registered, append the new controller to the existing `controllers` array and add the import alongside existing imports.\n\n### 7. Lint and format\n\nRun linting and formatting on the generated files:\n\n```bash\nbunx biome check --fix src/controllers/<Name>Controller.ts tests/controllers/<Name>Controller.spec.ts\n```\n\n### 8. Create the service\n\nAfter the controller is created, generate a service class for the controller's business logic using the `make:service` skill:\n\n```\n/make:service --name=<Name>\n```\n\nWhere `<Name>` matches the controller name (e.g., if the controller is `CreateUserController`, the service is `CreateUserService`).\n\n### 9. Create the pubsub event\n\nAfter the service is created, generate a pubsub event class for the controller's domain events using the `make:pubsub` skill:\n\n```\n/make:pubsub --name=<Name> --channel=<resource>.<action>\n```\n\nWhere:\n- `<Name>` matches the controller name (e.g., if the controller is `CreateUserController`, the event is `CreateUserEvent`)\n- `<resource>.<action>` follows the same dot notation as the route name (e.g., `user.create`, `book.delete`)\n\nOnce the event is created:\n- Inject the **service** into the **event** via the constructor, and call the service's `execute` method from the event's `handler` method.\n- Inject the **event** into the **controller** via the constructor, and publish the event from the controller's `index` method.\n";
7916
+ }),
7917
+ roles: [ERole.USER],
7918
+ })
7919
+ export class {{NAME}}Controller {
7920
+ public async index(context: ContextType<{{TYPE_NAME}}RouteType>) {
7921
+ // const { id } = context.params;
7724
7922
 
7725
- // src/templates/claude/skills/make.cron.md.txt
7726
- var make_cron_md_default = "---\nname: make:cron\ndescription: Generate a new cron job class with its test file, then complete the generated code. Use when creating a new scheduled task that extends the Cron base class from @ooneex/cron.\n---\n\n# Make Cron Class\n\nGenerate a new cron class and its test file using the `make:cron` CLI command, then complete the generated code with proper implementation.\n\n## Coding Conventions\n\n- Always explicitly show visibility on class methods and properties (`private`, `public`, `protected`)\n- Always use arrow functions when possible, but NOT for class methods (class methods use regular method syntax)\n- End type names with the `Type` suffix (e.g., `ExceptionStackFrameType`, `ServiceDataType`)\n- Start interface names with the `I` prefix (e.g., `IException`, `IService`, `IAiChat`)\n- For Entity class, if property is optional, add `null` to its type.\n\n## Steps\n\n### 1. Run the generator\n\nRun the following command to scaffold the cron class and test files:\n\n```bash\nbunx @ooneex/cli@latest make:cron --name=<name> --module=<module>\n```\n\nWhere `<name>` is the name provided by the user. The `--module` option is optional \u2014 if provided, files are generated under `modules/<module>/` instead of the project root. The command will generate:\n- `src/cron/<Name>Cron.ts` - The cron class file (or `modules/<module>/src/cron/<Name>Cron.ts` with `--module`)\n- `tests/cron/<Name>Cron.spec.ts` - The test file (or `modules/<module>/tests/cron/<Name>Cron.spec.ts` with `--module`)\n\n### 2. Read the generated files\n\nRead both generated files to understand the scaffolded code.\n\n### 3. Complete the cron class\n\nEdit `src/cron/<Name>Cron.ts` to complete the implementation:\n\n- Set the appropriate cron schedule in `getTime()` (e.g., `\"every 5 minutes\"`, `\"every 1 hours\"`, `\"every 30 seconds\"`)\n- Set the timezone in `getTimeZone()` if needed, or keep `null` for server timezone\n- Implement the `handler()` method with the actual cron job logic\n- Inject any required dependencies via the constructor\n\nThe generated class structure follows this pattern:\n\n```typescript\nimport type { TimeZoneType } from \"@ooneex/country\";\nimport { Cron, type CronTimeType, decorator } from \"@ooneex/cron\";\n\n@decorator.cron()\nexport class <Name>Cron extends Cron {\n public getTime(): CronTimeType {\n // Examples: \"every 5 minutes\", \"every 1 hours\", \"every 30 seconds\"\n return \"every 1 hours\";\n }\n\n public getTimeZone(): TimeZoneType | null {\n // Return null to use server timezone, or specify a timezone like \"Europe/Paris\"\n return null;\n }\n\n public async handler(): Promise<void> {\n // Implement your cron handler logic here\n // console.log(\"<Name>Cron handler executed\");\n }\n}\n```\n\n### 4. Complete the test file\n\nEdit `tests/cron/<Name>Cron.spec.ts` to add meaningful tests beyond the scaffolded ones:\n\n- Keep the existing scaffolded tests (class name, getTime, getTimeZone, handler methods)\n- Add tests relevant to the specific cron class behavior\n\nThe generated test structure follows this pattern:\n\n```typescript\nimport { describe, expect, test } from \"bun:test\";\nimport { <Name>Cron } from \"@/cron/<Name>Cron\";\n\ndescribe(\"<Name>Cron\", () => {\n test(\"should have class name ending with 'Cron'\", () => {\n expect(<Name>Cron.name.endsWith(\"Cron\")).toBe(true);\n });\n\n test(\"should have 'getTime' method\", () => {\n expect(<Name>Cron.prototype.getTime).toBeDefined();\n expect(typeof <Name>Cron.prototype.getTime).toBe(\"function\");\n });\n\n test(\"should have 'getTimeZone' method\", () => {\n expect(<Name>Cron.prototype.getTimeZone).toBeDefined();\n expect(typeof <Name>Cron.prototype.getTimeZone).toBe(\"function\");\n });\n\n test(\"should have 'handler' method\", () => {\n expect(<Name>Cron.prototype.handler).toBeDefined();\n expect(typeof <Name>Cron.prototype.handler).toBe(\"function\");\n });\n});\n```\n\n### 5. Register the cron job in the module\n\nAdd the new cron job to the module's `cronJobs` array in `src/<PascalModuleName>Module.ts` (e.g., `src/BookModule.ts` for the `book` module):\n\n```typescript\nimport type { ModuleType } from \"@ooneex/module\";\nimport { <Name>Cron } from \"./cron/<Name>Cron\";\n\nexport const <PascalModuleName>Module: ModuleType = {\n controllers: [],\n entities: [],\n middlewares: [],\n cronJobs: [<Name>Cron],\n events: [],\n};\n```\n\nThe module file uses PascalCase naming: `<PascalModuleName>Module.ts` with export `<PascalModuleName>Module`.\n\nIf the module already has other cron jobs registered, append the new cron job to the existing `cronJobs` array and add the import alongside existing imports.\n\n### 6. Lint and format\n\nRun linting and formatting on the generated files:\n\n```bash\nbunx biome check --fix src/cron/<Name>Cron.ts tests/cron/<Name>Cron.spec.ts\n```\n";
7923
+ return context.response.json({
7924
+
7925
+ });
7926
+ }
7927
+ }
7928
+ `;
7727
7929
 
7728
- // src/templates/claude/skills/make.database.md.txt
7729
- var make_database_md_default = "---\nname: make:database\ndescription: Generate a new database class with its test file, then complete the generated code. Use when creating a new database adapter that extends TypeormDatabase from @ooneex/database.\n---\n\n# Make Database Class\n\nGenerate a new database class and its test file using the `make:database` CLI command, then complete the generated code with proper implementation.\n\n## Coding Conventions\n\n- Always explicitly show visibility on class methods and properties (`private`, `public`, `protected`)\n- Always use arrow functions when possible, but NOT for class methods (class methods use regular method syntax)\n- End type names with the `Type` suffix (e.g., `ExceptionStackFrameType`, `ServiceDataType`)\n- Start interface names with the `I` prefix (e.g., `IException`, `IService`, `IAiChat`)\n- For Entity class, if property is optional, add `null` to its type.\n\n## Steps\n\n### 1. Run the generator\n\nRun the following command to scaffold the database class and test files:\n\n```bash\nbunx @ooneex/cli@latest make:database --name=<name> --module=<module>\n```\n\nWhere `<name>` is the name provided by the user. The `--module` option is optional \u2014 if provided, files are generated under `modules/<module>/` instead of the project root. The command will generate:\n- `src/databases/<Name>Database.ts` - The database class file (or `modules/<module>/src/databases/<Name>Database.ts` with `--module`)\n- `tests/databases/<Name>Database.spec.ts` - The test file (or `modules/<module>/tests/databases/<Name>Database.spec.ts` with `--module`)\n\n### 2. Read the generated files\n\nRead both generated files to understand the scaffolded code.\n\n### 3. Complete the database class\n\nEdit `src/databases/<Name>Database.ts` to complete the implementation:\n\n- Add entity imports and register them in the `entities` array\n- Adjust the database path if needed (default is `\"var/db\"`)\n- Configure DataSource options as appropriate (type, synchronize, etc.)\n- Inject any required dependencies via the constructor\n\nThe generated class structure follows this pattern:\n\n```typescript\nimport { DataSource } from \"typeorm\";\nimport { TypeormDatabase, DatabaseException, decorator } from \"@ooneex/database\";\n\n@decorator.database()\nexport class <Name>Database extends TypeormDatabase {\n public getSource(database?: string): DataSource {\n database = database || \"var/db\";\n\n this.source = new DataSource({\n synchronize: false,\n entities: [\n // TODO: Load your entities here\n ],\n enableWAL: true,\n busyErrorRetry: 2000,\n busyTimeout: 30_000,\n database,\n type: \"sqlite\",\n });\n\n return this.source;\n }\n}\n```\n\n### 4. Complete the test file\n\nEdit `tests/databases/<Name>Database.spec.ts` to add meaningful tests beyond the scaffolded ones:\n\n- Keep the existing scaffolded tests (class name, getSource method)\n- Add tests relevant to the specific database class behavior\n\nThe generated test structure follows this pattern:\n\n```typescript\nimport { describe, expect, test } from \"bun:test\";\nimport { <Name>Database } from \"@/databases/<Name>Database\";\n\ndescribe(\"<Name>Database\", () => {\n test(\"should have class name ending with 'Database'\", () => {\n expect(<Name>Database.name.endsWith(\"Database\")).toBe(true);\n });\n\n test(\"should have 'getSource' method\", () => {\n expect(<Name>Database.prototype.getSource).toBeDefined();\n expect(typeof <Name>Database.prototype.getSource).toBe(\"function\");\n });\n});\n```\n\n### 5. Lint and format\n\nRun linting and formatting on the generated files:\n\n```bash\nbunx biome check --fix src/databases/<Name>Database.ts tests/databases/<Name>Database.spec.ts\n```\n";
7930
+ // src/templates/controller.test.txt
7931
+ var controller_test_default = `import { describe, expect, test } from "bun:test";
7932
+ import { {{NAME}}Controller } from "@/controllers/{{NAME}}Controller";
7730
7933
 
7731
- // src/templates/claude/skills/make.entity.md.txt
7732
- var make_entity_md_default = "---\nname: make:entity\ndescription: Generate a new TypeORM entity class with its test file, then complete the generated code. Use when creating a new database entity with columns, relations, and table mapping.\n---\n\n# Make Entity Class\n\nGenerate a new entity class and its test file using the `make:entity` CLI command, then complete the generated code with proper implementation.\n\n## Coding Conventions\n\n- Always explicitly show visibility on class methods and properties (`private`, `public`, `protected`)\n- Always use arrow functions when possible, but NOT for class methods (class methods use regular method syntax)\n- End type names with the `Type` suffix (e.g., `ExceptionStackFrameType`, `ServiceDataType`)\n- Start interface names with the `I` prefix (e.g., `IException`, `IService`, `IAiChat`)\n- For Entity class, if property is optional, add `null` to its type.\n- Avoid non-null assertions (`!`). For example, `public name!: string;` should be `public name: string;`\n\n## Steps\n\n### 1. Run the generator\n\nRun the following command to scaffold the entity class and test files:\n\n```bash\nbunx @ooneex/cli@latest make:entity --name=<name> --module=<module> --table-name=<table_name>\n```\n\nWhere `<name>` is the name provided by the user. The `--table-name` option is optional \u2014 if omitted, it defaults to the snake_case pluralized form of the name (e.g., `UserProfile` becomes `user_profiles`). The `--module` option is optional \u2014 if provided, files are generated under `modules/<module>/` instead of the project root. The command will generate:\n- `src/entities/<Name>Entity.ts` - The entity class file (or `modules/<module>/src/entities/<Name>Entity.ts` with `--module`)\n- `tests/entities/<Name>Entity.spec.ts` - The test file (or `modules/<module>/tests/entities/<Name>Entity.spec.ts` with `--module`)\n\n### 2. Read the generated files\n\nRead both generated files to understand the scaffolded code.\n\n### 3. Complete the entity class\n\nEdit `src/entities/<Name>Entity.ts` to complete the implementation:\n\n- Add entity-specific columns with appropriate TypeORM decorators (`@Column`)\n- Add relations if needed (`@ManyToOne`, `@OneToMany`, `@ManyToMany`, etc.)\n- Remove any scaffolded columns that are not relevant to the entity\n- Adjust column types, lengths, and constraints as needed\n\nThe generated class structure follows this pattern:\n\n```typescript\nimport type { LocaleType } from \"@ooneex/translation\";\nimport { random } from \"@ooneex/utils\";\nimport { Column, CreateDateColumn, DeleteDateColumn, PrimaryColumn, UpdateDateColumn } from \"typeorm\";\n\n@Entity({\n name: \"<table_name>\",\n})\nexport class <Name>Entity extends BaseEntity {\n @PrimaryColumn({ name: \"id\", type: \"varchar\", length: 25 })\n id: string = random.nanoid(25);\n\n @Column({\n name: \"is_locked\",\n type: \"boolean\",\n default: false,\n nullable: true,\n })\n isLocked?: boolean;\n\n @Column({ name: \"locked_at\", type: \"timestamptz\", nullable: true })\n lockedAt?: Date;\n\n @Column({\n name: \"is_blocked\",\n type: \"boolean\",\n default: false,\n nullable: true,\n })\n isBlocked?: boolean;\n\n @Column({ name: \"blocked_at\", type: \"timestamptz\", nullable: true })\n blockedAt?: Date;\n\n @Column({ name: \"block_reason\", type: \"text\", nullable: true })\n blockReason?: string;\n\n @Column({ name: \"is_public\", type: \"boolean\", default: true, nullable: true })\n isPublic?: boolean;\n\n @Column({ name: \"lang\", type: \"varchar\", length: 10, nullable: true })\n lang?: LocaleType;\n\n @CreateDateColumn({ name: \"created_at\" })\n createdAt?: Date;\n\n @UpdateDateColumn({ name: \"updated_at\" })\n updatedAt?: Date;\n\n @DeleteDateColumn({ name: \"deleted_at\" })\n deletedAt?: Date;\n}\n```\n\n### 4. Complete the test file\n\nEdit `tests/entities/<Name>Entity.spec.ts` to add meaningful tests beyond the scaffolded ones:\n\n- Keep the existing scaffolded tests (class name, id, default columns)\n- Add tests for any new entity-specific columns and relations\n- Update tests if scaffolded columns were removed\n\nThe generated test structure follows this pattern:\n\n```typescript\nimport { describe, expect, test } from \"bun:test\";\nimport { <Name>Entity } from \"@/entities/<Name>Entity\";\n\ndescribe(\"<Name>Entity\", () => {\n test(\"should have class name ending with 'Entity'\", () => {\n expect(<Name>Entity.name.endsWith(\"Entity\")).toBe(true);\n });\n\n test(\"should have 'id' property with default nanoid\", () => {\n const entity = new <Name>Entity();\n expect(entity.id).toBeDefined();\n expect(typeof entity.id).toBe(\"string\");\n expect(entity.id.length).toBe(25);\n });\n\n test(\"should have 'isLocked' property\", () => {\n const entity = new <Name>Entity();\n expect(\"isLocked\" in entity).toBe(true);\n });\n\n // ... additional property tests\n});\n```\n\n### 5. Register the entity in the module\n\nAdd the new entity to the module's `entities` array in `src/<PascalModuleName>Module.ts` (e.g., `src/BookModule.ts` for the `book` module):\n\n```typescript\nimport type { ModuleType } from \"@ooneex/module\";\nimport { <Name>Entity } from \"./entities/<Name>Entity\";\n\nexport const <PascalModuleName>Module: ModuleType = {\n controllers: [],\n entities: [<Name>Entity],\n middlewares: [],\n cronJobs: [],\n events: [],\n};\n```\n\nThe module file uses PascalCase naming: `<PascalModuleName>Module.ts` with export `<PascalModuleName>Module`.\n\nIf the module already has other entities registered, append the new entity to the existing `entities` array and add the import alongside existing imports.\n\n### 6. Create a migration for the entity\n\nAfter creating or updating an entity, generate a migration to apply the corresponding schema changes to the database.\n\nRun the generator:\n\n```bash\nbunx @ooneex/cli@latest make:migration\n```\n\nThen read the generated migration file in `src/migrations/` and complete it:\n\n- In the `up` method, write the SQL to create the table (or alter it if updating an existing entity). Include all columns, types, constraints, defaults, and indexes matching the entity definition.\n- In the `down` method, write the reverse SQL to undo the changes (e.g., `DROP TABLE` or `ALTER TABLE DROP COLUMN`).\n- If the migration depends on another migration (e.g., a foreign key referencing another table), add that migration class to the `getDependencies()` return array.\n\nExample `up` method for a new entity:\n\n```typescript\npublic async up(tx: TransactionSQL): Promise<void> {\n await tx`\n CREATE TABLE IF NOT EXISTS <table_name> (\n id VARCHAR(25) PRIMARY KEY,\n is_locked BOOLEAN DEFAULT false,\n locked_at TIMESTAMPTZ,\n is_blocked BOOLEAN DEFAULT false,\n blocked_at TIMESTAMPTZ,\n block_reason TEXT,\n is_public BOOLEAN DEFAULT true,\n lang VARCHAR(10),\n created_at TIMESTAMPTZ DEFAULT NOW(),\n updated_at TIMESTAMPTZ DEFAULT NOW(),\n deleted_at TIMESTAMPTZ\n )\n `;\n}\n```\n\n### 7. Create a repository for the entity\n\nAfter creating the entity, generate a repository to handle database operations for it.\n\nRun the generator:\n\n```bash\nbunx @ooneex/cli@latest make:repository --name=<name>\n```\n\nWhere `<name>` is the same name used for the entity. The command will generate:\n- `src/repositories/<Name>Repository.ts` - The repository class\n- `tests/repositories/<Name>Repository.spec.ts` - The test file\n\nThen read the generated files and complete the repository implementation:\n\n- Adjust search fields in the `find()` method to match the entity's searchable columns\n- Customize relations loading in `findOne`/`findOneBy` if the entity has relations\n- Add any domain-specific methods relevant to the entity\n- Remove methods that don't apply to the entity\n- Update tests to match the final repository methods\n\n### 8. Lint and format\n\nRun linting and formatting on all generated files:\n\n```bash\nbunx biome check --fix src/entities/<Name>Entity.ts tests/entities/<Name>Entity.spec.ts src/repositories/<Name>Repository.ts tests/repositories/<Name>Repository.spec.ts src/migrations/\n```\n";
7934
+ describe("{{NAME}}Controller", () => {
7935
+ test("should have class name ending with 'Controller'", () => {
7936
+ expect({{NAME}}Controller.name.endsWith("Controller")).toBe(true);
7937
+ });
7733
7938
 
7734
- // src/templates/claude/skills/make.logger.md.txt
7735
- var make_logger_md_default = "---\nname: make:logger\ndescription: Generate a new logger class with its test file, then complete the generated code. Use when creating a new logger that implements the ILogger interface from @ooneex/logger.\n---\n\n# Make Logger Class\n\nGenerate a new logger class and its test file using the `make:logger` CLI command, then complete the generated code with proper implementation.\n\n## Coding Conventions\n\n- Always explicitly show visibility on class methods and properties (`private`, `public`, `protected`)\n- Always use arrow functions when possible, but NOT for class methods (class methods use regular method syntax)\n- End type names with the `Type` suffix (e.g., `ExceptionStackFrameType`, `ServiceDataType`)\n- Start interface names with the `I` prefix (e.g., `IException`, `IService`, `IAiChat`)\n- For Entity class, if property is optional, add `null` to its type.\n\n## Steps\n\n### 1. Run the generator\n\nRun the following command to scaffold the logger class and test files:\n\n```bash\nbunx @ooneex/cli@latest make:logger --name=<name> --module=<module>\n```\n\nWhere `<name>` is the name provided by the user. The `--module` option is optional \u2014 if provided, files are generated under `modules/<module>/` instead of the project root. The command will generate:\n- `src/loggers/<Name>Logger.ts` - The logger class file (or `modules/<module>/src/loggers/<Name>Logger.ts` with `--module`)\n- `tests/loggers/<Name>Logger.spec.ts` - The test file (or `modules/<module>/tests/loggers/<Name>Logger.spec.ts` with `--module`)\n\n### 2. Read the generated files\n\nRead both generated files to understand the scaffolded code.\n\n### 3. Complete the logger class\n\nEdit `src/loggers/<Name>Logger.ts` to complete the implementation:\n\n- Implement the `init()` method to set up the logger (e.g., open file handles, configure transports)\n- Implement `log`, `debug`, `info`, `success`, `warn`, and `error` methods with actual logging logic\n- Inject any required dependencies via the constructor\n\nThe generated class structure follows this pattern:\n\n```typescript\nimport type { IException } from \"@ooneex/exception\";\nimport { type ILogger, decorator } from \"@ooneex/logger\";\nimport type { ScalarType } from \"@ooneex/types\";\n\n@decorator.logger()\nexport class <Name>Logger implements ILogger {\n public async init(): Promise<void> {\n // Initialize your logger here\n }\n\n public log(message: string, data?: Record<string, ScalarType>): void {\n // Handle general logging\n }\n\n public debug(message: string, data?: Record<string, ScalarType>): void {\n // Handle debug logging\n }\n\n public info(message: string, data?: Record<string, ScalarType>): void {\n // Handle info logging\n }\n\n public success(message: string, data?: Record<string, ScalarType>): void {\n // Handle success logging\n }\n\n public warn(message: string, data?: Record<string, ScalarType>): void {\n // Handle warning logging\n }\n\n public error(message: string | IException, data?: Record<string, ScalarType>): void {\n // Handle error logging\n }\n}\n```\n\n### 4. Complete the test file\n\nEdit `tests/loggers/<Name>Logger.spec.ts` to add meaningful tests beyond the scaffolded ones:\n\n- Keep the existing scaffolded tests (class name, init, log, debug, info, success, warn, error methods)\n- Add tests relevant to the specific logger class behavior\n\nThe generated test structure follows this pattern:\n\n```typescript\nimport { describe, expect, test } from \"bun:test\";\nimport { <Name>Logger } from \"@/loggers/<Name>Logger\";\n\ndescribe(\"<Name>Logger\", () => {\n test(\"should have class name ending with 'Logger'\", () => {\n expect(<Name>Logger.name.endsWith(\"Logger\")).toBe(true);\n });\n\n test(\"should have 'init' method\", () => {\n expect(<Name>Logger.prototype.init).toBeDefined();\n expect(typeof <Name>Logger.prototype.init).toBe(\"function\");\n });\n\n test(\"should have 'log' method\", () => {\n expect(<Name>Logger.prototype.log).toBeDefined();\n expect(typeof <Name>Logger.prototype.log).toBe(\"function\");\n });\n\n test(\"should have 'debug' method\", () => {\n expect(<Name>Logger.prototype.debug).toBeDefined();\n expect(typeof <Name>Logger.prototype.debug).toBe(\"function\");\n });\n\n test(\"should have 'info' method\", () => {\n expect(<Name>Logger.prototype.info).toBeDefined();\n expect(typeof <Name>Logger.prototype.info).toBe(\"function\");\n });\n\n test(\"should have 'success' method\", () => {\n expect(<Name>Logger.prototype.success).toBeDefined();\n expect(typeof <Name>Logger.prototype.success).toBe(\"function\");\n });\n\n test(\"should have 'warn' method\", () => {\n expect(<Name>Logger.prototype.warn).toBeDefined();\n expect(typeof <Name>Logger.prototype.warn).toBe(\"function\");\n });\n\n test(\"should have 'error' method\", () => {\n expect(<Name>Logger.prototype.error).toBeDefined();\n expect(typeof <Name>Logger.prototype.error).toBe(\"function\");\n });\n});\n```\n\n### 5. Lint and format\n\nRun linting and formatting on the generated files:\n\n```bash\nbunx biome check --fix src/loggers/<Name>Logger.ts tests/loggers/<Name>Logger.spec.ts\n```\n";
7939
+ test("should have 'index' method", () => {
7940
+ expect({{NAME}}Controller.prototype.index).toBeDefined();
7941
+ expect(typeof {{NAME}}Controller.prototype.index).toBe("function");
7942
+ });
7943
+ });
7944
+ `;
7736
7945
 
7737
- // src/templates/claude/skills/make.mailer.md.txt
7738
- var make_mailer_md_default = "---\nname: make:mailer\ndescription: Generate a new mailer class with its template and test files, then complete the generated code. Use when creating a new email sender with JSX template using @ooneex/mailer.\n---\n\n# Make Mailer Class\n\nGenerate a new mailer class, its JSX template, and test files using the `make:mailer` CLI command, then complete the generated code with proper implementation.\n\n## Coding Conventions\n\n- Always explicitly show visibility on class methods and properties (`private`, `public`, `protected`)\n- Always use arrow functions when possible, but NOT for class methods (class methods use regular method syntax)\n- End type names with the `Type` suffix (e.g., `ExceptionStackFrameType`, `ServiceDataType`)\n- Start interface names with the `I` prefix (e.g., `IException`, `IService`, `IAiChat`)\n- For Entity class, if property is optional, add `null` to its type.\n\n## Steps\n\n### 1. Run the generator\n\nRun the following command to scaffold the mailer class and test files:\n\n```bash\nbunx @ooneex/cli@latest make:mailer --name=<name> --module=<module>\n```\n\nWhere `<name>` is the name provided by the user. The `--module` option is optional \u2014 if provided, files are generated under `modules/<module>/` instead of the project root. The command will generate (paths prefixed with `modules/<module>/` when `--module` is provided):\n- `src/mailers/<Name>Mailer.ts` - The mailer class file\n- `src/mailers/<Name>MailerTemplate.tsx` - The JSX email template\n- `tests/mailers/<Name>Mailer.spec.ts` - The mailer test file\n- `tests/mailers/<Name>MailerTemplate.spec.ts` - The template test file\n\n### 2. Read the generated files\n\nRead all four generated files to understand the scaffolded code.\n\n### 3. Complete the mailer class\n\nEdit `src/mailers/<Name>Mailer.ts` to complete the implementation:\n\n- Adjust the `send` method config type if additional parameters are needed\n- Add any pre-send logic (validation, data transformation, etc.)\n\nThe generated class structure follows this pattern:\n\n```typescript\nimport { inject } from \"@ooneex/container\";\nimport type { IMailer } from \"@ooneex/mailer\";\nimport { type <Name>MailerPropsType, <Name>MailerTemplate } from \"./<Name>MailerTemplate\";\n\nexport class <Name>Mailer implements IMailer {\n constructor(\n @inject(\"mailer\")\n private readonly mailer: IMailer,\n ) {}\n\n public send = async (config: {\n to: string[];\n subject: string;\n from?: { name: string; address: string };\n data?: <Name>MailerPropsType;\n }): Promise<void> => {\n const { data, ...rest } = config;\n\n await this.mailer.send({\n ...rest,\n content: <Name>MailerTemplate(data),\n });\n };\n}\n```\n\n### 4. Complete the mailer template\n\nEdit `src/mailers/<Name>MailerTemplate.tsx` to complete the implementation:\n\n- Update `<Name>MailerPropsType` with the actual props needed for the email\n- Build the email body using `MailerLayout` components (Header, Body, Footer)\n- Add email content, styling, and dynamic data rendering\n\nThe generated template structure follows this pattern:\n\n```tsx\nimport { MailerLayout } from \"@ooneex/mailer\";\n\nexport type <Name>MailerPropsType = {\n link: string;\n};\n\nexport const <Name>MailerTemplate = (props?: <Name>MailerPropsType) => (\n <MailerLayout>\n <MailerLayout.Header />\n <MailerLayout.Body>\n <a href={props?.link}>Login</a>\n </MailerLayout.Body>\n <MailerLayout.Footer />\n </MailerLayout>\n);\n```\n\n### 5. Complete the test files\n\nEdit `tests/mailers/<Name>Mailer.spec.ts` and `tests/mailers/<Name>MailerTemplate.spec.ts` to add meaningful tests beyond the scaffolded ones.\n\n### 6. Lint and format\n\nRun linting and formatting on the generated files:\n\n```bash\nbunx biome check --fix src/mailers/<Name>Mailer.ts src/mailers/<Name>MailerTemplate.tsx tests/mailers/<Name>Mailer.spec.ts tests/mailers/<Name>MailerTemplate.spec.ts\n```\n";
7946
+ // src/templates/controller.txt
7947
+ var controller_default = `import type { ContextType } from "@ooneex/controller";
7948
+ import { ERole } from "@ooneex/role";
7949
+ import { Route } from "@ooneex/routing";
7950
+ import { Assert } from "@ooneex/validation";
7739
7951
 
7740
- // src/templates/claude/skills/make.middleware.md.txt
7741
- var make_middleware_md_default = "---\nname: make:middleware\ndescription: Generate a new middleware class with its test file, then complete the generated code. Use when creating a new HTTP or WebSocket middleware that implements IMiddleware from @ooneex/middleware.\n---\n\n# Make Middleware Class\n\nGenerate a new middleware class and its test file using the `make:middleware` CLI command, then complete the generated code with proper implementation.\n\n## Coding Conventions\n\n- Always explicitly show visibility on class methods and properties (`private`, `public`, `protected`)\n- Always use arrow functions when possible, but NOT for class methods (class methods use regular method syntax)\n- End type names with the `Type` suffix (e.g., `ExceptionStackFrameType`, `ServiceDataType`)\n- Start interface names with the `I` prefix (e.g., `IException`, `IService`, `IAiChat`)\n- For Entity class, if property is optional, add `null` to its type.\n\n## Steps\n\n### 1. Run the generator\n\nRun the following command to scaffold the middleware class and test files:\n\n```bash\nbunx @ooneex/cli@latest make:middleware --name=<name> --module=<module> --is-socket=<true|false>\n```\n\nWhere `<name>` is the name provided by the user. The `--module` option is optional \u2014 if provided, files are generated under `modules/<module>/` instead of the project root. The `--is-socket` option determines whether to generate an HTTP middleware or a WebSocket middleware (defaults to `false` if omitted). The command will generate:\n- `src/middlewares/<Name>Middleware.ts` - The middleware class file (or `modules/<module>/src/middlewares/<Name>Middleware.ts` with `--module`)\n- `tests/middlewares/<Name>Middleware.spec.ts` - The test file (or `modules/<module>/tests/middlewares/<Name>Middleware.spec.ts` with `--module`)\n\n### 2. Read the generated files\n\nRead both generated files to understand the scaffolded code.\n\n### 3. Complete the middleware class\n\nEdit `src/middlewares/<Name>Middleware.ts` to complete the implementation:\n\n- Implement the `handler` method with actual middleware logic\n- Add request/response transformations, authentication checks, logging, etc.\n- Inject any required dependencies via the constructor\n\n**HTTP middleware** generated structure:\n\n```typescript\nimport type { ContextType } from \"@ooneex/controller\";\nimport { decorator, type IMiddleware } from \"@ooneex/middleware\";\n\n@decorator.middleware()\nexport class <Name>Middleware implements IMiddleware {\n public async handler(context: ContextType): Promise<ContextType> {\n // Example: Add custom header\n // context.response.header(\"X-Custom-Header\", \"value\");\n\n return context\n }\n}\n```\n\n**Socket middleware** generated structure:\n\n```typescript\nimport type { ContextType } from \"@ooneex/socket\";\nimport { decorator, type IMiddleware } from \"@ooneex/middleware\";\n\n@decorator.middleware()\nexport class <Name>Middleware implements IMiddleware {\n public async handler(context: ContextType): Promise<ContextType> {\n // Example: Add custom header\n // context.response.header(\"X-Custom-Header\", \"value\");\n\n return context\n }\n}\n```\n\n### 4. Complete the test file\n\nEdit `tests/middlewares/<Name>Middleware.spec.ts` to add meaningful tests beyond the scaffolded ones:\n\n- Keep the existing scaffolded tests (class name, handler method)\n- Add tests relevant to the specific middleware behavior\n\nThe generated test structure follows this pattern:\n\n```typescript\nimport { describe, expect, test } from \"bun:test\";\nimport { <Name>Middleware } from \"@/middlewares/<Name>Middleware\";\n\ndescribe(\"<Name>Middleware\", () => {\n test(\"should have class name ending with 'Middleware'\", () => {\n expect(<Name>Middleware.name.endsWith(\"Middleware\")).toBe(true);\n });\n\n test(\"should have 'handler' method\", () => {\n expect(<Name>Middleware.prototype.handler).toBeDefined();\n expect(typeof <Name>Middleware.prototype.handler).toBe(\"function\");\n });\n});\n```\n\n### 5. Register the middleware in the module\n\nAdd the new middleware to the module's `middlewares` array in `src/<PascalModuleName>Module.ts` (e.g., `src/BookModule.ts` for the `book` module):\n\n```typescript\nimport type { ModuleType } from \"@ooneex/module\";\nimport { <Name>Middleware } from \"./middlewares/<Name>Middleware\";\n\nexport const <PascalModuleName>Module: ModuleType = {\n controllers: [],\n entities: [],\n middlewares: [<Name>Middleware],\n cronJobs: [],\n events: [],\n};\n```\n\nThe module file uses PascalCase naming: `<PascalModuleName>Module.ts` with export `<PascalModuleName>Module`.\n\nIf the module already has other middlewares registered, append the new middleware to the existing `middlewares` array and add the import alongside existing imports.\n\n### 6. Lint and format\n\nRun linting and formatting on the generated files:\n\n```bash\nbunx biome check --fix src/middlewares/<Name>Middleware.ts tests/middlewares/<Name>Middleware.spec.ts\n```\n";
7952
+ export type {{TYPE_NAME}}RouteType = {
7953
+ params: {
7742
7954
 
7743
- // src/templates/claude/skills/make.migration.md.txt
7744
- var make_migration_md_default = "---\nname: make:migration\ndescription: Generate a new database migration file with its test file, then complete the generated code. Use when creating a new database migration for schema changes using @ooneex/migrations.\n---\n\n# Make Migration\n\nGenerate a new migration file and its test file using the `make:migration` CLI command, then complete the generated code with proper implementation.\n\n## Coding Conventions\n\n- Always explicitly show visibility on class methods and properties (`private`, `public`, `protected`)\n- Always use arrow functions when possible, but NOT for class methods (class methods use regular method syntax)\n- End type names with the `Type` suffix (e.g., `ExceptionStackFrameType`, `ServiceDataType`)\n- Start interface names with the `I` prefix (e.g., `IException`, `IService`, `IAiChat`)\n- For Entity class, if property is optional, add `null` to its type.\n\n## Steps\n\n### 1. Run the generator\n\nRun the following command to scaffold the migration file and test files:\n\n```bash\nbunx @ooneex/cli@latest make:migration --module=<module>\n```\n\nThe `--module` option is optional \u2014 if provided, files are generated under `modules/<module>/` instead of the project root. The command will generate:\n- `src/migrations/Migration<version>.ts` - The migration class file (or `modules/<module>/src/migrations/Migration<version>.ts` with `--module`)\n- `tests/migrations/Migration<version>.spec.ts` - The test file (or `modules/<module>/tests/migrations/Migration<version>.spec.ts` with `--module`)\n\nIt will also:\n- Generate a `migrations.ts` root export file in the migrations directory\n- Add a `migration:up` script to `package.json` if not already present\n\n### 2. Read the generated files\n\nRead both generated files to understand the scaffolded code.\n\n### 3. Complete the migration\n\nEdit `src/migrations/Migration<version>.ts` to implement:\n\n- The `up` method with the schema changes (create tables, add columns, create indexes, etc.)\n- The `down` method with the reverse operations to undo the migration\n\n### 4. Complete the test file\n\nEdit `tests/migrations/Migration<version>.spec.ts` to add meaningful tests beyond the scaffolded ones:\n\n- Keep the existing scaffolded tests (class name, up, down, getVersion, getDependencies methods)\n- Add tests relevant to the specific migration behavior\n\nThe generated test structure follows this pattern:\n\n```typescript\nimport { describe, expect, test } from \"bun:test\";\nimport { Migration<version> } from \"@/migrations/Migration<version>\";\n\ndescribe(\"Migration<version>\", () => {\n test(\"should have class name starting with 'Migration'\", () => {\n expect(Migration<version>.name.startsWith(\"Migration\")).toBe(true);\n });\n\n test(\"should have 'up' method\", () => {\n expect(Migration<version>.prototype.up).toBeDefined();\n expect(typeof Migration<version>.prototype.up).toBe(\"function\");\n });\n\n test(\"should have 'down' method\", () => {\n expect(Migration<version>.prototype.down).toBeDefined();\n expect(typeof Migration<version>.prototype.down).toBe(\"function\");\n });\n\n test(\"should have 'getVersion' method\", () => {\n expect(Migration<version>.prototype.getVersion).toBeDefined();\n expect(typeof Migration<version>.prototype.getVersion).toBe(\"function\");\n });\n\n test(\"should have 'getDependencies' method\", () => {\n expect(Migration<version>.prototype.getDependencies).toBeDefined();\n expect(typeof Migration<version>.prototype.getDependencies).toBe(\"function\");\n });\n});\n```\n\n### 5. Lint and format\n\nRun linting and formatting on the generated files:\n\n```bash\nbunx biome check --fix src/migrations/Migration<version>.ts tests/migrations/Migration<version>.spec.ts\n```\n";
7955
+ },
7956
+ payload: {
7745
7957
 
7746
- // src/templates/claude/skills/make.permission.md.txt
7747
- var make_permission_md_default = "---\nname: make:permission\ndescription: Generate a new permission class with its test file, then complete the generated code. Use when creating a new permission that extends Permission from @ooneex/permission.\n---\n\n# Make Permission Class\n\nGenerate a new permission class and its test file using the `make:permission` CLI command, then complete the generated code with proper implementation.\n\n## Coding Conventions\n\n- Always explicitly show visibility on class methods and properties (`private`, `public`, `protected`)\n- Always use arrow functions when possible, but NOT for class methods (class methods use regular method syntax)\n- End type names with the `Type` suffix (e.g., `ExceptionStackFrameType`, `ServiceDataType`)\n- Start interface names with the `I` prefix (e.g., `IException`, `IService`, `IAiChat`)\n- For Entity class, if property is optional, add `null` to its type.\n\n## Steps\n\n### 1. Run the generator\n\nRun the following command to scaffold the permission class and test files:\n\n```bash\nbunx @ooneex/cli@latest make:permission --name=<name> --module=<module>\n```\n\nWhere `<name>` is the name provided by the user. The `--module` option is optional \u2014 if provided, files are generated under `modules/<module>/` instead of the project root. The command will generate:\n- `src/permissions/<Name>Permission.ts` - The permission class file (or `modules/<module>/src/permissions/<Name>Permission.ts` with `--module`)\n- `tests/permissions/<Name>Permission.spec.ts` - The test file (or `modules/<module>/tests/permissions/<Name>Permission.spec.ts` with `--module`)\n\n### 2. Read the generated files\n\nRead both generated files to understand the scaffolded code.\n\n### 3. Complete the permission class\n\nEdit `src/permissions/<Name>Permission.ts` to complete the implementation:\n\n- Implement the `allow()` method with permission rules using `this.ability.can()`\n- Implement the `setUserPermissions()` method with role-based permission logic\n- Define which actions (read, create, update, delete, manage) are allowed on which entities\n\nThe generated class structure follows this pattern:\n\n```typescript\nimport { decorator, Permission } from \"@ooneex/permission\";\nimport type { IUser } from \"@ooneex/user\";\n\n@decorator.permission()\nexport class <Name>Permission extends Permission {\n public allow(): this {\n // Example: Add permissions using this.ability.can()\n // this.ability.can(\"read\", \"YourEntity\");\n // this.ability.can([\"read\", \"update\"], \"YourEntity\", { userId: user.id });\n\n return this;\n }\n\n public setUserPermissions(user: IUser | null): this {\n if (!user) {\n return this;\n }\n\n // Example: Grant full access to admins\n // const { roles } = user;\n // if (roles.includes(ERole.ADMIN)) {\n // this.ability.can(\"manage\", \"all\");\n // return this;\n // }\n\n // Example: Grant specific permissions based on roles\n // for (const role of roles) {\n // if (role === ERole.USER) {\n // this.ability.can([\"read\", \"update\"], \"YourEntity\", { userId: user.id });\n // }\n //\n // if (role === ERole.GUEST) {\n // this.ability.can(\"read\", \"YourEntity\", { public: true });\n // }\n // }\n\n return this;\n }\n}\n```\n\n### 4. Complete the test file\n\nEdit `tests/permissions/<Name>Permission.spec.ts` to add meaningful tests beyond the scaffolded ones:\n\n- Keep the existing scaffolded tests (class name, extends Permission, allow, setUserPermissions methods)\n- Add tests relevant to the specific permission rules\n\nThe generated test structure follows this pattern:\n\n```typescript\nimport { describe, expect, test } from \"bun:test\";\nimport { Permission } from \"@ooneex/permission\";\nimport { <Name>Permission } from \"@/permissions/<Name>Permission\";\n\ndescribe(\"<Name>Permission\", () => {\n test(\"should have class name ending with 'Permission'\", () => {\n expect(<Name>Permission.name.endsWith(\"Permission\")).toBe(true);\n });\n\n test(\"should extend Permission\", () => {\n const permission = new <Name>Permission();\n expect(permission).toBeInstanceOf(Permission);\n });\n\n test(\"should have 'allow' method\", () => {\n expect(<Name>Permission.prototype.allow).toBeDefined();\n expect(typeof <Name>Permission.prototype.allow).toBe(\"function\");\n });\n\n test(\"should have 'setUserPermissions' method\", () => {\n expect(<Name>Permission.prototype.setUserPermissions).toBeDefined();\n expect(typeof <Name>Permission.prototype.setUserPermissions).toBe(\"function\");\n });\n});\n```\n\n### 5. Register the permission in the module\n\nThe permission class is standalone and does not need to be registered in a module.\n\n### 6. Lint and format\n\nRun linting and formatting on the generated files:\n\n```bash\nbunx biome check --fix src/permissions/<Name>Permission.ts tests/permissions/<Name>Permission.spec.ts\n```\n";
7958
+ },
7959
+ queries: {
7748
7960
 
7749
- // src/templates/claude/skills/make.pubsub.md.txt
7750
- var make_pubsub_md_default = "---\nname: make:pubsub\ndescription: Generate a new PubSub event class with its test file, then complete the generated code. Use when creating a new publish/subscribe event that extends PubSub from @ooneex/pub-sub.\n---\n\n# Make PubSub Event Class\n\nGenerate a new PubSub event class and its test file using the `make:pubsub` CLI command, then complete the generated code with proper implementation.\n\n## Coding Conventions\n\n- Always explicitly show visibility on class methods and properties (`private`, `public`, `protected`)\n- Always use arrow functions when possible, but NOT for class methods (class methods use regular method syntax)\n- End type names with the `Type` suffix (e.g., `ExceptionStackFrameType`, `ServiceDataType`)\n- Start interface names with the `I` prefix (e.g., `IException`, `IService`, `IAiChat`)\n- For Entity class, if property is optional, add `null` to its type.\n\n## Steps\n\n### 1. Run the generator\n\nRun the following command to scaffold the PubSub event class and test files:\n\n```bash\nbunx @ooneex/cli@latest make:pubsub --name=<name> --module=<module> --channel=<channel>\n```\n\nWhere `<name>` is the name provided by the user. The `--module` option is optional \u2014 if provided, files are generated under `modules/<module>/` instead of the project root. The `--channel` option is optional \u2014 if omitted, it defaults to the kebab-case form of the name (e.g., `UserCreated` becomes `user-created`). The command will generate:\n- `src/events/<Name>Event.ts` - The event class file (or `modules/<module>/src/events/<Name>Event.ts` with `--module`)\n- `tests/events/<Name>Event.spec.ts` - The test file (or `modules/<module>/tests/events/<Name>Event.spec.ts` with `--module`)\n\n### 2. Read the generated files\n\nRead both generated files to understand the scaffolded code.\n\n### 3. Complete the PubSub event class\n\nEdit `src/events/<Name>Event.ts` to complete the implementation:\n\n- Define a proper data type instead of `Record<string, ScalarType>` for the event payload\n- Implement the `handler()` method with actual event handling logic\n- Set the appropriate channel name in `getChannel()`\n\nThe generated class structure follows this pattern:\n\n```typescript\nimport { inject } from \"@ooneex/container\";\nimport type { ScalarType } from \"@ooneex/types\";\nimport { decorator, PubSub, RedisPubSub } from \"@ooneex/pub-sub\";\n\n@decorator.pubSub()\nexport class <Name>Event<Data extends Record<string, ScalarType> = Record<string, ScalarType>> extends PubSub<Data> {\n constructor(\n @inject(RedisPubSub)\n client: RedisPubSub<Data>,\n ) {\n super(client);\n }\n\n public getChannel(): string {\n return \"<channel>\";\n }\n\n public async handler(context: { data: Data; channel: string }): Promise<void> {\n console.log(context);\n // TODO: Implement handler logic here\n }\n}\n```\n\n### 4. Complete the test file\n\nEdit `tests/events/<Name>Event.spec.ts` to add meaningful tests beyond the scaffolded ones:\n\n- Keep the existing scaffolded tests (class name, getChannel, handler, publish, subscribe, unsubscribe, unsubscribeAll methods)\n- Add tests relevant to the specific event behavior\n\nThe generated test structure follows this pattern:\n\n```typescript\nimport { describe, expect, test } from \"bun:test\";\nimport { <Name>PubSub } from \"@/pubsub/<Name>PubSub\";\n\ndescribe(\"<Name>PubSub\", () => {\n test(\"should have class name ending with 'PubSub'\", () => {\n expect(<Name>PubSub.name.endsWith(\"PubSub\")).toBe(true);\n });\n\n test(\"should have 'getChannel' method\", () => {\n expect(<Name>PubSub.prototype.getChannel).toBeDefined();\n expect(typeof <Name>PubSub.prototype.getChannel).toBe(\"function\");\n });\n\n test(\"should have 'handler' method\", () => {\n expect(<Name>PubSub.prototype.handler).toBeDefined();\n expect(typeof <Name>PubSub.prototype.handler).toBe(\"function\");\n });\n\n test(\"should have 'publish' method\", () => {\n expect(<Name>PubSub.prototype.publish).toBeDefined();\n expect(typeof <Name>PubSub.prototype.publish).toBe(\"function\");\n });\n\n test(\"should have 'subscribe' method\", () => {\n expect(<Name>PubSub.prototype.subscribe).toBeDefined();\n expect(typeof <Name>PubSub.prototype.subscribe).toBe(\"function\");\n });\n\n test(\"should have 'unsubscribe' method\", () => {\n expect(<Name>PubSub.prototype.unsubscribe).toBeDefined();\n expect(typeof <Name>PubSub.prototype.unsubscribe).toBe(\"function\");\n });\n\n test(\"should have 'unsubscribeAll' method\", () => {\n expect(<Name>PubSub.prototype.unsubscribeAll).toBeDefined();\n expect(typeof <Name>PubSub.prototype.unsubscribeAll).toBe(\"function\");\n });\n});\n```\n\n### 5. Register the event in the module\n\nAdd the new event to the module's `events` array in `src/<PascalModuleName>Module.ts` (e.g., `src/BookModule.ts` for the `book` module):\n\n```typescript\nimport type { ModuleType } from \"@ooneex/module\";\nimport { <Name>Event } from \"./events/<Name>Event\";\n\nexport const <PascalModuleName>Module: ModuleType = {\n controllers: [],\n entities: [],\n middlewares: [],\n cronJobs: [],\n events: [<Name>Event],\n};\n```\n\nThe module file uses PascalCase naming: `<PascalModuleName>Module.ts` with export `<PascalModuleName>Module`.\n\nIf the module already has other events registered, append the new event to the existing `events` array and add the import alongside existing imports.\n\n### 6. Lint and format\n\nRun linting and formatting on the generated files:\n\n```bash\nbunx biome check --fix src/events/<Name>Event.ts tests/events/<Name>Event.spec.ts\n```\n";
7961
+ },
7962
+ response: {
7751
7963
 
7752
- // src/templates/claude/skills/make.repository.md.txt
7753
- var make_repository_md_default = "---\nname: make:repository\ndescription: Generate a new repository class with its test file, then complete the generated code. Use when creating a new TypeORM repository for database operations on an entity.\n---\n\n# Make Repository Class\n\nGenerate a new repository class and its test file using the `make:repository` CLI command, then complete the generated code with proper implementation.\n\n## Coding Conventions\n\n- Always explicitly show visibility on class methods and properties (`private`, `public`, `protected`)\n- Always use arrow functions when possible, but NOT for class methods (class methods use regular method syntax)\n- End type names with the `Type` suffix (e.g., `ExceptionStackFrameType`, `ServiceDataType`)\n- Start interface names with the `I` prefix (e.g., `IException`, `IService`, `IAiChat`)\n- For Entity class, if property is optional, add `null` to its type.\n\n## Steps\n\n### 1. Run the generator\n\nRun the following command to scaffold the repository class and test files:\n\n```bash\nbunx @ooneex/cli@latest make:repository --name=<name> --module=<module>\n```\n\nWhere `<name>` is the name provided by the user. The `--module` option is optional \u2014 if provided, files are generated under `modules/<module>/` instead of the project root. The command will generate:\n- `src/repositories/<Name>Repository.ts` - The repository class file (or `modules/<module>/src/repositories/<Name>Repository.ts` with `--module`)\n- `tests/repositories/<Name>Repository.spec.ts` - The test file (or `modules/<module>/tests/repositories/<Name>Repository.spec.ts` with `--module`)\n\n### 2. Read the generated files\n\nRead both generated files to understand the scaffolded code.\n\n### 3. Complete the repository class\n\nEdit `src/repositories/<Name>Repository.ts` to complete the implementation:\n\n- Verify the entity import path matches the actual entity location\n- Adjust the `find` method's search fields (default searches `name` with `ILike`)\n- Customize relations loading in `findOne`/`findOneBy` if needed\n\n#### Adding methods\n\nLook at the entity's fields, relations, and business context to determine if custom domain-specific methods are needed. For example:\n- A `SessionRepository` might need `revokeSession(sessionId: string)` and `revokeAllUserSessions(userId: string)`\n- A `NotificationRepository` might need `markAsRead(id: string)` and `markAllAsRead(userId: string)`\n- Entities with status fields may need methods like `archive(id: string)` or `activate(id: string)`\n\nRead related entities, services, or actions in the module to understand what operations the repository should support, then add the appropriate methods.\n\n#### Removing methods\n\nRemove scaffolded methods that don't make sense for the entity's context:\n- Remove `createMany`/`updateMany` if the entity is always managed individually (e.g., user profiles, settings)\n- Remove `delete` if the entity uses soft deletes only (use a custom `softDelete` or `archive` method instead)\n- Remove `find` if the entity is only ever accessed by ID or specific criteria (e.g., singleton config entities)\n- Remove `count` if there's no use case for counting records of this entity\n\nThe generated class structure follows this pattern:\n\n```typescript\nimport { inject } from \"@ooneex/container\";\nimport type { ITypeormDatabase } from \"@ooneex/database\";\nimport { decorator } from \"@ooneex/repository\";\nimport type { FilterResultType } from \"@ooneex/types\";\nimport type { FindManyOptions, FindOptionsWhere, Repository, SaveOptions, UpdateResult } from \"typeorm\";\nimport { ILike } from \"typeorm\";\nimport { <Name>Entity } from \"../entities/<Name>Entity\";\n\n@decorator.repository()\nexport class <Name>Repository {\n constructor(\n @inject(\"database\")\n private readonly database: ITypeormDatabase,\n ) {}\n\n public async open(): Promise<Repository<<Name>Entity>> {\n return await this.database.open(<Name>Entity);\n }\n\n public async close(): Promise<void> {\n await this.database.close();\n }\n\n public async find(\n criteria: FindManyOptions<<Name>Entity> & { page?: number; limit?: number; q?: string },\n ): Promise<FilterResultType<<Name>Entity>> {\n // ... pagination and search logic\n }\n\n public async findOne(id: string): Promise<<Name>Entity | null> { ... }\n public async findOneBy(criteria: FindOptionsWhere<<Name>Entity>): Promise<<Name>Entity | null> { ... }\n public async create(entity: <Name>Entity, options?: SaveOptions): Promise<<Name>Entity> { ... }\n public async createMany(entities: <Name>Entity[], options?: SaveOptions): Promise<<Name>Entity[]> { ... }\n public async update(entity: <Name>Entity, options?: SaveOptions): Promise<<Name>Entity> { ... }\n public async updateMany(entities: <Name>Entity[], options?: SaveOptions): Promise<<Name>Entity[]> { ... }\n public async delete(criteria: FindOptionsWhere<<Name>Entity> | FindOptionsWhere<<Name>Entity>[]): Promise<UpdateResult> { ... }\n public async count(criteria?: FindOptionsWhere<<Name>Entity> | FindOptionsWhere<<Name>Entity>[]): Promise<number> { ... }\n}\n```\n\n### 4. Complete the test file\n\nEdit `tests/repositories/<Name>Repository.spec.ts` to add meaningful tests beyond the scaffolded ones:\n\n- Keep scaffolded tests for methods that remain in the repository (remove tests for methods that were removed)\n- Add tests for any custom domain-specific methods added to the repository\n- Add tests relevant to the specific repository behavior\n\n### 5. Lint and format\n\nRun linting and formatting on the generated files:\n\n```bash\nbunx biome check --fix src/repositories/<Name>Repository.ts tests/repositories/<Name>Repository.spec.ts\n```\n";
7964
+ },
7965
+ };
7754
7966
 
7755
- // src/templates/claude/skills/make.seed.md.txt
7756
- var make_seed_md_default = "---\nname: make:seed\ndescription: Generate a new database seed file with its test file, then complete the generated code. Use when creating seed data for populating the database using @ooneex/seeds.\n---\n\n# Make Seed\n\nGenerate a new seed file and its test file using the `make:seed` CLI command, then complete the generated code with proper implementation.\n\n## Coding Conventions\n\n- Always explicitly show visibility on class methods and properties (`private`, `public`, `protected`)\n- Always use arrow functions when possible, but NOT for class methods (class methods use regular method syntax)\n- End type names with the `Type` suffix (e.g., `ExceptionStackFrameType`, `ServiceDataType`)\n- Start interface names with the `I` prefix (e.g., `IException`, `IService`, `IAiChat`)\n- For Entity class, if property is optional, add `null` to its type.\n\n## Steps\n\n### 1. Run the generator\n\nRun the following command to scaffold the seed file and test files:\n\n```bash\nbunx @ooneex/cli@latest make:seed --name=<name> --module=<module>\n```\n\nWhere `<name>` is the name provided by the user. The `--module` option is optional \u2014 if provided, files are generated under `modules/<module>/` instead of the project root. The command will generate:\n- `src/seeds/<Name>Seed.ts` - The seed class file (or `modules/<module>/src/seeds/<Name>Seed.ts` with `--module`)\n- `src/seeds/<name-seed>.yml` - The seed data file in kebab-case (or `modules/<module>/src/seeds/<name-seed>.yml` with `--module`)\n- `tests/seeds/<Name>Seed.spec.ts` - The test file (or `modules/<module>/tests/seeds/<Name>Seed.spec.ts` with `--module`)\n\nIt will also:\n- Generate a `seeds.ts` root export file in the seeds directory\n- Add a `seed:run` script to `package.json` if not already present\n\n### 2. Read the generated files\n\nRead all generated files to understand the scaffolded code.\n\n### 3. Complete the seed data\n\nEdit `src/seeds/<name-seed>.yml` to add the seed data entries. Each entry should have hardcoded nanoid values for `id` fields (generate via `bun -e \"import { random } from '@ooneex/utils'; console.log(random.nanoid())\"`)\n\n- Do NOT use sequential IDs like `\"item-1\"`, `\"item-2\"`\n- Ensure the same entity uses the same ID everywhere it appears\n\n### 4. Complete the seed class\n\nEdit `src/seeds/<Name>Seed.ts` to implement:\n\n- Import the relevant entity classes and repository\n- Use `resolve()` from `@ooneex/container` to get the repository instance\n- Map the imported YAML data to entity instances\n- Use the repository to persist the entities\n\n### 5. Complete the test file\n\nEdit `tests/seeds/<Name>Seed.spec.ts` to add meaningful tests beyond the scaffolded ones:\n\n- Keep the existing scaffolded tests (class name, run, isActive, getDependencies, data yml file)\n- Add tests relevant to the specific seed behavior\n\nThe generated test structure follows this pattern:\n\n```typescript\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { describe, expect, test } from \"bun:test\";\nimport { <Name>Seed } from \"@/seeds/<Name>Seed\";\n\ndescribe(\"<Name>Seed\", () => {\n test(\"should have class name ending with 'Seed'\", () => {\n expect(<Name>Seed.name.endsWith(\"Seed\")).toBe(true);\n });\n\n test(\"should have 'run' method\", () => {\n expect(<Name>Seed.prototype.run).toBeDefined();\n expect(typeof <Name>Seed.prototype.run).toBe(\"function\");\n });\n\n test(\"should have 'isActive' method\", () => {\n expect(<Name>Seed.prototype.isActive).toBeDefined();\n expect(typeof <Name>Seed.prototype.isActive).toBe(\"function\");\n });\n\n test(\"should have 'getDependencies' method\", () => {\n expect(<Name>Seed.prototype.getDependencies).toBeDefined();\n expect(typeof <Name>Seed.prototype.getDependencies).toBe(\"function\");\n });\n\n test(\"should have a data yml file\", () => {\n const dataFile = join(__dirname, \"..\", \"src\", \"seeds\", \"<name-seed>.yml\");\n expect(existsSync(dataFile)).toBe(true);\n });\n});\n```\n\n### 6. Lint and format\n\nRun linting and formatting on the generated files:\n\n```bash\nbunx biome check --fix src/seeds/<Name>Seed.ts src/seeds/<name-seed>.yml tests/seeds/<Name>Seed.spec.ts\n```\n";
7967
+ @Route.{{ROUTE_METHOD}}("{{ROUTE_PATH}}", {
7968
+ name: "{{ROUTE_NAME}}",
7969
+ version: 1,
7970
+ description: "",
7971
+ params: {
7972
+ // id: Assert("string"),
7973
+ },
7974
+ payload: Assert({
7757
7975
 
7758
- // src/templates/claude/skills/make.service.md.txt
7759
- var make_service_md_default = "---\nname: make:service\ndescription: Generate a new service class with its test file, then complete the generated code. Use when creating a new business logic service that implements IService from @ooneex/service.\n---\n\n# Make Service Class\n\nGenerate a new service class and its test file using the `make:service` CLI command, then complete the generated code with proper implementation.\n\n## Coding Conventions\n\n- Always explicitly show visibility on class methods and properties (`private`, `public`, `protected`)\n- Always use arrow functions when possible, but NOT for class methods (class methods use regular method syntax)\n- End type names with the `Type` suffix (e.g., `ExceptionStackFrameType`, `ServiceDataType`)\n- Start interface names with the `I` prefix (e.g., `IException`, `IService`, `IAiChat`)\n- For Entity class, if property is optional, add `null` to its type.\n\n## Steps\n\n### 1. Run the generator\n\nRun the following command to scaffold the service class and test files:\n\n```bash\nbunx @ooneex/cli@latest make:service --name=<name> --module=<module>\n```\n\nWhere `<name>` is the name provided by the user. The `--module` option is optional \u2014 if provided, files are generated under `modules/<module>/` instead of the project root. The command will generate:\n- `src/services/<Name>Service.ts` - The service class file (or `modules/<module>/src/services/<Name>Service.ts` with `--module`)\n- `tests/services/<Name>Service.spec.ts` - The test file (or `modules/<module>/tests/services/<Name>Service.spec.ts` with `--module`)\n\n### 2. Read the generated files\n\nRead both generated files to understand the scaffolded code.\n\n### 3. Complete the service class\n\nEdit `src/services/<Name>Service.ts` to complete the implementation:\n\n- Define a proper type for `ServiceDataType` instead of `Record<string, unknown>`\n- Implement the `execute()` method with actual business logic\n- Inject any required dependencies (repositories, other services, etc.) via the constructor\n\nThe generated class structure follows this pattern:\n\n```typescript\nimport { type IService, decorator } from \"@ooneex/service\";\n\ntype ServiceDataType = Record<string, unknown>;\n\n@decorator.service()\nexport class <Name>Service implements IService {\n public async execute(data?: ServiceDataType): Promise<void> {\n // TODO: Implement service logic\n }\n}\n```\n\n### 4. Complete the test file\n\nEdit `tests/services/<Name>Service.spec.ts` to add meaningful tests beyond the scaffolded ones:\n\n- Keep the existing scaffolded tests (class name, execute method)\n- Add tests relevant to the specific service behavior\n\nThe generated test structure follows this pattern:\n\n```typescript\nimport { describe, expect, test } from \"bun:test\";\nimport { <Name>Service } from \"@/services/<Name>Service\";\n\ndescribe(\"<Name>Service\", () => {\n test(\"should have class name ending with 'Service'\", () => {\n expect(<Name>Service.name.endsWith(\"Service\")).toBe(true);\n });\n\n test(\"should have 'execute' method\", () => {\n expect(<Name>Service.prototype.execute).toBeDefined();\n expect(typeof <Name>Service.prototype.execute).toBe(\"function\");\n });\n});\n```\n\n### 5. Lint and format\n\nRun linting and formatting on the generated files:\n\n```bash\nbunx biome check --fix src/services/<Name>Service.ts tests/services/<Name>Service.spec.ts\n```\n";
7976
+ }),
7977
+ queries: Assert({
7760
7978
 
7761
- // src/templates/claude/skills/make.storage.md.txt
7762
- var make_storage_md_default = "---\nname: make:storage\ndescription: Generate a new storage class with its test file, then complete the generated code. Use when creating a new S3-compatible storage adapter that extends Storage from @ooneex/storage.\n---\n\n# Make Storage Class\n\nGenerate a new storage class and its test file using the `make:storage` CLI command, then complete the generated code with proper implementation.\n\n## Coding Conventions\n\n- Always explicitly show visibility on class methods and properties (`private`, `public`, `protected`)\n- Always use arrow functions when possible, but NOT for class methods (class methods use regular method syntax)\n- End type names with the `Type` suffix (e.g., `ExceptionStackFrameType`, `ServiceDataType`)\n- Start interface names with the `I` prefix (e.g., `IException`, `IService`, `IAiChat`)\n- For Entity class, if property is optional, add `null` to its type.\n\n## Steps\n\n### 1. Run the generator\n\nRun the following command to scaffold the storage class and test files:\n\n```bash\nbunx @ooneex/cli@latest make:storage --name=<name> --module=<module>\n```\n\nWhere `<name>` is the name provided by the user. The `--module` option is optional \u2014 if provided, files are generated under `modules/<module>/` instead of the project root. The command will generate:\n- `src/storage/<Name>Storage.ts` - The storage class file (or `modules/<module>/src/storage/<Name>Storage.ts` with `--module`)\n- `tests/storage/<Name>Storage.spec.ts` - The test file (or `modules/<module>/tests/storage/<Name>Storage.spec.ts` with `--module`)\n\n### 2. Read the generated files\n\nRead both generated files to understand the scaffolded code.\n\n### 3. Complete the storage class\n\nEdit `src/storage/<Name>Storage.ts` to complete the implementation:\n\n- Set the `bucket` property to the appropriate bucket name\n- Verify the environment variable names match the project configuration (`STORAGE_<NAME_UPPER>_ACCESS_KEY`, `STORAGE_<NAME_UPPER>_SECRET_KEY`, `STORAGE_<NAME_UPPER>_ENDPOINT`, `STORAGE_<NAME_UPPER>_REGION`)\n- Add any additional storage-specific methods if needed\n\nThe generated class structure follows this pattern:\n\n```typescript\nimport { Storage, decorator, StorageException } from \"@ooneex/storage\";\nimport type { S3Options } from \"bun\";\n\n@decorator.storage()\nexport class <Name>Storage extends Storage {\n protected bucket: string;\n private readonly accessKey: string;\n private readonly secretKey: string;\n private readonly endpoint: string;\n private readonly region: string;\n\n constructor(options?: {\n accessKey?: string;\n secretKey?: string;\n endpoint?: string;\n region?: string;\n }) {\n super();\n\n const accessKey = options?.accessKey || Bun.env.STORAGE_<NAME_UPPER>_ACCESS_KEY;\n const secretKey = options?.secretKey || Bun.env.STORAGE_<NAME_UPPER>_SECRET_KEY;\n const endpoint = options?.endpoint || Bun.env.STORAGE_<NAME_UPPER>_ENDPOINT;\n\n // ... validation throws StorageException if missing ...\n\n this.accessKey = accessKey;\n this.secretKey = secretKey;\n this.endpoint = endpoint;\n this.region = options?.region || Bun.env.STORAGE_<NAME_UPPER>_REGION || \"auto\";\n }\n\n public getOptions(): S3Options {\n return {\n accessKeyId: this.accessKey,\n secretAccessKey: this.secretKey,\n endpoint: this.endpoint,\n bucket: this.bucket,\n region: this.region,\n };\n }\n}\n```\n\n### 4. Complete the test file\n\nEdit `tests/storage/<Name>Storage.spec.ts` to add meaningful tests beyond the scaffolded ones:\n\n- Keep the existing scaffolded tests (class name, bucket field, getOptions method)\n- Add tests relevant to the specific storage class behavior\n\nThe generated test structure follows this pattern:\n\n```typescript\nimport { describe, expect, test } from \"bun:test\";\nimport { <Name>StorageAdapter } from \"@/storage/<Name>StorageAdapter\";\n\ndescribe(\"<Name>StorageAdapter\", () => {\n test(\"should have class name ending with 'StorageAdapter'\", () => {\n expect(<Name>StorageAdapter.name.endsWith(\"StorageAdapter\")).toBe(true);\n });\n\n test(\"should have 'bucket' field\", () => {\n expect(\"bucket\" in <Name>StorageAdapter.prototype || \"bucket\" in Object.getOwnPropertyNames(<Name>StorageAdapter.prototype)).toBe(true);\n });\n\n test(\"should have 'getOptions' method\", () => {\n expect(<Name>StorageAdapter.prototype.getOptions).toBeDefined();\n expect(typeof <Name>StorageAdapter.prototype.getOptions).toBe(\"function\");\n });\n});\n```\n\n### 5. Lint and format\n\nRun linting and formatting on the generated files:\n\n```bash\nbunx biome check --fix src/storage/<Name>Storage.ts tests/storage/<Name>Storage.spec.ts\n```\n";
7979
+ }),
7980
+ response: Assert({
7763
7981
 
7764
- // src/templates/claude/skills/make.vector-database.md.txt
7765
- var make_vector_database_md_default = "---\nname: make:vector-database\ndescription: Generate a new vector database class with its test file, then complete the generated code. Use when creating a new vector database that extends VectorDatabase from @ooneex/rag.\n---\n\n# Make Vector Database Class\n\nGenerate a new vector database class and its test file using the `make:vector-database` CLI command, then complete the generated code with proper implementation.\n\n## Coding Conventions\n\n- Always explicitly show visibility on class methods and properties (`private`, `public`, `protected`)\n- Always use arrow functions when possible, but NOT for class methods (class methods use regular method syntax)\n- End type names with the `Type` suffix (e.g., `ExceptionStackFrameType`, `ServiceDataType`)\n- Start interface names with the `I` prefix (e.g., `IException`, `IService`, `IAiChat`)\n- For Entity class, if property is optional, add `null` to its type.\n\n## Steps\n\n### 1. Run the generator\n\nRun the following command to scaffold the vector database class and test files:\n\n```bash\nbunx @ooneex/cli@latest make:vector-database --name=<name> --module=<module>\n```\n\nWhere `<name>` is the name provided by the user. The `--module` option is optional \u2014 if provided, files are generated under `modules/<module>/` instead of the project root. The command will generate:\n- `src/databases/<Name>VectorDatabase.ts` - The vector database class file (or `modules/<module>/src/databases/<Name>VectorDatabase.ts` with `--module`)\n- `tests/databases/<Name>VectorDatabase.spec.ts` - The test file (or `modules/<module>/tests/databases/<Name>VectorDatabase.spec.ts` with `--module`)\n\n### 2. Read the generated files\n\nRead both generated files to understand the scaffolded code.\n\n### 3. Complete the vector database class\n\nEdit `src/databases/<Name>VectorDatabase.ts` to complete the implementation:\n\n- Set the `getDatabaseUri()` return value to the actual LanceDB database path\n- Configure the embedding provider and model in `getEmbeddingModel()`\n- Define the custom data fields in `DataType` and map them in `getSchema()`\n- Import the appropriate Apache Arrow types for your schema fields\n\nThe generated class structure follows this pattern:\n\n```typescript\nimport { type EmbeddingModelType, type EmbeddingProviderType, type FieldValueType, VectorDatabase, decorator } from \"@ooneex/rag\";\nimport { Utf8 } from \"apache-arrow\";\n\ntype DataType = {\n name: string;\n};\n\n@decorator.vectorDatabase()\nexport class <Name>VectorDatabase extends VectorDatabase<DataType> {\n public getDatabaseUri(): string {\n return \"\";\n }\n\n public getEmbeddingModel(): { provider: EmbeddingProviderType; model: EmbeddingModelType[\"model\"] } {\n return { provider: \"openai\", model: \"text-embedding-ada-002\" };\n }\n\n public getSchema(): { [K in keyof DataType]: FieldValueType } {\n return {\n name: new Utf8(),\n };\n }\n}\n```\n\n### 4. Complete the test file\n\nEdit `tests/databases/<Name>VectorDatabase.spec.ts` to add meaningful tests beyond the scaffolded ones:\n\n- Keep the existing scaffolded tests (class name, getDatabaseUri, getEmbeddingModel, getSchema methods)\n- Add tests relevant to the specific vector database class behavior\n\nThe generated test structure follows this pattern:\n\n```typescript\nimport { describe, expect, test } from \"bun:test\";\nimport { <Name>VectorDatabase } from \"@/databases/<Name>VectorDatabase\";\n\ndescribe(\"<Name>VectorDatabase\", () => {\n test(\"should have class name ending with 'VectorDatabase'\", () => {\n expect(<Name>VectorDatabase.name.endsWith(\"VectorDatabase\")).toBe(true);\n });\n\n test(\"should have 'getDatabaseUri' method\", () => {\n expect(<Name>VectorDatabase.prototype.getDatabaseUri).toBeDefined();\n expect(typeof <Name>VectorDatabase.prototype.getDatabaseUri).toBe(\"function\");\n });\n\n test(\"should have 'getEmbeddingModel' method\", () => {\n expect(<Name>VectorDatabase.prototype.getEmbeddingModel).toBeDefined();\n expect(typeof <Name>VectorDatabase.prototype.getEmbeddingModel).toBe(\"function\");\n });\n\n test(\"should have 'getSchema' method\", () => {\n expect(<Name>VectorDatabase.prototype.getSchema).toBeDefined();\n expect(typeof <Name>VectorDatabase.prototype.getSchema).toBe(\"function\");\n });\n});\n```\n\n### 5. Lint and format\n\nRun linting and formatting on the generated files:\n\n```bash\nbunx biome check --fix src/databases/<Name>VectorDatabase.ts tests/databases/<Name>VectorDatabase.spec.ts\n```\n";
7982
+ }),
7983
+ roles: [ERole.USER],
7984
+ })
7985
+ export class {{NAME}}Controller {
7986
+ public async index(context: ContextType<{{TYPE_NAME}}RouteType>) {
7987
+ // const { id } = context.params;
7766
7988
 
7767
- // src/templates/claude/skills/optimize.md.txt
7768
- var optimize_md_default = "---\nname: optimize\ndescription: Optimize a module's codebase for quality, performance, and clean conventions. Enforces arrow functions (except class methods), type/interface naming, removes duplication, and ensures only important tests remain.\n---\n\n# Optimize Codebase\n\nOptimize a module's codebase for quality, performance, and clean conventions.\n\n## Coding Conventions\n\nApply these conventions across all files in the target module:\n\n- **Arrow functions everywhere** \u2014 use arrow functions for all function expressions, callbacks, standalone functions, and variable declarations. The ONLY exception is class methods, which must use regular method syntax.\n- **Type naming** \u2014 all type aliases must end with the `Type` suffix (e.g., `UserDataType`, `ConfigOptionsType`). Rename any that don't comply.\n- **Interface naming** \u2014 all interface names must start with the `I` prefix (e.g., `IUser`, `IService`, `IRepository`). Rename any that don't comply.\n- **No definite assignment assertions** \u2014 never use `!` on class properties (e.g., `email!: string`). Use a default value or make the property optional (`?`) instead.\n- Always explicitly show visibility on class methods and properties (`private`, `public`, `protected`).\n- For Entity class, if property is optional, add `null` to its type.\n\n## Steps\n\n### 1. Identify the target\n\nDetermine the module to optimize. If the user specifies a module name, work in `modules/<module>/`. If no module is specified, ask the user which module to optimize.\n\n### 2. Read and analyze the module\n\nRead all source files (`src/**/*.ts`) and test files (`tests/**/*.ts`) in the target module. Build a full picture of:\n\n- All types, interfaces, classes, and functions\n- Dependencies between files\n- Existing test coverage\n\n### 3. Enforce naming conventions\n\nScan every file and fix:\n\n- **Types not ending with `Type`** \u2014 rename the type and update all references across the module\n- **Interfaces not starting with `I`** \u2014 rename the interface and update all references across the module\n- **Non-arrow functions** \u2014 convert all function expressions, callbacks, and standalone functions to arrow functions. Do NOT convert class methods.\n- **Missing visibility modifiers** \u2014 add explicit `public`, `private`, or `protected` to all class methods and properties\n\n### 4. Remove code duplication\n\nIdentify duplicated or near-duplicated code within the module:\n\n- Extract shared logic into well-named helper arrow functions or base classes\n- Consolidate repeated type definitions\n- Merge similar utility functions\n- Remove dead code (unused imports, unreachable branches, unused variables)\n\n### 5. Optimize for performance\n\nReview and improve:\n\n- Replace inefficient loops or repeated iterations with single-pass approaches where possible\n- Use `Map`/`Set` instead of arrays for lookups when appropriate\n- Avoid unnecessary object spreads or deep clones\n- Prefer early returns to reduce nesting\n- Remove unnecessary `async`/`await` where a direct return suffices\n- Eliminate redundant null/undefined checks when the type system already guarantees the value\n\n### 6. Optimize tests\n\nReview all test files and restructure:\n\n- **Remove trivial tests** \u2014 delete tests that only check obvious things (e.g., \"class name ends with X\", \"method exists\") unless they serve as smoke tests for generated code\n- **Keep and improve important tests** \u2014 focus on tests that verify actual business logic, edge cases, error handling, and integration behavior\n- **Write fewer but more meaningful tests** \u2014 each test should validate a real scenario or invariant, not just existence checks\n- **Consolidate redundant test cases** \u2014 merge tests that cover the same code path with slightly different inputs into parameterized patterns\n- **Ensure critical paths are covered** \u2014 every public method with logic should have at least one test covering its happy path and one covering its error/edge case\n\n### 7. Final cleanup\n\n- Remove all unused imports\n- Remove empty files or files with no exports\n- Ensure consistent formatting\n\n### 8. Lint and format\n\nRun linting and formatting on all modified files:\n\n```bash\nbunx biome check --fix <list of modified files>\n```\n\n### 9. Run tests\n\nRun the module's tests to verify nothing is broken:\n\n```bash\nbun test <module test directory>\n```\n\nIf any test fails, fix the issue and re-run until all tests pass.\n";
7989
+ return context.response.json({
7769
7990
 
7770
- // src/commands/MakeClaudeSkillCommand.ts
7771
- var skills = {
7772
- "make.ai": make_ai_md_default,
7773
- "make.analytics": make_analytics_md_default,
7774
- "make.cache": make_cache_md_default,
7775
- "make.controller": make_controller_md_default,
7776
- "make.cron": make_cron_md_default,
7777
- "make.database": make_database_md_default,
7778
- "make.entity": make_entity_md_default,
7779
- "make.logger": make_logger_md_default,
7780
- "make.mailer": make_mailer_md_default,
7781
- "make.middleware": make_middleware_md_default,
7782
- "make.migration": make_migration_md_default,
7783
- "make.permission": make_permission_md_default,
7784
- "make.pubsub": make_pubsub_md_default,
7785
- "make.repository": make_repository_md_default,
7786
- "make.seed": make_seed_md_default,
7787
- "make.service": make_service_md_default,
7788
- "make.storage": make_storage_md_default,
7789
- "make.vector-database": make_vector_database_md_default,
7790
- commit: commit_md_default,
7791
- optimize: optimize_md_default
7792
- };
7991
+ });
7992
+ }
7993
+ }
7994
+ `;
7793
7995
 
7794
- class MakeClaudeSkillCommand {
7996
+ // src/commands/MakeControllerCommand.ts
7997
+ class MakeControllerCommand {
7795
7998
  getName() {
7796
- return "make:claude:skill";
7999
+ return "make:controller";
7797
8000
  }
7798
8001
  getDescription() {
7799
- return "Generate Claude skills from templates";
8002
+ return "Generate a new controller class";
7800
8003
  }
7801
- async run() {
7802
- const skillsLocalDir = join9(".claude", "skills");
7803
- const skillsDir = join9(process.cwd(), skillsLocalDir);
7804
- const logger = new TerminalLogger8;
7805
- for (const [skillName, content] of Object.entries(skills)) {
7806
- const dirName = skillName.replace(/\./g, "-");
7807
- const filePath = join9(skillsDir, dirName, "SKILL.md");
7808
- await Bun.write(filePath, content);
7809
- logger.success(`${join9(skillsLocalDir, dirName, "SKILL.md")} created successfully`, undefined, {
7810
- showTimestamp: false,
7811
- showArrow: false,
7812
- useSymbol: true
7813
- });
8004
+ async addToModule(modulePath, controllerName) {
8005
+ let content = await Bun.file(modulePath).text();
8006
+ const className = `${controllerName}Controller`;
8007
+ const importLine = `import { ${className} } from "./controllers/${className}";
8008
+ `;
8009
+ const lastImportIndex = content.lastIndexOf("import ");
8010
+ const lineEnd = content.indexOf(`
8011
+ `, lastImportIndex);
8012
+ content = `${content.slice(0, lineEnd + 1)}${importLine}${content.slice(lineEnd + 1)}`;
8013
+ const regex = /(controllers:\s*\[)([^\]]*)/s;
8014
+ const match = content.match(regex);
8015
+ if (match) {
8016
+ const existing = match[2]?.trim();
8017
+ const newValue = existing ? `${existing}, ${className}` : className;
8018
+ content = content.replace(regex, `$1${newValue}`);
7814
8019
  }
7815
- }
7816
- }
7817
- MakeClaudeSkillCommand = __legacyDecorateClassTS([
7818
- decorator8.command()
7819
- ], MakeClaudeSkillCommand);
7820
- // src/commands/MakeCommandCommand.ts
7821
- import { join as join10 } from "path";
7822
- import { commandCreate, decorator as decorator9 } from "@ooneex/command";
7823
- import { TerminalLogger as TerminalLogger9 } from "@ooneex/logger";
7824
- class MakeCommandCommand {
7825
- getName() {
7826
- return "make:command";
7827
- }
7828
- getDescription() {
7829
- return "Generate a new command class";
8020
+ await Bun.write(modulePath, content);
7830
8021
  }
7831
8022
  async run(options) {
7832
- let { name, module } = options;
8023
+ let { name, module, isSocket } = options;
7833
8024
  if (!name) {
7834
- name = await askName({ message: "Enter name" });
8025
+ name = await askName({ message: "Enter controller name" });
7835
8026
  }
7836
- if (module) {
7837
- await ensureModule(module);
8027
+ if (isSocket === undefined) {
8028
+ isSocket = await askConfirm({ message: "Is this a socket controller?" });
7838
8029
  }
7839
- const base = module ? join10("modules", module) : ".";
7840
- const { commandPath, testPath } = await commandCreate({
7841
- name,
7842
- commandDir: join10(base, "src", "commands"),
7843
- testsDir: join10(base, "tests", "commands")
7844
- });
7845
- if (module && module !== "app") {
7846
- const appCommandsRootPath = join10(process.cwd(), "modules", "app", "src", "commands", "commands.ts");
7847
- const appCommandsRootFile = Bun.file(appCommandsRootPath);
7848
- const importLine = `import "@${module}/commands/commands";`;
7849
- if (await appCommandsRootFile.exists()) {
7850
- const appCommandsContent = await appCommandsRootFile.text();
7851
- if (!appCommandsContent.includes(importLine)) {
7852
- await Bun.write(appCommandsRootPath, `${appCommandsContent.trimEnd()}
7853
- ${importLine}
7854
- `);
7855
- }
7856
- } else {
7857
- await Bun.write(appCommandsRootPath, `${importLine}
7858
- `);
7859
- }
8030
+ name = toPascalCase5(name).replace(/Controller$/, "");
8031
+ const { route = {} } = options;
8032
+ const selectedTemplate = isSocket ? controller_socket_default : controller_default;
8033
+ let content = selectedTemplate.replaceAll("{{NAME}}", name);
8034
+ if (!route.name) {
8035
+ route.name = await askRouteName({ message: "Enter route name (e.g., api.user.create)" });
7860
8036
  }
7861
- const binCommandRunPath = join10(process.cwd(), "modules", "app", "bin", "command", "run.ts");
7862
- const binCommandRunFile = Bun.file(binCommandRunPath);
7863
- if (!await binCommandRunFile.exists()) {
7864
- await Bun.write(binCommandRunPath, command_run_default);
8037
+ const routeTypeName = toPascalCase5(route.name);
8038
+ content = content.replaceAll("{{ROUTE_NAME}}", route.name).replaceAll("{{TYPE_NAME}}", routeTypeName);
8039
+ if (!route.path) {
8040
+ route.path = await askRoutePath({ message: "Enter route path", initial: "/" });
7865
8041
  }
7866
- const packageJsonPath = join10(process.cwd(), "modules", "app", "package.json");
7867
- const packageJsonFile = Bun.file(packageJsonPath);
7868
- if (await packageJsonFile.exists()) {
7869
- const packageJson = await packageJsonFile.json();
7870
- packageJson.scripts = packageJson.scripts || {};
7871
- packageJson.scripts.command = "bun ./bin/command/run.ts";
7872
- await Bun.write(packageJsonPath, JSON.stringify(packageJson, null, 2));
8042
+ const routePath = `/${toKebabCase3(trim(route.path, "/"))}`;
8043
+ content = content.replaceAll("{{ROUTE_PATH}}", routePath);
8044
+ if (!isSocket && !route.method) {
8045
+ route.method = await askRouteMethod({ message: "Enter route method" });
7873
8046
  }
7874
- const logger = new TerminalLogger9;
7875
- logger.success(`${commandPath} created successfully`, undefined, {
8047
+ if (!isSocket && route.method) {
8048
+ content = content.replaceAll("{{ROUTE_METHOD}}", route.method.toLowerCase());
8049
+ }
8050
+ if (module) {
8051
+ await ensureModule(module);
8052
+ }
8053
+ const base = module ? join13("modules", module) : ".";
8054
+ const controllersLocalDir = join13(base, "src", "controllers");
8055
+ const controllersDir = join13(process.cwd(), controllersLocalDir);
8056
+ const filePath = join13(controllersDir, `${name}Controller.ts`);
8057
+ await Bun.write(filePath, content);
8058
+ const testContent = controller_test_default.replace(/{{NAME}}/g, name);
8059
+ const testsLocalDir = join13(base, "tests", "controllers");
8060
+ const testsDir = join13(process.cwd(), testsLocalDir);
8061
+ const testFilePath = join13(testsDir, `${name}Controller.spec.ts`);
8062
+ await Bun.write(testFilePath, testContent);
8063
+ const modulePascalName = module ? toPascalCase5(module) : toPascalCase5(basename(process.cwd()));
8064
+ const modulePath = join13(process.cwd(), base, "src", `${modulePascalName}Module.ts`);
8065
+ if (await Bun.file(modulePath).exists()) {
8066
+ await this.addToModule(modulePath, name);
8067
+ }
8068
+ const logger = new TerminalLogger12;
8069
+ logger.success(`${join13(controllersLocalDir, name)}Controller.ts created successfully`, undefined, {
7876
8070
  showTimestamp: false,
7877
8071
  showArrow: false,
7878
8072
  useSymbol: true
7879
8073
  });
7880
- logger.success(`${testPath} created successfully`, undefined, {
8074
+ logger.success(`${join13(testsLocalDir, name)}Controller.spec.ts created successfully`, undefined, {
7881
8075
  showTimestamp: false,
7882
8076
  showArrow: false,
7883
8077
  useSymbol: true
7884
8078
  });
7885
- logger.info("Run 'bun run command' to execute commands", undefined, {
7886
- showTimestamp: false,
7887
- showArrow: true,
7888
- showLevel: false
7889
- });
8079
+ const packageJsonPath = join13(process.cwd(), "package.json");
8080
+ const packageJson = await Bun.file(packageJsonPath).json();
8081
+ const deps = packageJson.dependencies ?? {};
8082
+ const devDeps = packageJson.devDependencies ?? {};
8083
+ if (!deps["@ooneex/controller"] && !devDeps["@ooneex/controller"]) {
8084
+ const install = Bun.spawn(["bun", "add", "@ooneex/controller"], {
8085
+ cwd: process.cwd(),
8086
+ stdout: "ignore",
8087
+ stderr: "inherit"
8088
+ });
8089
+ await install.exited;
8090
+ }
7890
8091
  }
7891
8092
  }
7892
- MakeCommandCommand = __legacyDecorateClassTS([
7893
- decorator9.command()
7894
- ], MakeCommandCommand);
8093
+ MakeControllerCommand = __legacyDecorateClassTS([
8094
+ decorator13.command()
8095
+ ], MakeControllerCommand);
7895
8096
  // src/commands/MakeCronCommand.ts
7896
- import { basename as basename2, join as join11 } from "path";
7897
- import { decorator as decorator10 } from "@ooneex/command";
7898
- import { TerminalLogger as TerminalLogger10 } from "@ooneex/logger";
8097
+ import { basename as basename2, join as join14 } from "path";
8098
+ import { decorator as decorator14 } from "@ooneex/command";
8099
+ import { TerminalLogger as TerminalLogger13 } from "@ooneex/logger";
7899
8100
  import { toPascalCase as toPascalCase6 } from "@ooneex/utils";
7900
8101
 
7901
8102
  // src/templates/cron.test.txt
@@ -7984,33 +8185,33 @@ class MakeCronCommand {
7984
8185
  if (module) {
7985
8186
  await ensureModule(module);
7986
8187
  }
7987
- const base = module ? join11("modules", module) : ".";
7988
- const cronLocalDir = join11(base, "src", "crons");
7989
- const cronDir = join11(process.cwd(), cronLocalDir);
7990
- const filePath = join11(cronDir, `${name}Cron.ts`);
8188
+ const base = module ? join14("modules", module) : ".";
8189
+ const cronLocalDir = join14(base, "src", "crons");
8190
+ const cronDir = join14(process.cwd(), cronLocalDir);
8191
+ const filePath = join14(cronDir, `${name}Cron.ts`);
7991
8192
  await Bun.write(filePath, content);
7992
8193
  const testContent = cron_test_default.replace(/{{NAME}}/g, name);
7993
- const testsLocalDir = join11(base, "tests", "crons");
7994
- const testsDir = join11(process.cwd(), testsLocalDir);
7995
- const testFilePath = join11(testsDir, `${name}Cron.spec.ts`);
8194
+ const testsLocalDir = join14(base, "tests", "crons");
8195
+ const testsDir = join14(process.cwd(), testsLocalDir);
8196
+ const testFilePath = join14(testsDir, `${name}Cron.spec.ts`);
7996
8197
  await Bun.write(testFilePath, testContent);
7997
8198
  const modulePascalName = module ? toPascalCase6(module) : toPascalCase6(basename2(process.cwd()));
7998
- const modulePath = join11(process.cwd(), base, "src", `${modulePascalName}Module.ts`);
8199
+ const modulePath = join14(process.cwd(), base, "src", `${modulePascalName}Module.ts`);
7999
8200
  if (await Bun.file(modulePath).exists()) {
8000
8201
  await this.addToModule(modulePath, name);
8001
8202
  }
8002
- const logger = new TerminalLogger10;
8003
- logger.success(`${join11(cronLocalDir, name)}Cron.ts created successfully`, undefined, {
8203
+ const logger = new TerminalLogger13;
8204
+ logger.success(`${join14(cronLocalDir, name)}Cron.ts created successfully`, undefined, {
8004
8205
  showTimestamp: false,
8005
8206
  showArrow: false,
8006
8207
  useSymbol: true
8007
8208
  });
8008
- logger.success(`${join11(testsLocalDir, name)}Cron.spec.ts created successfully`, undefined, {
8209
+ logger.success(`${join14(testsLocalDir, name)}Cron.spec.ts created successfully`, undefined, {
8009
8210
  showTimestamp: false,
8010
8211
  showArrow: false,
8011
8212
  useSymbol: true
8012
8213
  });
8013
- const packageJsonPath = join11(process.cwd(), "package.json");
8214
+ const packageJsonPath = join14(process.cwd(), "package.json");
8014
8215
  const packageJson = await Bun.file(packageJsonPath).json();
8015
8216
  const deps = packageJson.dependencies ?? {};
8016
8217
  const devDeps = packageJson.devDependencies ?? {};
@@ -8025,12 +8226,12 @@ class MakeCronCommand {
8025
8226
  }
8026
8227
  }
8027
8228
  MakeCronCommand = __legacyDecorateClassTS([
8028
- decorator10.command()
8229
+ decorator14.command()
8029
8230
  ], MakeCronCommand);
8030
8231
  // src/commands/MakeDatabaseCommand.ts
8031
- import { join as join12 } from "path";
8032
- import { decorator as decorator11 } from "@ooneex/command";
8033
- import { TerminalLogger as TerminalLogger11 } from "@ooneex/logger";
8232
+ import { join as join15 } from "path";
8233
+ import { decorator as decorator15 } from "@ooneex/command";
8234
+ import { TerminalLogger as TerminalLogger14 } from "@ooneex/logger";
8034
8235
  import { toPascalCase as toPascalCase7 } from "@ooneex/utils";
8035
8236
 
8036
8237
  // src/templates/database.test.txt
@@ -8093,28 +8294,28 @@ class MakeDatabaseCommand {
8093
8294
  if (module) {
8094
8295
  await ensureModule(module);
8095
8296
  }
8096
- const base = module ? join12("modules", module) : ".";
8097
- const databaseLocalDir = join12(base, "src", "databases");
8098
- const databaseDir = join12(process.cwd(), databaseLocalDir);
8099
- const filePath = join12(databaseDir, `${name}Database.ts`);
8297
+ const base = module ? join15("modules", module) : ".";
8298
+ const databaseLocalDir = join15(base, "src", "databases");
8299
+ const databaseDir = join15(process.cwd(), databaseLocalDir);
8300
+ const filePath = join15(databaseDir, `${name}Database.ts`);
8100
8301
  await Bun.write(filePath, content);
8101
8302
  const testContent = database_test_default.replace(/{{NAME}}/g, name);
8102
- const testsLocalDir = join12(base, "tests", "databases");
8103
- const testsDir = join12(process.cwd(), testsLocalDir);
8104
- const testFilePath = join12(testsDir, `${name}Database.spec.ts`);
8303
+ const testsLocalDir = join15(base, "tests", "databases");
8304
+ const testsDir = join15(process.cwd(), testsLocalDir);
8305
+ const testFilePath = join15(testsDir, `${name}Database.spec.ts`);
8105
8306
  await Bun.write(testFilePath, testContent);
8106
- const logger = new TerminalLogger11;
8107
- logger.success(`${join12(databaseLocalDir, name)}Database.ts created successfully`, undefined, {
8307
+ const logger = new TerminalLogger14;
8308
+ logger.success(`${join15(databaseLocalDir, name)}Database.ts created successfully`, undefined, {
8108
8309
  showTimestamp: false,
8109
8310
  showArrow: false,
8110
8311
  useSymbol: true
8111
8312
  });
8112
- logger.success(`${join12(testsLocalDir, name)}Database.spec.ts created successfully`, undefined, {
8313
+ logger.success(`${join15(testsLocalDir, name)}Database.spec.ts created successfully`, undefined, {
8113
8314
  showTimestamp: false,
8114
8315
  showArrow: false,
8115
8316
  useSymbol: true
8116
8317
  });
8117
- const packageJsonPath = join12(process.cwd(), "package.json");
8318
+ const packageJsonPath = join15(process.cwd(), "package.json");
8118
8319
  const packageJson = await Bun.file(packageJsonPath).json();
8119
8320
  const deps = packageJson.dependencies ?? {};
8120
8321
  const devDeps = packageJson.devDependencies ?? {};
@@ -8129,12 +8330,12 @@ class MakeDatabaseCommand {
8129
8330
  }
8130
8331
  }
8131
8332
  MakeDatabaseCommand = __legacyDecorateClassTS([
8132
- decorator11.command()
8333
+ decorator15.command()
8133
8334
  ], MakeDatabaseCommand);
8134
8335
  // src/commands/MakeDockerCommand.ts
8135
- import { join as join13 } from "path";
8136
- import { decorator as decorator12 } from "@ooneex/command";
8137
- import { TerminalLogger as TerminalLogger12 } from "@ooneex/logger";
8336
+ import { join as join16 } from "path";
8337
+ import { decorator as decorator16 } from "@ooneex/command";
8338
+ import { TerminalLogger as TerminalLogger15 } from "@ooneex/logger";
8138
8339
  var {YAML } = globalThis.Bun;
8139
8340
 
8140
8341
  // src/prompts/askDockerService.ts
@@ -8627,9 +8828,9 @@ class MakeDockerCommand {
8627
8828
  name = await askDockerService({ message: "Select docker service" });
8628
8829
  }
8629
8830
  const templateContent = templates[name];
8630
- const base = join13("modules", "app");
8631
- const composePath = join13(process.cwd(), base, "docker-compose.yml");
8632
- const logger = new TerminalLogger12;
8831
+ const base = join16("modules", "app");
8832
+ const composePath = join16(process.cwd(), base, "docker-compose.yml");
8833
+ const logger = new TerminalLogger15;
8633
8834
  const composeFile = Bun.file(composePath);
8634
8835
  if (await composeFile.exists()) {
8635
8836
  const existingContent = await composeFile.text();
@@ -8688,7 +8889,7 @@ volumes:
8688
8889
  } else {
8689
8890
  await Bun.write(composePath, templateContent);
8690
8891
  }
8691
- const packageJsonPath = join13(process.cwd(), base, "package.json");
8892
+ const packageJsonPath = join16(process.cwd(), base, "package.json");
8692
8893
  const packageJsonFile = Bun.file(packageJsonPath);
8693
8894
  if (await packageJsonFile.exists()) {
8694
8895
  const packageJson = await packageJsonFile.json();
@@ -8709,13 +8910,13 @@ volumes:
8709
8910
  }
8710
8911
  }
8711
8912
  MakeDockerCommand = __legacyDecorateClassTS([
8712
- decorator12.command()
8913
+ decorator16.command()
8713
8914
  ], MakeDockerCommand);
8714
8915
  // src/commands/MakeEntityCommand.ts
8715
- var import_pluralize = __toESM(require_pluralize(), 1);
8716
- import { basename as basename3, join as join14 } from "path";
8717
- import { decorator as decorator13 } from "@ooneex/command";
8718
- import { TerminalLogger as TerminalLogger13 } from "@ooneex/logger";
8916
+ var import_pluralize = __toESM(require_pluralize(), 1);
8917
+ import { basename as basename3, join as join17 } from "path";
8918
+ import { decorator as decorator17 } from "@ooneex/command";
8919
+ import { TerminalLogger as TerminalLogger16 } from "@ooneex/logger";
8719
8920
  import { toPascalCase as toPascalCase8, toSnakeCase as toSnakeCase2 } from "@ooneex/utils";
8720
8921
 
8721
8922
  // src/templates/entity.test.txt
@@ -8879,28 +9080,28 @@ class MakeEntityCommand {
8879
9080
  if (module) {
8880
9081
  await ensureModule(module);
8881
9082
  }
8882
- const base = module ? join14("modules", module) : ".";
8883
- const entitiesLocalDir = join14(base, "src", "entities");
8884
- const entitiesDir = join14(process.cwd(), entitiesLocalDir);
8885
- const filePath = join14(entitiesDir, `${name}Entity.ts`);
9083
+ const base = module ? join17("modules", module) : ".";
9084
+ const entitiesLocalDir = join17(base, "src", "entities");
9085
+ const entitiesDir = join17(process.cwd(), entitiesLocalDir);
9086
+ const filePath = join17(entitiesDir, `${name}Entity.ts`);
8886
9087
  await Bun.write(filePath, content);
8887
9088
  const testContent = entity_test_default.replace(/{{NAME}}/g, name);
8888
- const testsLocalDir = join14(base, "tests", "entities");
8889
- const testsDir = join14(process.cwd(), testsLocalDir);
8890
- const testFilePath = join14(testsDir, `${name}Entity.spec.ts`);
9089
+ const testsLocalDir = join17(base, "tests", "entities");
9090
+ const testsDir = join17(process.cwd(), testsLocalDir);
9091
+ const testFilePath = join17(testsDir, `${name}Entity.spec.ts`);
8891
9092
  await Bun.write(testFilePath, testContent);
8892
9093
  const modulePascalName = module ? toPascalCase8(module) : toPascalCase8(basename3(process.cwd()));
8893
- const modulePath = join14(process.cwd(), base, "src", `${modulePascalName}Module.ts`);
9094
+ const modulePath = join17(process.cwd(), base, "src", `${modulePascalName}Module.ts`);
8894
9095
  if (await Bun.file(modulePath).exists()) {
8895
9096
  await this.addToModule(modulePath, name);
8896
9097
  }
8897
- const logger = new TerminalLogger13;
8898
- logger.success(`${join14(entitiesLocalDir, name)}Entity.ts created successfully`, undefined, {
9098
+ const logger = new TerminalLogger16;
9099
+ logger.success(`${join17(entitiesLocalDir, name)}Entity.ts created successfully`, undefined, {
8899
9100
  showTimestamp: false,
8900
9101
  showArrow: false,
8901
9102
  useSymbol: true
8902
9103
  });
8903
- logger.success(`${join14(testsLocalDir, name)}Entity.spec.ts created successfully`, undefined, {
9104
+ logger.success(`${join17(testsLocalDir, name)}Entity.spec.ts created successfully`, undefined, {
8904
9105
  showTimestamp: false,
8905
9106
  showArrow: false,
8906
9107
  useSymbol: true
@@ -8908,12 +9109,12 @@ class MakeEntityCommand {
8908
9109
  }
8909
9110
  }
8910
9111
  MakeEntityCommand = __legacyDecorateClassTS([
8911
- decorator13.command()
9112
+ decorator17.command()
8912
9113
  ], MakeEntityCommand);
8913
9114
  // src/commands/MakeLoggerCommand.ts
8914
- import { join as join15 } from "path";
8915
- import { decorator as decorator14 } from "@ooneex/command";
8916
- import { TerminalLogger as TerminalLogger14 } from "@ooneex/logger";
9115
+ import { join as join18 } from "path";
9116
+ import { decorator as decorator18 } from "@ooneex/command";
9117
+ import { TerminalLogger as TerminalLogger17 } from "@ooneex/logger";
8917
9118
  import { toPascalCase as toPascalCase9 } from "@ooneex/utils";
8918
9119
 
8919
9120
  // src/templates/logger.test.txt
@@ -9018,28 +9219,28 @@ class MakeLoggerCommand {
9018
9219
  if (module) {
9019
9220
  await ensureModule(module);
9020
9221
  }
9021
- const base = module ? join15("modules", module) : ".";
9022
- const loggerLocalDir = join15(base, "src", "loggers");
9023
- const loggerDir = join15(process.cwd(), loggerLocalDir);
9024
- const filePath = join15(loggerDir, `${name}Logger.ts`);
9222
+ const base = module ? join18("modules", module) : ".";
9223
+ const loggerLocalDir = join18(base, "src", "loggers");
9224
+ const loggerDir = join18(process.cwd(), loggerLocalDir);
9225
+ const filePath = join18(loggerDir, `${name}Logger.ts`);
9025
9226
  await Bun.write(filePath, content);
9026
9227
  const testContent = logger_test_default.replace(/{{NAME}}/g, name);
9027
- const testsLocalDir = join15(base, "tests", "loggers");
9028
- const testsDir = join15(process.cwd(), testsLocalDir);
9029
- const testFilePath = join15(testsDir, `${name}Logger.spec.ts`);
9228
+ const testsLocalDir = join18(base, "tests", "loggers");
9229
+ const testsDir = join18(process.cwd(), testsLocalDir);
9230
+ const testFilePath = join18(testsDir, `${name}Logger.spec.ts`);
9030
9231
  await Bun.write(testFilePath, testContent);
9031
- const logger = new TerminalLogger14;
9032
- logger.success(`${join15(loggerLocalDir, name)}Logger.ts created successfully`, undefined, {
9232
+ const logger = new TerminalLogger17;
9233
+ logger.success(`${join18(loggerLocalDir, name)}Logger.ts created successfully`, undefined, {
9033
9234
  showTimestamp: false,
9034
9235
  showArrow: false,
9035
9236
  useSymbol: true
9036
9237
  });
9037
- logger.success(`${join15(testsLocalDir, name)}Logger.spec.ts created successfully`, undefined, {
9238
+ logger.success(`${join18(testsLocalDir, name)}Logger.spec.ts created successfully`, undefined, {
9038
9239
  showTimestamp: false,
9039
9240
  showArrow: false,
9040
9241
  useSymbol: true
9041
9242
  });
9042
- const packageJsonPath = join15(process.cwd(), "package.json");
9243
+ const packageJsonPath = join18(process.cwd(), "package.json");
9043
9244
  const packageJson = await Bun.file(packageJsonPath).json();
9044
9245
  const deps = packageJson.dependencies ?? {};
9045
9246
  const devDeps = packageJson.devDependencies ?? {};
@@ -9054,12 +9255,12 @@ class MakeLoggerCommand {
9054
9255
  }
9055
9256
  }
9056
9257
  MakeLoggerCommand = __legacyDecorateClassTS([
9057
- decorator14.command()
9258
+ decorator18.command()
9058
9259
  ], MakeLoggerCommand);
9059
9260
  // src/commands/MakeMailerCommand.ts
9060
- import { join as join16 } from "path";
9061
- import { decorator as decorator15 } from "@ooneex/command";
9062
- import { TerminalLogger as TerminalLogger15 } from "@ooneex/logger";
9261
+ import { join as join19 } from "path";
9262
+ import { decorator as decorator19 } from "@ooneex/command";
9263
+ import { TerminalLogger as TerminalLogger18 } from "@ooneex/logger";
9063
9264
  import { toPascalCase as toPascalCase10 } from "@ooneex/utils";
9064
9265
 
9065
9266
  // src/templates/mailer/mailer.test.txt
@@ -9157,43 +9358,43 @@ class MakeMailerCommand {
9157
9358
  if (module) {
9158
9359
  await ensureModule(module);
9159
9360
  }
9160
- const base = module ? join16("modules", module) : ".";
9161
- const mailerLocalDir = join16(base, "src", "mailers");
9162
- const mailerDir = join16(process.cwd(), mailerLocalDir);
9163
- const mailerFilePath = join16(mailerDir, `${name}Mailer.ts`);
9164
- const templateFilePath = join16(mailerDir, `${name}MailerTemplate.tsx`);
9361
+ const base = module ? join19("modules", module) : ".";
9362
+ const mailerLocalDir = join19(base, "src", "mailers");
9363
+ const mailerDir = join19(process.cwd(), mailerLocalDir);
9364
+ const mailerFilePath = join19(mailerDir, `${name}Mailer.ts`);
9365
+ const templateFilePath = join19(mailerDir, `${name}MailerTemplate.tsx`);
9165
9366
  await Bun.write(mailerFilePath, mailerContent);
9166
9367
  await Bun.write(templateFilePath, templateContent);
9167
9368
  const mailerTestContent = mailer_test_default.replace(/{{NAME}}/g, name);
9168
9369
  const templateTestContent = mailer_template_test_default.replace(/{{NAME}}/g, name);
9169
- const testsLocalDir = join16(base, "tests", "mailers");
9170
- const testsDir = join16(process.cwd(), testsLocalDir);
9171
- const mailerTestFilePath = join16(testsDir, `${name}Mailer.spec.ts`);
9172
- const templateTestFilePath = join16(testsDir, `${name}MailerTemplate.spec.ts`);
9370
+ const testsLocalDir = join19(base, "tests", "mailers");
9371
+ const testsDir = join19(process.cwd(), testsLocalDir);
9372
+ const mailerTestFilePath = join19(testsDir, `${name}Mailer.spec.ts`);
9373
+ const templateTestFilePath = join19(testsDir, `${name}MailerTemplate.spec.ts`);
9173
9374
  await Bun.write(mailerTestFilePath, mailerTestContent);
9174
9375
  await Bun.write(templateTestFilePath, templateTestContent);
9175
- const logger = new TerminalLogger15;
9176
- logger.success(`${join16(mailerLocalDir, name)}Mailer.ts created successfully`, undefined, {
9376
+ const logger = new TerminalLogger18;
9377
+ logger.success(`${join19(mailerLocalDir, name)}Mailer.ts created successfully`, undefined, {
9177
9378
  showTimestamp: false,
9178
9379
  showArrow: false,
9179
9380
  useSymbol: true
9180
9381
  });
9181
- logger.success(`${join16(mailerLocalDir, name)}MailerTemplate.tsx created successfully`, undefined, {
9382
+ logger.success(`${join19(mailerLocalDir, name)}MailerTemplate.tsx created successfully`, undefined, {
9182
9383
  showTimestamp: false,
9183
9384
  showArrow: false,
9184
9385
  useSymbol: true
9185
9386
  });
9186
- logger.success(`${join16(testsLocalDir, name)}Mailer.spec.ts created successfully`, undefined, {
9387
+ logger.success(`${join19(testsLocalDir, name)}Mailer.spec.ts created successfully`, undefined, {
9187
9388
  showTimestamp: false,
9188
9389
  showArrow: false,
9189
9390
  useSymbol: true
9190
9391
  });
9191
- logger.success(`${join16(testsLocalDir, name)}MailerTemplate.spec.ts created successfully`, undefined, {
9392
+ logger.success(`${join19(testsLocalDir, name)}MailerTemplate.spec.ts created successfully`, undefined, {
9192
9393
  showTimestamp: false,
9193
9394
  showArrow: false,
9194
9395
  useSymbol: true
9195
9396
  });
9196
- const packageJsonPath = join16(process.cwd(), "package.json");
9397
+ const packageJsonPath = join19(process.cwd(), "package.json");
9197
9398
  const packageJson = await Bun.file(packageJsonPath).json();
9198
9399
  const deps = packageJson.dependencies ?? {};
9199
9400
  const devDeps = packageJson.devDependencies ?? {};
@@ -9208,12 +9409,12 @@ class MakeMailerCommand {
9208
9409
  }
9209
9410
  }
9210
9411
  MakeMailerCommand = __legacyDecorateClassTS([
9211
- decorator15.command()
9412
+ decorator19.command()
9212
9413
  ], MakeMailerCommand);
9213
9414
  // src/commands/MakeMiddlewareCommand.ts
9214
- import { basename as basename4, join as join17 } from "path";
9215
- import { decorator as decorator16 } from "@ooneex/command";
9216
- import { TerminalLogger as TerminalLogger16 } from "@ooneex/logger";
9415
+ import { basename as basename4, join as join20 } from "path";
9416
+ import { decorator as decorator20 } from "@ooneex/command";
9417
+ import { TerminalLogger as TerminalLogger19 } from "@ooneex/logger";
9217
9418
  import { toPascalCase as toPascalCase11 } from "@ooneex/utils";
9218
9419
 
9219
9420
  // src/templates/middleware.socket.txt
@@ -9302,33 +9503,33 @@ class MakeMiddlewareCommand {
9302
9503
  if (module) {
9303
9504
  await ensureModule(module);
9304
9505
  }
9305
- const base = module ? join17("modules", module) : ".";
9306
- const middlewareLocalDir = join17(base, "src", "middlewares");
9307
- const middlewareDir = join17(process.cwd(), middlewareLocalDir);
9308
- const filePath = join17(middlewareDir, `${name}Middleware.ts`);
9506
+ const base = module ? join20("modules", module) : ".";
9507
+ const middlewareLocalDir = join20(base, "src", "middlewares");
9508
+ const middlewareDir = join20(process.cwd(), middlewareLocalDir);
9509
+ const filePath = join20(middlewareDir, `${name}Middleware.ts`);
9309
9510
  await Bun.write(filePath, content);
9310
9511
  const testContent = middleware_test_default.replace(/{{NAME}}/g, name);
9311
- const testsLocalDir = join17(base, "tests", "middlewares");
9312
- const testsDir = join17(process.cwd(), testsLocalDir);
9313
- const testFilePath = join17(testsDir, `${name}Middleware.spec.ts`);
9512
+ const testsLocalDir = join20(base, "tests", "middlewares");
9513
+ const testsDir = join20(process.cwd(), testsLocalDir);
9514
+ const testFilePath = join20(testsDir, `${name}Middleware.spec.ts`);
9314
9515
  await Bun.write(testFilePath, testContent);
9315
9516
  const modulePascalName = module ? toPascalCase11(module) : toPascalCase11(basename4(process.cwd()));
9316
- const modulePath = join17(process.cwd(), base, "src", `${modulePascalName}Module.ts`);
9517
+ const modulePath = join20(process.cwd(), base, "src", `${modulePascalName}Module.ts`);
9317
9518
  if (await Bun.file(modulePath).exists()) {
9318
9519
  await this.addToModule(modulePath, name);
9319
9520
  }
9320
- const logger = new TerminalLogger16;
9321
- logger.success(`${join17(middlewareLocalDir, name)}Middleware.ts created successfully`, undefined, {
9521
+ const logger = new TerminalLogger19;
9522
+ logger.success(`${join20(middlewareLocalDir, name)}Middleware.ts created successfully`, undefined, {
9322
9523
  showTimestamp: false,
9323
9524
  showArrow: false,
9324
9525
  useSymbol: true
9325
9526
  });
9326
- logger.success(`${join17(testsLocalDir, name)}Middleware.spec.ts created successfully`, undefined, {
9527
+ logger.success(`${join20(testsLocalDir, name)}Middleware.spec.ts created successfully`, undefined, {
9327
9528
  showTimestamp: false,
9328
9529
  showArrow: false,
9329
9530
  useSymbol: true
9330
9531
  });
9331
- const packageJsonPath = join17(process.cwd(), "package.json");
9532
+ const packageJsonPath = join20(process.cwd(), "package.json");
9332
9533
  const packageJson = await Bun.file(packageJsonPath).json();
9333
9534
  const deps = packageJson.dependencies ?? {};
9334
9535
  const devDeps = packageJson.devDependencies ?? {};
@@ -9343,21 +9544,21 @@ class MakeMiddlewareCommand {
9343
9544
  }
9344
9545
  }
9345
9546
  MakeMiddlewareCommand = __legacyDecorateClassTS([
9346
- decorator16.command()
9547
+ decorator20.command()
9347
9548
  ], MakeMiddlewareCommand);
9348
9549
  // src/commands/MakeMigrationCommand.ts
9349
- import { join as join18 } from "path";
9350
- import { decorator as decorator17 } from "@ooneex/command";
9351
- import { TerminalLogger as TerminalLogger17 } from "@ooneex/logger";
9550
+ import { join as join21 } from "path";
9551
+ import { decorator as decorator21 } from "@ooneex/command";
9552
+ import { TerminalLogger as TerminalLogger20 } from "@ooneex/logger";
9352
9553
  import { migrationCreate } from "@ooneex/migrations";
9353
9554
 
9354
9555
  // src/templates/module/migration.up.txt
9355
9556
  var migration_up_default = `#!/usr/bin/env bun
9356
9557
 
9357
- import { migrationUp } from "@ooneex/migrations";
9558
+ import { up } from "@ooneex/migrations";
9358
9559
  import "@/migrations/migrations";
9359
9560
 
9360
- await migrationUp({
9561
+ await up({
9361
9562
  databaseUrl: Bun.env.DATABASE_URL,
9362
9563
  tableName: "migrations",
9363
9564
  });
@@ -9376,41 +9577,29 @@ class MakeMigrationCommand {
9376
9577
  if (module) {
9377
9578
  await ensureModule(module);
9378
9579
  }
9379
- const base = module ? join18("modules", module) : ".";
9580
+ const base = module ? join21("modules", module) : ".";
9380
9581
  const { migrationPath: filePath } = await migrationCreate({
9381
- migrationsDir: join18(base, "src", "migrations"),
9382
- testsDir: join18(base, "tests", "migrations")
9582
+ migrationsDir: join21(base, "src", "migrations"),
9583
+ testsDir: join21(base, "tests", "migrations")
9383
9584
  });
9384
- const binMigrationUpPath = join18(process.cwd(), base, "bin", "migration", "up.ts");
9585
+ const binMigrationUpPath = join21(process.cwd(), base, "bin", "migration", "up.ts");
9385
9586
  const binMigrationUpFile = Bun.file(binMigrationUpPath);
9386
9587
  if (!await binMigrationUpFile.exists()) {
9387
9588
  await Bun.write(binMigrationUpPath, migration_up_default);
9388
9589
  }
9389
- const packageJsonPath = join18(process.cwd(), base, "package.json");
9390
- const packageJsonFile = Bun.file(packageJsonPath);
9391
- if (await packageJsonFile.exists()) {
9392
- const packageJson = await packageJsonFile.json();
9393
- packageJson.scripts = packageJson.scripts || {};
9394
- packageJson.scripts["migration:up"] = "bun ./bin/migration/up.ts";
9395
- await Bun.write(packageJsonPath, JSON.stringify(packageJson, null, 2));
9396
- }
9397
- const logger = new TerminalLogger17;
9590
+ const packageJsonPath = join21(process.cwd(), base, "package.json");
9591
+ const logger = new TerminalLogger20;
9398
9592
  logger.success(`${filePath} created successfully`, undefined, {
9399
9593
  showTimestamp: false,
9400
9594
  showArrow: false,
9401
9595
  useSymbol: true
9402
9596
  });
9403
- logger.info("Run 'bun run migration:up' to execute migrations", undefined, {
9404
- showTimestamp: false,
9405
- showArrow: true,
9406
- showLevel: false
9407
- });
9408
9597
  const pkgJson = await Bun.file(packageJsonPath).json();
9409
9598
  const deps = pkgJson.dependencies ?? {};
9410
9599
  const devDeps = pkgJson.devDependencies ?? {};
9411
9600
  if (!deps["@ooneex/migrations"] && !devDeps["@ooneex/migrations"]) {
9412
9601
  const install = Bun.spawn(["bun", "add", "--dev", "@ooneex/migrations"], {
9413
- cwd: join18(process.cwd(), base),
9602
+ cwd: join21(process.cwd(), base),
9414
9603
  stdout: "ignore",
9415
9604
  stderr: "inherit"
9416
9605
  });
@@ -9419,12 +9608,12 @@ class MakeMigrationCommand {
9419
9608
  }
9420
9609
  }
9421
9610
  MakeMigrationCommand = __legacyDecorateClassTS([
9422
- decorator17.command()
9611
+ decorator21.command()
9423
9612
  ], MakeMigrationCommand);
9424
9613
  // src/commands/MakePermissionCommand.ts
9425
- import { join as join19 } from "path";
9426
- import { decorator as decorator18 } from "@ooneex/command";
9427
- import { TerminalLogger as TerminalLogger18 } from "@ooneex/logger";
9614
+ import { join as join22 } from "path";
9615
+ import { decorator as decorator22 } from "@ooneex/command";
9616
+ import { TerminalLogger as TerminalLogger21 } from "@ooneex/logger";
9428
9617
  import { toPascalCase as toPascalCase12 } from "@ooneex/utils";
9429
9618
 
9430
9619
  // src/templates/permission.test.txt
@@ -9515,28 +9704,28 @@ class MakePermissionCommand {
9515
9704
  if (module) {
9516
9705
  await ensureModule(module);
9517
9706
  }
9518
- const base = module ? join19("modules", module) : ".";
9519
- const permissionLocalDir = join19(base, "src", "permissions");
9520
- const permissionDir = join19(process.cwd(), permissionLocalDir);
9521
- const filePath = join19(permissionDir, `${name}Permission.ts`);
9707
+ const base = module ? join22("modules", module) : ".";
9708
+ const permissionLocalDir = join22(base, "src", "permissions");
9709
+ const permissionDir = join22(process.cwd(), permissionLocalDir);
9710
+ const filePath = join22(permissionDir, `${name}Permission.ts`);
9522
9711
  await Bun.write(filePath, content);
9523
9712
  const testContent = permission_test_default.replace(/{{NAME}}/g, name);
9524
- const testsLocalDir = join19(base, "tests", "permissions");
9525
- const testsDir = join19(process.cwd(), testsLocalDir);
9526
- const testFilePath = join19(testsDir, `${name}Permission.spec.ts`);
9713
+ const testsLocalDir = join22(base, "tests", "permissions");
9714
+ const testsDir = join22(process.cwd(), testsLocalDir);
9715
+ const testFilePath = join22(testsDir, `${name}Permission.spec.ts`);
9527
9716
  await Bun.write(testFilePath, testContent);
9528
- const logger = new TerminalLogger18;
9529
- logger.success(`${join19(permissionLocalDir, name)}Permission.ts created successfully`, undefined, {
9717
+ const logger = new TerminalLogger21;
9718
+ logger.success(`${join22(permissionLocalDir, name)}Permission.ts created successfully`, undefined, {
9530
9719
  showTimestamp: false,
9531
9720
  showArrow: false,
9532
9721
  useSymbol: true
9533
9722
  });
9534
- logger.success(`${join19(testsLocalDir, name)}Permission.spec.ts created successfully`, undefined, {
9723
+ logger.success(`${join22(testsLocalDir, name)}Permission.spec.ts created successfully`, undefined, {
9535
9724
  showTimestamp: false,
9536
9725
  showArrow: false,
9537
9726
  useSymbol: true
9538
9727
  });
9539
- const packageJsonPath = join19(process.cwd(), "package.json");
9728
+ const packageJsonPath = join22(process.cwd(), "package.json");
9540
9729
  const packageJson = await Bun.file(packageJsonPath).json();
9541
9730
  const deps = packageJson.dependencies ?? {};
9542
9731
  const devDeps = packageJson.devDependencies ?? {};
@@ -9551,12 +9740,12 @@ class MakePermissionCommand {
9551
9740
  }
9552
9741
  }
9553
9742
  MakePermissionCommand = __legacyDecorateClassTS([
9554
- decorator18.command()
9743
+ decorator22.command()
9555
9744
  ], MakePermissionCommand);
9556
9745
  // src/commands/MakePubSubCommand.ts
9557
- import { basename as basename5, join as join20 } from "path";
9558
- import { decorator as decorator19 } from "@ooneex/command";
9559
- import { TerminalLogger as TerminalLogger19 } from "@ooneex/logger";
9746
+ import { basename as basename5, join as join23 } from "path";
9747
+ import { decorator as decorator23 } from "@ooneex/command";
9748
+ import { TerminalLogger as TerminalLogger22 } from "@ooneex/logger";
9560
9749
  import { toKebabCase as toKebabCase4, toPascalCase as toPascalCase13 } from "@ooneex/utils";
9561
9750
 
9562
9751
  // src/templates/pubsub.test.txt
@@ -9664,33 +9853,33 @@ class MakePubSubCommand {
9664
9853
  if (module) {
9665
9854
  await ensureModule(module);
9666
9855
  }
9667
- const base = module ? join20("modules", module) : ".";
9668
- const pubSubLocalDir = join20(base, "src", "events");
9669
- const pubSubDir = join20(process.cwd(), pubSubLocalDir);
9670
- const filePath = join20(pubSubDir, `${name}Event.ts`);
9856
+ const base = module ? join23("modules", module) : ".";
9857
+ const pubSubLocalDir = join23(base, "src", "events");
9858
+ const pubSubDir = join23(process.cwd(), pubSubLocalDir);
9859
+ const filePath = join23(pubSubDir, `${name}Event.ts`);
9671
9860
  await Bun.write(filePath, content);
9672
9861
  const testContent = pubsub_test_default.replace(/{{NAME}}/g, name);
9673
- const testsLocalDir = join20(base, "tests", "events");
9674
- const testsDir = join20(process.cwd(), testsLocalDir);
9675
- const testFilePath = join20(testsDir, `${name}Event.spec.ts`);
9862
+ const testsLocalDir = join23(base, "tests", "events");
9863
+ const testsDir = join23(process.cwd(), testsLocalDir);
9864
+ const testFilePath = join23(testsDir, `${name}Event.spec.ts`);
9676
9865
  await Bun.write(testFilePath, testContent);
9677
9866
  const modulePascalName = module ? toPascalCase13(module) : toPascalCase13(basename5(process.cwd()));
9678
- const modulePath = join20(process.cwd(), base, "src", `${modulePascalName}Module.ts`);
9867
+ const modulePath = join23(process.cwd(), base, "src", `${modulePascalName}Module.ts`);
9679
9868
  if (await Bun.file(modulePath).exists()) {
9680
9869
  await this.addToModule(modulePath, name);
9681
9870
  }
9682
- const logger = new TerminalLogger19;
9683
- logger.success(`${join20(pubSubLocalDir, name)}Event.ts created successfully`, undefined, {
9871
+ const logger = new TerminalLogger22;
9872
+ logger.success(`${join23(pubSubLocalDir, name)}Event.ts created successfully`, undefined, {
9684
9873
  showTimestamp: false,
9685
9874
  showArrow: false,
9686
9875
  useSymbol: true
9687
9876
  });
9688
- logger.success(`${join20(testsLocalDir, name)}Event.spec.ts created successfully`, undefined, {
9877
+ logger.success(`${join23(testsLocalDir, name)}Event.spec.ts created successfully`, undefined, {
9689
9878
  showTimestamp: false,
9690
9879
  showArrow: false,
9691
9880
  useSymbol: true
9692
9881
  });
9693
- const packageJsonPath = join20(process.cwd(), "package.json");
9882
+ const packageJsonPath = join23(process.cwd(), "package.json");
9694
9883
  const packageJson = await Bun.file(packageJsonPath).json();
9695
9884
  const deps = packageJson.dependencies ?? {};
9696
9885
  const devDeps = packageJson.devDependencies ?? {};
@@ -9705,13 +9894,13 @@ class MakePubSubCommand {
9705
9894
  }
9706
9895
  }
9707
9896
  MakePubSubCommand = __legacyDecorateClassTS([
9708
- decorator19.command()
9897
+ decorator23.command()
9709
9898
  ], MakePubSubCommand);
9710
9899
  // src/commands/MakeReleaseCommand.ts
9711
9900
  import { readdir } from "fs/promises";
9712
- import { join as join21 } from "path";
9713
- import { decorator as decorator20 } from "@ooneex/command";
9714
- import { TerminalLogger as TerminalLogger20 } from "@ooneex/logger";
9901
+ import { join as join24 } from "path";
9902
+ import { decorator as decorator24 } from "@ooneex/command";
9903
+ import { TerminalLogger as TerminalLogger23 } from "@ooneex/logger";
9715
9904
  var {$ } = globalThis.Bun;
9716
9905
  var COMMIT_TYPE_TO_CATEGORY = {
9717
9906
  feat: "Added",
@@ -9734,7 +9923,7 @@ class MakeReleaseCommand {
9734
9923
  return "Release packages with version bump, changelog, and git tag";
9735
9924
  }
9736
9925
  async run() {
9737
- const logger = new TerminalLogger20;
9926
+ const logger = new TerminalLogger23;
9738
9927
  const cwd = process.cwd();
9739
9928
  const dirs = [];
9740
9929
  for (const { name, type } of [
@@ -9742,8 +9931,8 @@ class MakeReleaseCommand {
9742
9931
  { name: "modules", type: "module" }
9743
9932
  ]) {
9744
9933
  try {
9745
- const entries = await readdir(join21(cwd, name), { withFileTypes: true });
9746
- dirs.push(...entries.filter((d) => d.isDirectory()).map((d) => ({ base: join21(name, d.name), type })));
9934
+ const entries = await readdir(join24(cwd, name), { withFileTypes: true });
9935
+ dirs.push(...entries.filter((d) => d.isDirectory()).map((d) => ({ base: join24(name, d.name), type })));
9747
9936
  } catch {}
9748
9937
  }
9749
9938
  const logOptions = { showTimestamp: false, showArrow: false, useSymbol: true };
@@ -9753,8 +9942,8 @@ class MakeReleaseCommand {
9753
9942
  }
9754
9943
  let releasedCount = 0;
9755
9944
  for (const dir of dirs) {
9756
- const fullDir = join21(cwd, dir.base);
9757
- const pkgJsonPath = join21(fullDir, "package.json");
9945
+ const fullDir = join24(cwd, dir.base);
9946
+ const pkgJsonPath = join24(fullDir, "package.json");
9758
9947
  const pkgJsonFile = Bun.file(pkgJsonPath);
9759
9948
  if (!await pkgJsonFile.exists()) {
9760
9949
  continue;
@@ -9772,7 +9961,7 @@ class MakeReleaseCommand {
9772
9961
  await Bun.write(pkgJsonPath, `${JSON.stringify(pkgJson, null, 2)}
9773
9962
  `);
9774
9963
  await this.updateChangelog(fullDir, newVersion, tag, commits);
9775
- await this.gitAdd(join21(dir.base, "package.json"), join21(dir.base, "CHANGELOG.md"));
9964
+ await this.gitAdd(join24(dir.base, "package.json"), join24(dir.base, "CHANGELOG.md"));
9776
9965
  await this.gitCommit(`chore(release): ${pkgJson.name}@${newVersion}`);
9777
9966
  await this.gitTag(tag, `chore(release): ${pkgJson.name}@${newVersion}`);
9778
9967
  logger.success(`${pkgJson.name}@${newVersion} released (${bumpType} bump, ${commits.length} commit(s))`, undefined, logOptions);
@@ -9869,7 +10058,7 @@ class MakeReleaseCommand {
9869
10058
  }
9870
10059
  }
9871
10060
  async updateChangelog(dir, version, tag, commits) {
9872
- const changelogPath = join21(dir, "CHANGELOG.md");
10061
+ const changelogPath = join24(dir, "CHANGELOG.md");
9873
10062
  const today = new Date().toISOString().split("T")[0];
9874
10063
  const repoUrl = await this.getRepoUrl();
9875
10064
  const grouped = new Map;
@@ -9940,12 +10129,12 @@ ${section}
9940
10129
  }
9941
10130
  }
9942
10131
  MakeReleaseCommand = __legacyDecorateClassTS([
9943
- decorator20.command()
10132
+ decorator24.command()
9944
10133
  ], MakeReleaseCommand);
9945
10134
  // src/commands/MakeRepositoryCommand.ts
9946
- import { join as join22 } from "path";
9947
- import { decorator as decorator21 } from "@ooneex/command";
9948
- import { TerminalLogger as TerminalLogger21 } from "@ooneex/logger";
10135
+ import { join as join25 } from "path";
10136
+ import { decorator as decorator25 } from "@ooneex/command";
10137
+ import { TerminalLogger as TerminalLogger24 } from "@ooneex/logger";
9949
10138
  import { toPascalCase as toPascalCase14 } from "@ooneex/utils";
9950
10139
 
9951
10140
  // src/templates/repository.test.txt
@@ -10159,28 +10348,28 @@ class MakeRepositoryCommand {
10159
10348
  if (module) {
10160
10349
  await ensureModule(module);
10161
10350
  }
10162
- const base = module ? join22("modules", module) : ".";
10163
- const repositoriesLocalDir = join22(base, "src", "repositories");
10164
- const repositoriesDir = join22(process.cwd(), repositoriesLocalDir);
10165
- const filePath = join22(repositoriesDir, `${name}Repository.ts`);
10351
+ const base = module ? join25("modules", module) : ".";
10352
+ const repositoriesLocalDir = join25(base, "src", "repositories");
10353
+ const repositoriesDir = join25(process.cwd(), repositoriesLocalDir);
10354
+ const filePath = join25(repositoriesDir, `${name}Repository.ts`);
10166
10355
  await Bun.write(filePath, content);
10167
10356
  const testContent = repository_test_default.replace(/{{NAME}}/g, name);
10168
- const testsLocalDir = join22(base, "tests", "repositories");
10169
- const testsDir = join22(process.cwd(), testsLocalDir);
10170
- const testFilePath = join22(testsDir, `${name}Repository.spec.ts`);
10357
+ const testsLocalDir = join25(base, "tests", "repositories");
10358
+ const testsDir = join25(process.cwd(), testsLocalDir);
10359
+ const testFilePath = join25(testsDir, `${name}Repository.spec.ts`);
10171
10360
  await Bun.write(testFilePath, testContent);
10172
- const logger = new TerminalLogger21;
10173
- logger.success(`${join22(repositoriesLocalDir, name)}Repository.ts created successfully`, undefined, {
10361
+ const logger = new TerminalLogger24;
10362
+ logger.success(`${join25(repositoriesLocalDir, name)}Repository.ts created successfully`, undefined, {
10174
10363
  showTimestamp: false,
10175
10364
  showArrow: false,
10176
10365
  useSymbol: true
10177
10366
  });
10178
- logger.success(`${join22(testsLocalDir, name)}Repository.spec.ts created successfully`, undefined, {
10367
+ logger.success(`${join25(testsLocalDir, name)}Repository.spec.ts created successfully`, undefined, {
10179
10368
  showTimestamp: false,
10180
10369
  showArrow: false,
10181
10370
  useSymbol: true
10182
10371
  });
10183
- const packageJsonPath = join22(process.cwd(), "package.json");
10372
+ const packageJsonPath = join25(process.cwd(), "package.json");
10184
10373
  const packageJson = await Bun.file(packageJsonPath).json();
10185
10374
  const deps = packageJson.dependencies ?? {};
10186
10375
  const devDeps = packageJson.devDependencies ?? {};
@@ -10195,11 +10384,11 @@ class MakeRepositoryCommand {
10195
10384
  }
10196
10385
  }
10197
10386
  MakeRepositoryCommand = __legacyDecorateClassTS([
10198
- decorator21.command()
10387
+ decorator25.command()
10199
10388
  ], MakeRepositoryCommand);
10200
10389
  // src/commands/MakeResourceBookCommand.ts
10201
- import { join as join24 } from "path";
10202
- import { decorator as decorator23 } from "@ooneex/command";
10390
+ import { join as join27 } from "path";
10391
+ import { decorator as decorator27 } from "@ooneex/command";
10203
10392
  var {Glob } = globalThis.Bun;
10204
10393
 
10205
10394
  // src/templates/resources/book/BookEntity.txt
@@ -10895,9 +11084,9 @@ export class UpdateBookService implements IService {
10895
11084
  `;
10896
11085
 
10897
11086
  // src/commands/MakeServiceCommand.ts
10898
- import { join as join23 } from "path";
10899
- import { decorator as decorator22 } from "@ooneex/command";
10900
- import { TerminalLogger as TerminalLogger22 } from "@ooneex/logger";
11087
+ import { join as join26 } from "path";
11088
+ import { decorator as decorator26 } from "@ooneex/command";
11089
+ import { TerminalLogger as TerminalLogger25 } from "@ooneex/logger";
10901
11090
  import { toPascalCase as toPascalCase15 } from "@ooneex/utils";
10902
11091
 
10903
11092
  // src/templates/service.test.txt
@@ -10949,28 +11138,28 @@ class MakeServiceCommand {
10949
11138
  if (module) {
10950
11139
  await ensureModule(module);
10951
11140
  }
10952
- const base = module ? join23("modules", module) : ".";
10953
- const serviceLocalDir = join23(base, "src", "services");
10954
- const serviceDir = join23(process.cwd(), serviceLocalDir);
10955
- const filePath = join23(serviceDir, `${name}Service.ts`);
11141
+ const base = module ? join26("modules", module) : ".";
11142
+ const serviceLocalDir = join26(base, "src", "services");
11143
+ const serviceDir = join26(process.cwd(), serviceLocalDir);
11144
+ const filePath = join26(serviceDir, `${name}Service.ts`);
10956
11145
  await Bun.write(filePath, content);
10957
11146
  const testContent = service_test_default.replace(/{{NAME}}/g, name);
10958
- const testsLocalDir = join23(base, "tests", "services");
10959
- const testsDir = join23(process.cwd(), testsLocalDir);
10960
- const testFilePath = join23(testsDir, `${name}Service.spec.ts`);
11147
+ const testsLocalDir = join26(base, "tests", "services");
11148
+ const testsDir = join26(process.cwd(), testsLocalDir);
11149
+ const testFilePath = join26(testsDir, `${name}Service.spec.ts`);
10961
11150
  await Bun.write(testFilePath, testContent);
10962
- const logger = new TerminalLogger22;
10963
- logger.success(`${join23(serviceLocalDir, name)}Service.ts created successfully`, undefined, {
11151
+ const logger = new TerminalLogger25;
11152
+ logger.success(`${join26(serviceLocalDir, name)}Service.ts created successfully`, undefined, {
10964
11153
  showTimestamp: false,
10965
11154
  showArrow: false,
10966
11155
  useSymbol: true
10967
11156
  });
10968
- logger.success(`${join23(testsLocalDir, name)}Service.spec.ts created successfully`, undefined, {
11157
+ logger.success(`${join26(testsLocalDir, name)}Service.spec.ts created successfully`, undefined, {
10969
11158
  showTimestamp: false,
10970
11159
  showArrow: false,
10971
11160
  useSymbol: true
10972
11161
  });
10973
- const packageJsonPath = join23(process.cwd(), "package.json");
11162
+ const packageJsonPath = join26(process.cwd(), "package.json");
10974
11163
  const packageJson = await Bun.file(packageJsonPath).json();
10975
11164
  const deps = packageJson.dependencies ?? {};
10976
11165
  const devDeps = packageJson.devDependencies ?? {};
@@ -10985,7 +11174,7 @@ class MakeServiceCommand {
10985
11174
  }
10986
11175
  }
10987
11176
  MakeServiceCommand = __legacyDecorateClassTS([
10988
- decorator22.command()
11177
+ decorator26.command()
10989
11178
  ], MakeServiceCommand);
10990
11179
 
10991
11180
  // src/commands/MakeResourceBookCommand.ts
@@ -10998,7 +11187,7 @@ class MakeResourceBookCommand {
10998
11187
  }
10999
11188
  async run() {
11000
11189
  const module = "book";
11001
- const base = join24("modules", module);
11190
+ const base = join27("modules", module);
11002
11191
  const makeModuleCommand = new MakeModuleCommand;
11003
11192
  await makeModuleCommand.run({ name: module, silent: true, skipMigrations: false, skipSeeds: true });
11004
11193
  const makeEntityCommand = new MakeEntityCommand;
@@ -11018,26 +11207,26 @@ class MakeResourceBookCommand {
11018
11207
  for (const controller of controllers) {
11019
11208
  await makeControllerCommand.run({ ...controller, module, isSocket: false });
11020
11209
  }
11021
- const controllersDir = join24(process.cwd(), base, "src", "controllers");
11022
- await Bun.write(join24(controllersDir, "CreateBookController.ts"), CreateBookController_default);
11023
- await Bun.write(join24(controllersDir, "GetBookController.ts"), GetBookController_default);
11024
- await Bun.write(join24(controllersDir, "ListBooksController.ts"), ListBooksController_default);
11025
- await Bun.write(join24(controllersDir, "UpdateBookController.ts"), UpdateBookController_default);
11026
- await Bun.write(join24(controllersDir, "DeleteBookController.ts"), DeleteBookController_default);
11210
+ const controllersDir = join27(process.cwd(), base, "src", "controllers");
11211
+ await Bun.write(join27(controllersDir, "CreateBookController.ts"), CreateBookController_default);
11212
+ await Bun.write(join27(controllersDir, "GetBookController.ts"), GetBookController_default);
11213
+ await Bun.write(join27(controllersDir, "ListBooksController.ts"), ListBooksController_default);
11214
+ await Bun.write(join27(controllersDir, "UpdateBookController.ts"), UpdateBookController_default);
11215
+ await Bun.write(join27(controllersDir, "DeleteBookController.ts"), DeleteBookController_default);
11027
11216
  const makeServiceCommand = new MakeServiceCommand;
11028
11217
  const services = ["CreateBook", "GetBook", "ListBooks", "UpdateBook", "DeleteBook"];
11029
11218
  for (const name of services) {
11030
11219
  await makeServiceCommand.run({ name, module });
11031
11220
  }
11032
- const servicesDir = join24(process.cwd(), base, "src", "services");
11033
- await Bun.write(join24(servicesDir, "CreateBookService.ts"), CreateBookService_default);
11034
- await Bun.write(join24(servicesDir, "GetBookService.ts"), GetBookService_default);
11035
- await Bun.write(join24(servicesDir, "ListBooksService.ts"), ListBooksService_default);
11036
- await Bun.write(join24(servicesDir, "UpdateBookService.ts"), UpdateBookService_default);
11037
- await Bun.write(join24(servicesDir, "DeleteBookService.ts"), DeleteBookService_default);
11038
- const entityPath = join24(process.cwd(), base, "src", "entities", "BookEntity.ts");
11221
+ const servicesDir = join27(process.cwd(), base, "src", "services");
11222
+ await Bun.write(join27(servicesDir, "CreateBookService.ts"), CreateBookService_default);
11223
+ await Bun.write(join27(servicesDir, "GetBookService.ts"), GetBookService_default);
11224
+ await Bun.write(join27(servicesDir, "ListBooksService.ts"), ListBooksService_default);
11225
+ await Bun.write(join27(servicesDir, "UpdateBookService.ts"), UpdateBookService_default);
11226
+ await Bun.write(join27(servicesDir, "DeleteBookService.ts"), DeleteBookService_default);
11227
+ const entityPath = join27(process.cwd(), base, "src", "entities", "BookEntity.ts");
11039
11228
  await Bun.write(entityPath, BookEntity_default);
11040
- const migrationsDir = join24(process.cwd(), base, "src", "migrations");
11229
+ const migrationsDir = join27(process.cwd(), base, "src", "migrations");
11041
11230
  const glob = new Glob("Migration*.ts");
11042
11231
  for await (const file of glob.scan(migrationsDir)) {
11043
11232
  if (file === "migrations.ts")
@@ -11045,28 +11234,28 @@ class MakeResourceBookCommand {
11045
11234
  const name = file.replace(/\.ts$/, "");
11046
11235
  const version = name.replace("Migration", "");
11047
11236
  const content = BookMigration_default.replaceAll("{{ name }}", name).replaceAll("{{ version }}", version);
11048
- await Bun.write(join24(migrationsDir, file), content);
11237
+ await Bun.write(join27(migrationsDir, file), content);
11049
11238
  }
11050
- const repositoryPath = join24(process.cwd(), base, "src", "repositories", "BookRepository.ts");
11239
+ const repositoryPath = join27(process.cwd(), base, "src", "repositories", "BookRepository.ts");
11051
11240
  await Bun.write(repositoryPath, BookRepository_default);
11052
11241
  }
11053
11242
  }
11054
11243
  MakeResourceBookCommand = __legacyDecorateClassTS([
11055
- decorator23.command()
11244
+ decorator27.command()
11056
11245
  ], MakeResourceBookCommand);
11057
11246
  // src/commands/MakeSeedCommand.ts
11058
- import { join as join25 } from "path";
11059
- import { decorator as decorator24 } from "@ooneex/command";
11060
- import { TerminalLogger as TerminalLogger23 } from "@ooneex/logger";
11247
+ import { join as join28 } from "path";
11248
+ import { decorator as decorator28 } from "@ooneex/command";
11249
+ import { TerminalLogger as TerminalLogger26 } from "@ooneex/logger";
11061
11250
  import { seedCreate } from "@ooneex/seeds";
11062
11251
 
11063
11252
  // src/templates/module/seed.run.txt
11064
11253
  var seed_run_default = `#!/usr/bin/env bun
11065
11254
 
11066
- import { seedRun } from "@ooneex/seeds";
11255
+ import { run } from "@ooneex/seeds";
11067
11256
  import "@/seeds/seeds";
11068
11257
 
11069
- await seedRun();
11258
+ await run();
11070
11259
  `;
11071
11260
 
11072
11261
  // src/commands/MakeSeedCommand.ts
@@ -11085,26 +11274,19 @@ class MakeSeedCommand {
11085
11274
  if (module) {
11086
11275
  await ensureModule(module);
11087
11276
  }
11088
- const base = module ? join25("modules", module) : ".";
11277
+ const base = module ? join28("modules", module) : ".";
11089
11278
  const { seedPath: filePath, dataPath } = await seedCreate({
11090
11279
  name,
11091
- seedsDir: join25(base, "src", "seeds"),
11092
- testsDir: join25(base, "tests", "seeds")
11280
+ seedsDir: join28(base, "src", "seeds"),
11281
+ testsDir: join28(base, "tests", "seeds")
11093
11282
  });
11094
- const binSeedRunPath = join25(process.cwd(), base, "bin", "seed", "run.ts");
11283
+ const binSeedRunPath = join28(process.cwd(), base, "bin", "seed", "run.ts");
11095
11284
  const binSeedRunFile = Bun.file(binSeedRunPath);
11096
11285
  if (!await binSeedRunFile.exists()) {
11097
11286
  await Bun.write(binSeedRunPath, seed_run_default);
11098
11287
  }
11099
- const packageJsonPath = join25(process.cwd(), base, "package.json");
11100
- const packageJsonFile = Bun.file(packageJsonPath);
11101
- if (await packageJsonFile.exists()) {
11102
- const packageJson = await packageJsonFile.json();
11103
- packageJson.scripts = packageJson.scripts || {};
11104
- packageJson.scripts["seed:run"] = "bun ./bin/seed/run.ts";
11105
- await Bun.write(packageJsonPath, JSON.stringify(packageJson, null, 2));
11106
- }
11107
- const logger = new TerminalLogger23;
11288
+ const packageJsonPath = join28(process.cwd(), base, "package.json");
11289
+ const logger = new TerminalLogger26;
11108
11290
  logger.success(`${filePath} created successfully`, undefined, {
11109
11291
  showTimestamp: false,
11110
11292
  showArrow: false,
@@ -11115,17 +11297,12 @@ class MakeSeedCommand {
11115
11297
  showArrow: false,
11116
11298
  useSymbol: true
11117
11299
  });
11118
- logger.info("Run 'bun run seed:run' to execute seeds", undefined, {
11119
- showTimestamp: false,
11120
- showArrow: true,
11121
- showLevel: false
11122
- });
11123
11300
  const pkgJson = await Bun.file(packageJsonPath).json();
11124
11301
  const deps = pkgJson.dependencies ?? {};
11125
11302
  const devDeps = pkgJson.devDependencies ?? {};
11126
11303
  if (!deps["@ooneex/seeds"] && !devDeps["@ooneex/seeds"]) {
11127
11304
  const install = Bun.spawn(["bun", "add", "--dev", "@ooneex/seeds"], {
11128
- cwd: join25(process.cwd(), base),
11305
+ cwd: join28(process.cwd(), base),
11129
11306
  stdout: "ignore",
11130
11307
  stderr: "inherit"
11131
11308
  });
@@ -11134,12 +11311,12 @@ class MakeSeedCommand {
11134
11311
  }
11135
11312
  }
11136
11313
  MakeSeedCommand = __legacyDecorateClassTS([
11137
- decorator24.command()
11314
+ decorator28.command()
11138
11315
  ], MakeSeedCommand);
11139
11316
  // src/commands/MakeStorageCommand.ts
11140
- import { join as join26 } from "path";
11141
- import { decorator as decorator25 } from "@ooneex/command";
11142
- import { TerminalLogger as TerminalLogger24 } from "@ooneex/logger";
11317
+ import { join as join29 } from "path";
11318
+ import { decorator as decorator29 } from "@ooneex/command";
11319
+ import { TerminalLogger as TerminalLogger27 } from "@ooneex/logger";
11143
11320
  import { toPascalCase as toPascalCase16, toSnakeCase as toSnakeCase3 } from "@ooneex/utils";
11144
11321
 
11145
11322
  // src/templates/storage.test.txt
@@ -11239,28 +11416,28 @@ class MakeStorageCommand {
11239
11416
  if (module) {
11240
11417
  await ensureModule(module);
11241
11418
  }
11242
- const base = module ? join26("modules", module) : ".";
11243
- const storageLocalDir = join26(base, "src", "storage");
11244
- const storageDir = join26(process.cwd(), storageLocalDir);
11245
- const filePath = join26(storageDir, `${name}Storage.ts`);
11419
+ const base = module ? join29("modules", module) : ".";
11420
+ const storageLocalDir = join29(base, "src", "storage");
11421
+ const storageDir = join29(process.cwd(), storageLocalDir);
11422
+ const filePath = join29(storageDir, `${name}Storage.ts`);
11246
11423
  await Bun.write(filePath, content);
11247
11424
  const testContent = storage_test_default.replace(/{{NAME}}/g, name);
11248
- const testsLocalDir = join26(base, "tests", "storage");
11249
- const testsDir = join26(process.cwd(), testsLocalDir);
11250
- const testFilePath = join26(testsDir, `${name}Storage.spec.ts`);
11425
+ const testsLocalDir = join29(base, "tests", "storage");
11426
+ const testsDir = join29(process.cwd(), testsLocalDir);
11427
+ const testFilePath = join29(testsDir, `${name}Storage.spec.ts`);
11251
11428
  await Bun.write(testFilePath, testContent);
11252
- const logger = new TerminalLogger24;
11253
- logger.success(`${join26(storageLocalDir, name)}Storage.ts created successfully`, undefined, {
11429
+ const logger = new TerminalLogger27;
11430
+ logger.success(`${join29(storageLocalDir, name)}Storage.ts created successfully`, undefined, {
11254
11431
  showTimestamp: false,
11255
11432
  showArrow: false,
11256
11433
  useSymbol: true
11257
11434
  });
11258
- logger.success(`${join26(testsLocalDir, name)}Storage.spec.ts created successfully`, undefined, {
11435
+ logger.success(`${join29(testsLocalDir, name)}Storage.spec.ts created successfully`, undefined, {
11259
11436
  showTimestamp: false,
11260
11437
  showArrow: false,
11261
11438
  useSymbol: true
11262
11439
  });
11263
- const packageJsonPath = join26(process.cwd(), "package.json");
11440
+ const packageJsonPath = join29(process.cwd(), "package.json");
11264
11441
  const packageJson = await Bun.file(packageJsonPath).json();
11265
11442
  const deps = packageJson.dependencies ?? {};
11266
11443
  const devDeps = packageJson.devDependencies ?? {};
@@ -11275,12 +11452,12 @@ class MakeStorageCommand {
11275
11452
  }
11276
11453
  }
11277
11454
  MakeStorageCommand = __legacyDecorateClassTS([
11278
- decorator25.command()
11455
+ decorator29.command()
11279
11456
  ], MakeStorageCommand);
11280
11457
  // src/commands/MakeVectorDatabaseCommand.ts
11281
- import { join as join27 } from "path";
11282
- import { decorator as decorator26 } from "@ooneex/command";
11283
- import { TerminalLogger as TerminalLogger25 } from "@ooneex/logger";
11458
+ import { join as join30 } from "path";
11459
+ import { decorator as decorator30 } from "@ooneex/command";
11460
+ import { TerminalLogger as TerminalLogger28 } from "@ooneex/logger";
11284
11461
  import { toPascalCase as toPascalCase17 } from "@ooneex/utils";
11285
11462
 
11286
11463
  // src/templates/vector-database.test.txt
@@ -11354,28 +11531,28 @@ class MakeVectorDatabaseCommand {
11354
11531
  if (module) {
11355
11532
  await ensureModule(module);
11356
11533
  }
11357
- const base = module ? join27("modules", module) : ".";
11358
- const vectorDatabaseLocalDir = join27(base, "src", "databases");
11359
- const vectorDatabaseDir = join27(process.cwd(), vectorDatabaseLocalDir);
11360
- const filePath = join27(vectorDatabaseDir, `${name}VectorDatabase.ts`);
11534
+ const base = module ? join30("modules", module) : ".";
11535
+ const vectorDatabaseLocalDir = join30(base, "src", "databases");
11536
+ const vectorDatabaseDir = join30(process.cwd(), vectorDatabaseLocalDir);
11537
+ const filePath = join30(vectorDatabaseDir, `${name}VectorDatabase.ts`);
11361
11538
  await Bun.write(filePath, content);
11362
11539
  const testContent = vector_database_test_default.replace(/{{NAME}}/g, name);
11363
- const testsLocalDir = join27(base, "tests", "databases");
11364
- const testsDir = join27(process.cwd(), testsLocalDir);
11365
- const testFilePath = join27(testsDir, `${name}VectorDatabase.spec.ts`);
11540
+ const testsLocalDir = join30(base, "tests", "databases");
11541
+ const testsDir = join30(process.cwd(), testsLocalDir);
11542
+ const testFilePath = join30(testsDir, `${name}VectorDatabase.spec.ts`);
11366
11543
  await Bun.write(testFilePath, testContent);
11367
- const logger = new TerminalLogger25;
11368
- logger.success(`${join27(vectorDatabaseLocalDir, name)}VectorDatabase.ts created successfully`, undefined, {
11544
+ const logger = new TerminalLogger28;
11545
+ logger.success(`${join30(vectorDatabaseLocalDir, name)}VectorDatabase.ts created successfully`, undefined, {
11369
11546
  showTimestamp: false,
11370
11547
  showArrow: false,
11371
11548
  useSymbol: true
11372
11549
  });
11373
- logger.success(`${join27(testsLocalDir, name)}VectorDatabase.spec.ts created successfully`, undefined, {
11550
+ logger.success(`${join30(testsLocalDir, name)}VectorDatabase.spec.ts created successfully`, undefined, {
11374
11551
  showTimestamp: false,
11375
11552
  showArrow: false,
11376
11553
  useSymbol: true
11377
11554
  });
11378
- const packageJsonPath = join27(process.cwd(), "package.json");
11555
+ const packageJsonPath = join30(process.cwd(), "package.json");
11379
11556
  const packageJson = await Bun.file(packageJsonPath).json();
11380
11557
  const deps = packageJson.dependencies ?? {};
11381
11558
  const devDeps = packageJson.devDependencies ?? {};
@@ -11390,9 +11567,157 @@ class MakeVectorDatabaseCommand {
11390
11567
  }
11391
11568
  }
11392
11569
  MakeVectorDatabaseCommand = __legacyDecorateClassTS([
11393
- decorator26.command()
11570
+ decorator30.command()
11394
11571
  ], MakeVectorDatabaseCommand);
11572
+ // src/commands/MigrationUpCommand.ts
11573
+ import { existsSync } from "fs";
11574
+ import { join as join31 } from "path";
11575
+ import { decorator as decorator31 } from "@ooneex/command";
11576
+ import { TerminalLogger as TerminalLogger29 } from "@ooneex/logger";
11577
+ class MigrationUpCommand {
11578
+ getName() {
11579
+ return "migration:up";
11580
+ }
11581
+ getDescription() {
11582
+ return "Run migrations for all modules";
11583
+ }
11584
+ async run() {
11585
+ const logger = new TerminalLogger29;
11586
+ const modulesDir = join31(process.cwd(), "modules");
11587
+ if (!existsSync(modulesDir)) {
11588
+ logger.warn("No modules with migrations found", undefined, {
11589
+ showTimestamp: false,
11590
+ showArrow: false,
11591
+ useSymbol: true
11592
+ });
11593
+ return;
11594
+ }
11595
+ const glob = new Bun.Glob("*/package.json");
11596
+ const modules = [];
11597
+ for await (const match of glob.scan({ cwd: modulesDir, onlyFiles: true })) {
11598
+ const entry = match.replace("/package.json", "");
11599
+ const moduleDir = join31(modulesDir, entry);
11600
+ const migrationUpFile = Bun.file(join31(moduleDir, "bin", "migration", "up.ts"));
11601
+ if (await migrationUpFile.exists()) {
11602
+ const packageJson = await Bun.file(join31(modulesDir, match)).json();
11603
+ modules.push({ name: packageJson.name ?? entry, dir: moduleDir });
11604
+ }
11605
+ }
11606
+ if (modules.length === 0) {
11607
+ logger.warn("No modules with migrations found", undefined, {
11608
+ showTimestamp: false,
11609
+ showArrow: false,
11610
+ useSymbol: true
11611
+ });
11612
+ return;
11613
+ }
11614
+ for (const { name, dir } of modules) {
11615
+ const migrationUpPath = join31(dir, "bin", "migration", "up.ts");
11616
+ logger.info(`Running migrations for ${name}...`, undefined, {
11617
+ showTimestamp: false,
11618
+ showArrow: false,
11619
+ useSymbol: true
11620
+ });
11621
+ const proc = Bun.spawn(["bun", "run", migrationUpPath], {
11622
+ cwd: dir,
11623
+ stdout: "inherit",
11624
+ stderr: "inherit"
11625
+ });
11626
+ const exitCode = await proc.exited;
11627
+ if (exitCode === 0) {
11628
+ logger.success(`Migrations completed for ${name}`, undefined, {
11629
+ showTimestamp: false,
11630
+ showArrow: false,
11631
+ useSymbol: true
11632
+ });
11633
+ } else {
11634
+ logger.error(`Migrations failed for ${name} (exit code: ${exitCode})`, undefined, {
11635
+ showTimestamp: false,
11636
+ showArrow: false,
11637
+ useSymbol: true
11638
+ });
11639
+ }
11640
+ }
11641
+ }
11642
+ }
11643
+ MigrationUpCommand = __legacyDecorateClassTS([
11644
+ decorator31.command()
11645
+ ], MigrationUpCommand);
11646
+ // src/commands/SeedRunCommand.ts
11647
+ import { existsSync as existsSync2 } from "fs";
11648
+ import { join as join32 } from "path";
11649
+ import { decorator as decorator32 } from "@ooneex/command";
11650
+ import { TerminalLogger as TerminalLogger30 } from "@ooneex/logger";
11651
+ class SeedRunCommand {
11652
+ getName() {
11653
+ return "seed:run";
11654
+ }
11655
+ getDescription() {
11656
+ return "Run seeds for all modules";
11657
+ }
11658
+ async run() {
11659
+ const logger = new TerminalLogger30;
11660
+ const modulesDir = join32(process.cwd(), "modules");
11661
+ if (!existsSync2(modulesDir)) {
11662
+ logger.warn("No modules with seeds found", undefined, {
11663
+ showTimestamp: false,
11664
+ showArrow: false,
11665
+ useSymbol: true
11666
+ });
11667
+ return;
11668
+ }
11669
+ const glob = new Bun.Glob("*/package.json");
11670
+ const modules = [];
11671
+ for await (const match of glob.scan({ cwd: modulesDir, onlyFiles: true })) {
11672
+ const entry = match.replace("/package.json", "");
11673
+ const moduleDir = join32(modulesDir, entry);
11674
+ const seedRunFile = Bun.file(join32(moduleDir, "bin", "seed", "run.ts"));
11675
+ if (await seedRunFile.exists()) {
11676
+ const packageJson = await Bun.file(join32(modulesDir, match)).json();
11677
+ modules.push({ name: packageJson.name ?? entry, dir: moduleDir });
11678
+ }
11679
+ }
11680
+ if (modules.length === 0) {
11681
+ logger.warn("No modules with seeds found", undefined, {
11682
+ showTimestamp: false,
11683
+ showArrow: false,
11684
+ useSymbol: true
11685
+ });
11686
+ return;
11687
+ }
11688
+ for (const { name, dir } of modules) {
11689
+ const seedRunPath = join32(dir, "bin", "seed", "run.ts");
11690
+ logger.info(`Running seeds for ${name}...`, undefined, {
11691
+ showTimestamp: false,
11692
+ showArrow: false,
11693
+ useSymbol: true
11694
+ });
11695
+ const proc = Bun.spawn(["bun", "run", seedRunPath], {
11696
+ cwd: dir,
11697
+ stdout: "inherit",
11698
+ stderr: "inherit"
11699
+ });
11700
+ const exitCode = await proc.exited;
11701
+ if (exitCode === 0) {
11702
+ logger.success(`Seeds completed for ${name}`, undefined, {
11703
+ showTimestamp: false,
11704
+ showArrow: false,
11705
+ useSymbol: true
11706
+ });
11707
+ } else {
11708
+ logger.error(`Seeds failed for ${name} (exit code: ${exitCode})`, undefined, {
11709
+ showTimestamp: false,
11710
+ showArrow: false,
11711
+ useSymbol: true
11712
+ });
11713
+ }
11714
+ }
11715
+ }
11716
+ }
11717
+ SeedRunCommand = __legacyDecorateClassTS([
11718
+ decorator32.command()
11719
+ ], SeedRunCommand);
11395
11720
  // src/index.ts
11396
11721
  await run();
11397
11722
 
11398
- //# debugId=14FC8E204F5BE58B64756E2164756E21
11723
+ //# debugId=1317A3FF7CEC8E3164756E2164756E21