@ooneex/cli 1.15.2 → 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 +1605 -1274
  2. package/dist/index.js.map +16 -10
  3. package/package.json +4 -23
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,33 +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
- appModulePackageJson.scripts.dev = "bun --hot run ./src/index.ts";
7044
+ appModulePackageJson.scripts.dev = "docker compose up -d && bun --hot run ./src/index.ts";
7045
+ appModulePackageJson.scripts.stop = "docker compose down";
7299
7046
  appModulePackageJson.scripts.build = "bun build ./src/index.ts --outdir ./dist --target bun";
7300
7047
  await Bun.write(appModulePackagePath, JSON.stringify(appModulePackageJson, null, 2));
7301
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"');
7302
- await Bun.write(join7(destination, "modules", "app", ".env"), envContent);
7303
- await Bun.write(join7(destination, "modules", "app", ".env.example"), env_default);
7304
- await Bun.write(join7(destination, "modules", "app", "src", "databases", "AppDatabase.ts"), app_database_default);
7305
- 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);
7306
7053
  const snakeName = toSnakeCase(name);
7307
7054
  const dockerComposeContent = docker_compose_yml_default.replace(/{{NAME}}/g, snakeName);
7308
- await Bun.write(join7(destination, "modules", "app", "docker-compose.yml"), dockerComposeContent);
7055
+ await Bun.write(join9(destination, "modules", "app", "docker-compose.yml"), dockerComposeContent);
7309
7056
  const dockerfileContent = Dockerfile_default.replace(/{{NAME}}/g, snakeName);
7310
- await Bun.write(join7(destination, "modules", "app", "Dockerfile"), dockerfileContent);
7311
- await Bun.write(join7(destination, "modules", "app", "var", ".gitkeep"), "");
7312
- const makeControllerCommand = new MakeControllerCommand;
7313
- await makeControllerCommand.run({
7314
- name: "HealthCheck",
7315
- isSocket: false,
7316
- module: "app",
7317
- route: {
7318
- name: "api.health.check",
7319
- path: "/healthcheck",
7320
- method: "GET"
7321
- }
7322
- });
7057
+ await Bun.write(join9(destination, "modules", "app", "Dockerfile"), dockerfileContent);
7058
+ await Bun.write(join9(destination, "modules", "app", "var", ".gitkeep"), "");
7323
7059
  const gitInit = Bun.spawn(["git", "init"], { cwd: destination, stdout: "ignore", stderr: "inherit" });
7324
7060
  await gitInit.exited;
7325
7061
  const addDeps = Bun.spawn([
@@ -7378,9 +7114,9 @@ class MakeAppCommand {
7378
7114
  await addDevDeps.exited;
7379
7115
  const huskyInit = Bun.spawn(["bunx", "husky", "init"], { cwd: destination, stdout: "ignore", stderr: "inherit" });
7380
7116
  await huskyInit.exited;
7381
- await Bun.write(join7(destination, ".husky", "pre-commit"), "lint-staged");
7382
- await Bun.write(join7(destination, ".husky", "commit-msg"), `bunx commitlint --edit "$1"`);
7383
- 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;
7384
7120
  logger.success(`${kebabName} created successfully at ${destination}`, undefined, {
7385
7121
  showTimestamp: false,
7386
7122
  showArrow: false,
@@ -7389,512 +7125,978 @@ class MakeAppCommand {
7389
7125
  }
7390
7126
  }
7391
7127
  MakeAppCommand = __legacyDecorateClassTS([
7392
- decorator6.command()
7128
+ decorator9.command()
7393
7129
  ], MakeAppCommand);
7394
7130
  // src/commands/MakeCacheCommand.ts
7395
- import { join as join8 } from "path";
7396
- import { decorator as decorator7 } from "@ooneex/command";
7397
- import { TerminalLogger as TerminalLogger7 } from "@ooneex/logger";
7398
- 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\` |
7399
7329
 
7400
- // src/templates/cache.test.txt
7401
- var cache_test_default = `import { describe, expect, test } from "bun:test";
7402
- import { {{NAME}}Cache } from "@/cache/{{NAME}}Cache";
7330
+ ## Examples
7403
7331
 
7404
- describe("{{NAME}}Cache", () => {
7405
- test("should have class name ending with 'Cache'", () => {
7406
- expect({{NAME}}Cache.name.endsWith("Cache")).toBe(true);
7407
- });
7332
+ ### Example 1: Multiple Module Changes
7408
7333
 
7409
- test("should have 'get' method", () => {
7410
- expect({{NAME}}Cache.prototype.get).toBeDefined();
7411
- expect(typeof {{NAME}}Cache.prototype.get).toBe("function");
7412
- });
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
+ \`\`\`
7413
7344
 
7414
- test("should have 'set' method", () => {
7415
- expect({{NAME}}Cache.prototype.set).toBeDefined();
7416
- expect(typeof {{NAME}}Cache.prototype.set).toBe("function");
7417
- });
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"
7418
7350
 
7419
- test("should have 'delete' method", () => {
7420
- expect({{NAME}}Cache.prototype.delete).toBeDefined();
7421
- expect(typeof {{NAME}}Cache.prototype.delete).toBe("function");
7422
- });
7351
+ # Commit product module
7352
+ git add modules/product/
7353
+ git commit -m "refactor(product): Update Product entity and repository"
7423
7354
 
7424
- test("should have 'has' method", () => {
7425
- expect({{NAME}}Cache.prototype.has).toBeDefined();
7426
- expect(typeof {{NAME}}Cache.prototype.has).toBe("function");
7427
- });
7428
- });
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
+ \`\`\`
7429
7446
  `;
7430
7447
 
7431
- // src/templates/cache.txt
7432
- var cache_default = `import { CacheException, decorator } from "@ooneex/cache";
7433
- 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';
7434
7450
 
7435
- @decorator.cache()
7436
- export class {{NAME}}Cache implements ICache {
7437
- public async get<T = unknown>(key: string): Promise<T | undefined> {
7438
- throw new CacheException(\`Failed to get key "\${key}": Not implemented\`);
7439
- }
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';
7440
7453
 
7441
- public async set<T = unknown>(key: string, value: T, ttl?: number): Promise<void> {
7442
- throw new CacheException(\`Failed to set key "\${key}": Not implemented\`);
7443
- }
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';
7444
7456
 
7445
- public async delete(key: string): Promise<boolean> {
7446
- throw new CacheException(\`Failed to delete key "\${key}": Not implemented\`);
7447
- }
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";
7448
7459
 
7449
- public async has(key: string): Promise<boolean> {
7450
- 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
+ }
7451
7550
  }
7452
7551
  }
7453
- `;
7454
-
7455
- // src/commands/MakeCacheCommand.ts
7456
- 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 {
7457
7560
  getName() {
7458
- return "make:cache";
7561
+ return "make:command";
7459
7562
  }
7460
7563
  getDescription() {
7461
- return "Generate a new cache class";
7564
+ return "Generate a new command class";
7462
7565
  }
7463
7566
  async run(options) {
7464
7567
  let { name, module } = options;
7465
7568
  if (!name) {
7466
- 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
+ }
7467
7595
  }
7468
- name = toPascalCase5(name).replace(/Cache$/, "");
7469
- const content = cache_default.replace(/{{NAME}}/g, name);
7470
- if (module) {
7471
- 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);
7472
7600
  }
7473
- const base = module ? join8("modules", module) : ".";
7474
- const cacheLocalDir = join8(base, "src", "cache");
7475
- const cacheDir = join8(process.cwd(), cacheLocalDir);
7476
- const filePath = join8(cacheDir, `${name}Cache.ts`);
7477
- await Bun.write(filePath, content);
7478
- const testContent = cache_test_default.replace(/{{NAME}}/g, name);
7479
- const testsLocalDir = join8(base, "tests", "cache");
7480
- const testsDir = join8(process.cwd(), testsLocalDir);
7481
- const testFilePath = join8(testsDir, `${name}Cache.spec.ts`);
7482
- await Bun.write(testFilePath, testContent);
7483
- const logger = new TerminalLogger7;
7484
- 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, {
7485
7611
  showTimestamp: false,
7486
7612
  showArrow: false,
7487
7613
  useSymbol: true
7488
7614
  });
7489
- logger.success(`${join8(testsLocalDir, name)}Cache.spec.ts created successfully`, undefined, {
7615
+ logger.success(`${testPath} created successfully`, undefined, {
7490
7616
  showTimestamp: false,
7491
7617
  showArrow: false,
7492
7618
  useSymbol: true
7493
7619
  });
7494
- const packageJsonPath = join8(process.cwd(), "package.json");
7495
- const packageJson = await Bun.file(packageJsonPath).json();
7496
- const deps = packageJson.dependencies ?? {};
7497
- const devDeps = packageJson.devDependencies ?? {};
7498
- if (!deps["@ooneex/cache"] && !devDeps["@ooneex/cache"]) {
7499
- const install = Bun.spawn(["bun", "add", "@ooneex/cache"], {
7500
- cwd: process.cwd(),
7501
- stdout: "ignore",
7502
- stderr: "inherit"
7503
- });
7504
- await install.exited;
7505
- }
7620
+ logger.info("Run 'bun run command' to execute commands", undefined, {
7621
+ showTimestamp: false,
7622
+ showArrow: true,
7623
+ showLevel: false
7624
+ });
7506
7625
  }
7507
7626
  }
7508
- MakeCacheCommand = __legacyDecorateClassTS([
7509
- decorator7.command()
7510
- ], MakeCacheCommand);
7511
- // src/commands/MakeClaudeSkillCommand.ts
7512
- import { join as join9 } from "path";
7513
- import { decorator as decorator8 } from "@ooneex/command";
7514
- import { TerminalLogger as TerminalLogger8 } from "@ooneex/logger";
7515
-
7516
- // src/templates/claude/skills/commit.md.txt
7517
- var commit_md_default = `---
7518
- name: commit
7519
- 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.
7520
- ---
7521
-
7522
- # Commit by Module
7523
-
7524
- Create separate commits for each modified module, following the project's commitlint conventions.
7525
-
7526
- ## Workflow
7527
-
7528
- 1. **Analyze staged/unstaged changes**
7529
- - Run \`git status --porcelain\` to get all modified files
7530
- - Group changes by module (files under \`modules/<module-name>/\`)
7531
- - Identify non-module changes (files not in \`modules/\`) as scope \`common\`
7532
-
7533
- 2. **For each module with changes**
7534
- - Stage only that module's files: \`git add modules/<module-name>/\`
7535
- - Determine the appropriate commit type based on changes
7536
- - Create a commit with proper format: \`type(scope): Subject\`
7537
- - The scope is the module name in lower-case
7538
- - Repeat for next module
7539
-
7540
- 3. **Handle non-module changes**
7541
- - Files outside \`modules/\` (e.g., \`bun.lock\`, \`package.json\`, config files, \`packages/\` changes) use scope \`common\`
7542
- - Stage and commit separately from module changes
7543
-
7544
- ## Commit Message Format
7545
-
7546
- \`\`\`
7547
- type(scope): Subject line
7548
- \`\`\`
7549
-
7550
- ### Valid Types
7551
- - \`feat\`: New feature
7552
- - \`fix\`: Bug fix
7553
- - \`refactor\`: Code refactoring (no new feature, no bug fix)
7554
- - \`test\`: Adding/updating tests
7555
- - \`chore\`: Maintenance tasks (dependencies, configs)
7556
- - \`docs\`: Documentation changes
7557
- - \`style\`: Code style changes (formatting, whitespace)
7558
- - \`perf\`: Performance improvements
7559
- - \`build\`: Build system changes
7560
- - \`ci\`: CI configuration changes
7561
- - \`revert\`: Revert previous commit
7562
-
7563
- ### Scope Rules
7564
- - For files under \`modules/<name>/\`: use the module name as scope (e.g., \`module\` scope matches the commitlint config)
7565
- - For all other files: use \`common\`
7566
- - Scope must be lower-case
7567
- - Scope must never be empty
7568
- - If the module name matches a valid commitlint scope, use it directly
7569
- - If it does not match, use \`module\` as the scope
7570
-
7571
- ### Subject Rules
7572
- - Use sentence-case, start-case, pascal-case, or upper-case
7573
- - No period at the end
7574
- - Maximum 100 characters for entire header
7575
- - Use imperative mood ("Add feature" not "Added feature")
7576
-
7577
- ## Determining Commit Type
7578
-
7579
- Analyze the changes to determine the appropriate type:
7580
-
7581
- | Change Pattern | Type |
7582
- |---------------|------|
7583
- | New files with functionality | \`feat\` |
7584
- | Bug fixes, error corrections | \`fix\` |
7585
- | Code restructuring, renaming | \`refactor\` |
7586
- | New/updated test files (\`*.spec.ts\`) | \`test\` |
7587
- | Documentation files (\`*.md\`) | \`docs\` |
7588
- | Dependency updates, lock files | \`chore\` |
7589
- | Build configs, scripts | \`build\` |
7590
- | CI/CD files | \`ci\` |
7591
- | Formatting only | \`style\` |
7592
- | Performance optimizations | \`perf\` |
7593
-
7594
- ## Examples
7595
-
7596
- ### Example 1: Multiple Module Changes
7597
-
7598
- Git status shows:
7599
- \`\`\`
7600
- M modules/user/src/services/UserService.ts
7601
- A modules/user/src/services/AuthService.ts
7602
- M modules/user/tests/UserService.spec.ts
7603
- M modules/product/src/entities/Product.ts
7604
- M modules/product/src/repositories/ProductRepository.ts
7605
- M bun.lock
7606
- M packages/cache/src/index.ts
7607
- \`\`\`
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";
7608
7635
 
7609
- Commands to execute:
7610
- \`\`\`bash
7611
- # Commit user module
7612
- git add modules/user/
7613
- git commit -m "feat(user): Add AuthService and update UserService"
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
+ };
7614
7647
 
7615
- # Commit product module
7616
- git add modules/product/
7617
- git commit -m "refactor(product): Update Product entity and repository"
7648
+ // ../types/dist/index.js
7649
+ var HTTP_METHODS = ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"];
7618
7650
 
7619
- # Commit non-module changes
7620
- git add bun.lock packages/cache/
7621
- git commit -m "chore(common): Update dependencies and cache package"
7622
- \`\`\`
7651
+ // src/prompts/askRouteMethod.ts
7652
+ var import_enquirer4 = __toESM(require_enquirer(), 1);
7623
7653
 
7624
- ### Example 2: Single Module with Tests
7654
+ // src/constraints/AssertRouteMethod.ts
7655
+ import { Assert as Assert2, Validation as Validation2 } from "@ooneex/validation";
7625
7656
 
7626
- Git status shows:
7627
- \`\`\`
7628
- A modules/payment/src/services/StripeService.ts
7629
- A modules/payment/tests/StripeService.spec.ts
7630
- \`\`\`
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
+ }
7631
7688
 
7632
- Command:
7633
- \`\`\`bash
7634
- git add modules/payment/
7635
- git commit -m "feat(payment): Add StripeService with tests"
7636
- \`\`\`
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
+ };
7637
7708
 
7638
- ### Example 3: Only Non-module Changes
7709
+ // src/prompts/askRouteName.ts
7710
+ var import_enquirer5 = __toESM(require_enquirer(), 1);
7639
7711
 
7640
- Git status shows:
7641
- \`\`\`
7642
- M package.json
7643
- M bun.lock
7644
- M packages/logger/src/Logger.ts
7645
- \`\`\`
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]+$/;
7646
7715
 
7647
- Command:
7648
- \`\`\`bash
7649
- git add package.json bun.lock packages/logger/
7650
- git commit -m "chore(common): Update dependencies and logger package"
7651
- \`\`\`
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
+ }
7652
7760
 
7653
- ### Example 4: Module Refactoring with Deletions
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
+ };
7654
7778
 
7655
- Git status shows:
7656
- \`\`\`
7657
- D modules/order/src/services/OldOrderService.ts
7658
- A modules/order/src/services/OrderService.ts
7659
- M modules/order/src/index.ts
7660
- \`\`\`
7779
+ // src/prompts/askRoutePath.ts
7780
+ var import_enquirer6 = __toESM(require_enquirer(), 1);
7661
7781
 
7662
- Command:
7663
- \`\`\`bash
7664
- git add modules/order/
7665
- git commit -m "refactor(order): Replace OldOrderService with OrderService"
7666
- \`\`\`
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]*$/;
7667
7788
 
7668
- ## Subject Line Guidelines
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
+ }
7669
7860
 
7670
- Write clear, descriptive subjects:
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
+ };
7671
7879
 
7672
- | Good | Bad |
7673
- |------|-----|
7674
- | \`Add Stripe payment integration\` | \`stripe\` |
7675
- | \`Fix null check in user validation\` | \`fix bug\` |
7676
- | \`Update order processing flow\` | \`changes\` |
7677
- | \`Remove deprecated auth methods\` | \`cleanup\` |
7678
- | \`Rename UserAdapter to UserService\` | \`rename\` |
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";
7679
7885
 
7680
- ## Pre-commit Checklist
7886
+ export type {{TYPE_NAME}}RouteType = {
7887
+ params: {
7681
7888
 
7682
- Before committing, verify:
7683
- 1. Changes are logically grouped by module
7684
- 2. Tests pass for modified modules
7685
- 3. No debug code or console.logs left in
7686
- 4. Commit message follows the format exactly
7687
- 5. For Entity class, if property is optional, add \`null\` to its type.
7889
+ },
7890
+ payload: {
7688
7891
 
7689
- ## Handling Special Cases
7892
+ },
7893
+ queries: {
7690
7894
 
7691
- ### Mixed Changes in One Module
7692
- If a module has both new features and bug fixes, prioritize:
7693
- 1. \`feat\` if primary change is new functionality
7694
- 2. \`fix\` if primary change is a bug fix
7695
- 3. Split into multiple commits if changes are truly independent
7895
+ },
7896
+ response: {
7696
7897
 
7697
- ### Deleted Files Only
7698
- Use \`refactor\` for removing deprecated code:
7699
- \`\`\`bash
7700
- git add modules/user/
7701
- git commit -m "refactor(user): Remove deprecated UserAdapter"
7702
- \`\`\`
7898
+ },
7899
+ };
7703
7900
 
7704
- ### Renamed/Moved Files
7705
- Use \`refactor\` for file reorganization:
7706
- \`\`\`bash
7707
- git add modules/product/
7708
- git commit -m "refactor(product): Reorganize service file structure"
7709
- \`\`\`
7710
- `;
7901
+ @Route.socket("{{ROUTE_PATH}}", {
7902
+ name: "{{ROUTE_NAME}}",
7903
+ version: 1,
7904
+ description: "",
7905
+ params: {
7906
+ // id: Assert("string"),
7907
+ },
7908
+ payload: Assert({
7711
7909
 
7712
- // src/templates/claude/skills/make.ai.md.txt
7713
- 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";
7910
+ }),
7911
+ queries: Assert({
7714
7912
 
7715
- // src/templates/claude/skills/make.analytics.md.txt
7716
- 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";
7913
+ }),
7914
+ response: Assert({
7717
7915
 
7718
- // src/templates/claude/skills/make.cache.md.txt
7719
- 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";
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;
7720
7922
 
7721
- // src/templates/claude/skills/make.controller.md.txt
7722
- 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";
7923
+ return context.response.json({
7723
7924
 
7724
- // src/templates/claude/skills/make.cron.md.txt
7725
- 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";
7925
+ });
7926
+ }
7927
+ }
7928
+ `;
7726
7929
 
7727
- // src/templates/claude/skills/make.database.md.txt
7728
- 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";
7729
7933
 
7730
- // src/templates/claude/skills/make.entity.md.txt
7731
- 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\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
+ });
7732
7938
 
7733
- // src/templates/claude/skills/make.logger.md.txt
7734
- 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
+ `;
7735
7945
 
7736
- // src/templates/claude/skills/make.mailer.md.txt
7737
- 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";
7738
7951
 
7739
- // src/templates/claude/skills/make.middleware.md.txt
7740
- 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: {
7741
7954
 
7742
- // src/templates/claude/skills/make.migration.md.txt
7743
- 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: {
7744
7957
 
7745
- // src/templates/claude/skills/make.permission.md.txt
7746
- 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: {
7747
7960
 
7748
- // src/templates/claude/skills/make.pubsub.md.txt
7749
- 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: {
7750
7963
 
7751
- // src/templates/claude/skills/make.repository.md.txt
7752
- 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
+ };
7753
7966
 
7754
- // src/templates/claude/skills/make.seed.md.txt
7755
- 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- `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 both generated files to understand the scaffolded code.\n\n### 3. Complete the seed\n\nEdit `src/seeds/<Name>Seed.ts` to implement:\n\n- Import the relevant entity classes\n- Create seed data with hardcoded nanoid values for `id` fields (generate via `bun -e \"import { random } from '@ooneex/utils'; console.log(random.nanoid())\"`)\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 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 methods)\n- Add tests relevant to the specific seed behavior\n\nThe generated test structure follows this pattern:\n\n```typescript\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```\n\n### 5. Lint and format\n\nRun linting and formatting on the generated files:\n\n```bash\nbunx biome check --fix src/seeds/<Name>Seed.ts 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({
7756
7975
 
7757
- // src/templates/claude/skills/make.service.md.txt
7758
- 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({
7759
7978
 
7760
- // src/templates/claude/skills/make.storage.md.txt
7761
- 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({
7762
7981
 
7763
- // src/templates/claude/skills/make.vector-database.md.txt
7764
- 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;
7765
7988
 
7766
- // src/templates/claude/skills/optimize.md.txt
7767
- 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({
7768
7990
 
7769
- // src/commands/MakeClaudeSkillCommand.ts
7770
- var skills = {
7771
- "make.ai": make_ai_md_default,
7772
- "make.analytics": make_analytics_md_default,
7773
- "make.cache": make_cache_md_default,
7774
- "make.controller": make_controller_md_default,
7775
- "make.cron": make_cron_md_default,
7776
- "make.database": make_database_md_default,
7777
- "make.entity": make_entity_md_default,
7778
- "make.logger": make_logger_md_default,
7779
- "make.mailer": make_mailer_md_default,
7780
- "make.middleware": make_middleware_md_default,
7781
- "make.migration": make_migration_md_default,
7782
- "make.permission": make_permission_md_default,
7783
- "make.pubsub": make_pubsub_md_default,
7784
- "make.repository": make_repository_md_default,
7785
- "make.seed": make_seed_md_default,
7786
- "make.service": make_service_md_default,
7787
- "make.storage": make_storage_md_default,
7788
- "make.vector-database": make_vector_database_md_default,
7789
- commit: commit_md_default,
7790
- optimize: optimize_md_default
7791
- };
7991
+ });
7992
+ }
7993
+ }
7994
+ `;
7792
7995
 
7793
- class MakeClaudeSkillCommand {
7996
+ // src/commands/MakeControllerCommand.ts
7997
+ class MakeControllerCommand {
7794
7998
  getName() {
7795
- return "make:claude:skill";
7999
+ return "make:controller";
7796
8000
  }
7797
8001
  getDescription() {
7798
- return "Generate Claude skills from templates";
8002
+ return "Generate a new controller class";
7799
8003
  }
7800
- async run() {
7801
- const skillsLocalDir = join9(".claude", "skills");
7802
- const skillsDir = join9(process.cwd(), skillsLocalDir);
7803
- const logger = new TerminalLogger8;
7804
- for (const [skillName, content] of Object.entries(skills)) {
7805
- const dirName = skillName.replace(/\./g, "-");
7806
- const filePath = join9(skillsDir, dirName, "SKILL.md");
7807
- await Bun.write(filePath, content);
7808
- logger.success(`${join9(skillsLocalDir, dirName, "SKILL.md")} created successfully`, undefined, {
7809
- showTimestamp: false,
7810
- showArrow: false,
7811
- useSymbol: true
7812
- });
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}`);
7813
8019
  }
7814
- }
7815
- }
7816
- MakeClaudeSkillCommand = __legacyDecorateClassTS([
7817
- decorator8.command()
7818
- ], MakeClaudeSkillCommand);
7819
- // src/commands/MakeCommandCommand.ts
7820
- import { join as join10 } from "path";
7821
- import { commandCreate, decorator as decorator9 } from "@ooneex/command";
7822
- import { TerminalLogger as TerminalLogger9 } from "@ooneex/logger";
7823
- class MakeCommandCommand {
7824
- getName() {
7825
- return "make:command";
7826
- }
7827
- getDescription() {
7828
- return "Generate a new command class";
8020
+ await Bun.write(modulePath, content);
7829
8021
  }
7830
8022
  async run(options) {
7831
- let { name, module } = options;
8023
+ let { name, module, isSocket } = options;
7832
8024
  if (!name) {
7833
- name = await askName({ message: "Enter name" });
8025
+ name = await askName({ message: "Enter controller name" });
7834
8026
  }
7835
- if (module) {
7836
- await ensureModule(module);
8027
+ if (isSocket === undefined) {
8028
+ isSocket = await askConfirm({ message: "Is this a socket controller?" });
7837
8029
  }
7838
- const base = module ? join10("modules", module) : ".";
7839
- const { commandPath, testPath } = await commandCreate({
7840
- name,
7841
- commandDir: join10(base, "src", "commands"),
7842
- testsDir: join10(base, "tests", "commands")
7843
- });
7844
- if (module && module !== "app") {
7845
- const appCommandsRootPath = join10(process.cwd(), "modules", "app", "src", "commands", "commands.ts");
7846
- const appCommandsRootFile = Bun.file(appCommandsRootPath);
7847
- const importLine = `import "@${module}/commands/commands";`;
7848
- if (await appCommandsRootFile.exists()) {
7849
- const appCommandsContent = await appCommandsRootFile.text();
7850
- if (!appCommandsContent.includes(importLine)) {
7851
- await Bun.write(appCommandsRootPath, `${appCommandsContent.trimEnd()}
7852
- ${importLine}
7853
- `);
7854
- }
7855
- } else {
7856
- await Bun.write(appCommandsRootPath, `${importLine}
7857
- `);
7858
- }
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)" });
7859
8036
  }
7860
- const binCommandRunPath = join10(process.cwd(), "modules", "app", "bin", "command", "run.ts");
7861
- const binCommandRunFile = Bun.file(binCommandRunPath);
7862
- if (!await binCommandRunFile.exists()) {
7863
- 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: "/" });
7864
8041
  }
7865
- const packageJsonPath = join10(process.cwd(), "modules", "app", "package.json");
7866
- const packageJsonFile = Bun.file(packageJsonPath);
7867
- if (await packageJsonFile.exists()) {
7868
- const packageJson = await packageJsonFile.json();
7869
- packageJson.scripts = packageJson.scripts || {};
7870
- packageJson.scripts.command = "bun ./bin/command/run.ts";
7871
- 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" });
7872
8046
  }
7873
- const logger = new TerminalLogger9;
7874
- 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, {
7875
8070
  showTimestamp: false,
7876
8071
  showArrow: false,
7877
8072
  useSymbol: true
7878
8073
  });
7879
- logger.success(`${testPath} created successfully`, undefined, {
8074
+ logger.success(`${join13(testsLocalDir, name)}Controller.spec.ts created successfully`, undefined, {
7880
8075
  showTimestamp: false,
7881
8076
  showArrow: false,
7882
8077
  useSymbol: true
7883
8078
  });
7884
- logger.info("Run 'bun run command' to execute commands", undefined, {
7885
- showTimestamp: false,
7886
- showArrow: true,
7887
- showLevel: false
7888
- });
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
+ }
7889
8091
  }
7890
8092
  }
7891
- MakeCommandCommand = __legacyDecorateClassTS([
7892
- decorator9.command()
7893
- ], MakeCommandCommand);
8093
+ MakeControllerCommand = __legacyDecorateClassTS([
8094
+ decorator13.command()
8095
+ ], MakeControllerCommand);
7894
8096
  // src/commands/MakeCronCommand.ts
7895
- import { basename as basename2, join as join11 } from "path";
7896
- import { decorator as decorator10 } from "@ooneex/command";
7897
- 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";
7898
8100
  import { toPascalCase as toPascalCase6 } from "@ooneex/utils";
7899
8101
 
7900
8102
  // src/templates/cron.test.txt
@@ -7983,33 +8185,33 @@ class MakeCronCommand {
7983
8185
  if (module) {
7984
8186
  await ensureModule(module);
7985
8187
  }
7986
- const base = module ? join11("modules", module) : ".";
7987
- const cronLocalDir = join11(base, "src", "crons");
7988
- const cronDir = join11(process.cwd(), cronLocalDir);
7989
- 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`);
7990
8192
  await Bun.write(filePath, content);
7991
8193
  const testContent = cron_test_default.replace(/{{NAME}}/g, name);
7992
- const testsLocalDir = join11(base, "tests", "crons");
7993
- const testsDir = join11(process.cwd(), testsLocalDir);
7994
- 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`);
7995
8197
  await Bun.write(testFilePath, testContent);
7996
8198
  const modulePascalName = module ? toPascalCase6(module) : toPascalCase6(basename2(process.cwd()));
7997
- const modulePath = join11(process.cwd(), base, "src", `${modulePascalName}Module.ts`);
8199
+ const modulePath = join14(process.cwd(), base, "src", `${modulePascalName}Module.ts`);
7998
8200
  if (await Bun.file(modulePath).exists()) {
7999
8201
  await this.addToModule(modulePath, name);
8000
8202
  }
8001
- const logger = new TerminalLogger10;
8002
- 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, {
8003
8205
  showTimestamp: false,
8004
8206
  showArrow: false,
8005
8207
  useSymbol: true
8006
8208
  });
8007
- logger.success(`${join11(testsLocalDir, name)}Cron.spec.ts created successfully`, undefined, {
8209
+ logger.success(`${join14(testsLocalDir, name)}Cron.spec.ts created successfully`, undefined, {
8008
8210
  showTimestamp: false,
8009
8211
  showArrow: false,
8010
8212
  useSymbol: true
8011
8213
  });
8012
- const packageJsonPath = join11(process.cwd(), "package.json");
8214
+ const packageJsonPath = join14(process.cwd(), "package.json");
8013
8215
  const packageJson = await Bun.file(packageJsonPath).json();
8014
8216
  const deps = packageJson.dependencies ?? {};
8015
8217
  const devDeps = packageJson.devDependencies ?? {};
@@ -8024,12 +8226,12 @@ class MakeCronCommand {
8024
8226
  }
8025
8227
  }
8026
8228
  MakeCronCommand = __legacyDecorateClassTS([
8027
- decorator10.command()
8229
+ decorator14.command()
8028
8230
  ], MakeCronCommand);
8029
8231
  // src/commands/MakeDatabaseCommand.ts
8030
- import { join as join12 } from "path";
8031
- import { decorator as decorator11 } from "@ooneex/command";
8032
- 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";
8033
8235
  import { toPascalCase as toPascalCase7 } from "@ooneex/utils";
8034
8236
 
8035
8237
  // src/templates/database.test.txt
@@ -8092,28 +8294,28 @@ class MakeDatabaseCommand {
8092
8294
  if (module) {
8093
8295
  await ensureModule(module);
8094
8296
  }
8095
- const base = module ? join12("modules", module) : ".";
8096
- const databaseLocalDir = join12(base, "src", "databases");
8097
- const databaseDir = join12(process.cwd(), databaseLocalDir);
8098
- 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`);
8099
8301
  await Bun.write(filePath, content);
8100
8302
  const testContent = database_test_default.replace(/{{NAME}}/g, name);
8101
- const testsLocalDir = join12(base, "tests", "databases");
8102
- const testsDir = join12(process.cwd(), testsLocalDir);
8103
- 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`);
8104
8306
  await Bun.write(testFilePath, testContent);
8105
- const logger = new TerminalLogger11;
8106
- 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, {
8107
8309
  showTimestamp: false,
8108
8310
  showArrow: false,
8109
8311
  useSymbol: true
8110
8312
  });
8111
- logger.success(`${join12(testsLocalDir, name)}Database.spec.ts created successfully`, undefined, {
8313
+ logger.success(`${join15(testsLocalDir, name)}Database.spec.ts created successfully`, undefined, {
8112
8314
  showTimestamp: false,
8113
8315
  showArrow: false,
8114
8316
  useSymbol: true
8115
8317
  });
8116
- const packageJsonPath = join12(process.cwd(), "package.json");
8318
+ const packageJsonPath = join15(process.cwd(), "package.json");
8117
8319
  const packageJson = await Bun.file(packageJsonPath).json();
8118
8320
  const deps = packageJson.dependencies ?? {};
8119
8321
  const devDeps = packageJson.devDependencies ?? {};
@@ -8128,12 +8330,12 @@ class MakeDatabaseCommand {
8128
8330
  }
8129
8331
  }
8130
8332
  MakeDatabaseCommand = __legacyDecorateClassTS([
8131
- decorator11.command()
8333
+ decorator15.command()
8132
8334
  ], MakeDatabaseCommand);
8133
8335
  // src/commands/MakeDockerCommand.ts
8134
- import { join as join13 } from "path";
8135
- import { decorator as decorator12 } from "@ooneex/command";
8136
- 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";
8137
8339
  var {YAML } = globalThis.Bun;
8138
8340
 
8139
8341
  // src/prompts/askDockerService.ts
@@ -8626,9 +8828,9 @@ class MakeDockerCommand {
8626
8828
  name = await askDockerService({ message: "Select docker service" });
8627
8829
  }
8628
8830
  const templateContent = templates[name];
8629
- const base = join13("modules", "app");
8630
- const composePath = join13(process.cwd(), base, "docker-compose.yml");
8631
- 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;
8632
8834
  const composeFile = Bun.file(composePath);
8633
8835
  if (await composeFile.exists()) {
8634
8836
  const existingContent = await composeFile.text();
@@ -8687,7 +8889,7 @@ volumes:
8687
8889
  } else {
8688
8890
  await Bun.write(composePath, templateContent);
8689
8891
  }
8690
- const packageJsonPath = join13(process.cwd(), base, "package.json");
8892
+ const packageJsonPath = join16(process.cwd(), base, "package.json");
8691
8893
  const packageJsonFile = Bun.file(packageJsonPath);
8692
8894
  if (await packageJsonFile.exists()) {
8693
8895
  const packageJson = await packageJsonFile.json();
@@ -8708,13 +8910,13 @@ volumes:
8708
8910
  }
8709
8911
  }
8710
8912
  MakeDockerCommand = __legacyDecorateClassTS([
8711
- decorator12.command()
8913
+ decorator16.command()
8712
8914
  ], MakeDockerCommand);
8713
8915
  // src/commands/MakeEntityCommand.ts
8714
8916
  var import_pluralize = __toESM(require_pluralize(), 1);
8715
- import { basename as basename3, join as join14 } from "path";
8716
- import { decorator as decorator13 } from "@ooneex/command";
8717
- import { TerminalLogger as TerminalLogger13 } from "@ooneex/logger";
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";
8718
8920
  import { toPascalCase as toPascalCase8, toSnakeCase as toSnakeCase2 } from "@ooneex/utils";
8719
8921
 
8720
8922
  // src/templates/entity.test.txt
@@ -8878,28 +9080,28 @@ class MakeEntityCommand {
8878
9080
  if (module) {
8879
9081
  await ensureModule(module);
8880
9082
  }
8881
- const base = module ? join14("modules", module) : ".";
8882
- const entitiesLocalDir = join14(base, "src", "entities");
8883
- const entitiesDir = join14(process.cwd(), entitiesLocalDir);
8884
- 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`);
8885
9087
  await Bun.write(filePath, content);
8886
9088
  const testContent = entity_test_default.replace(/{{NAME}}/g, name);
8887
- const testsLocalDir = join14(base, "tests", "entities");
8888
- const testsDir = join14(process.cwd(), testsLocalDir);
8889
- 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`);
8890
9092
  await Bun.write(testFilePath, testContent);
8891
9093
  const modulePascalName = module ? toPascalCase8(module) : toPascalCase8(basename3(process.cwd()));
8892
- const modulePath = join14(process.cwd(), base, "src", `${modulePascalName}Module.ts`);
9094
+ const modulePath = join17(process.cwd(), base, "src", `${modulePascalName}Module.ts`);
8893
9095
  if (await Bun.file(modulePath).exists()) {
8894
9096
  await this.addToModule(modulePath, name);
8895
9097
  }
8896
- const logger = new TerminalLogger13;
8897
- 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, {
8898
9100
  showTimestamp: false,
8899
9101
  showArrow: false,
8900
9102
  useSymbol: true
8901
9103
  });
8902
- logger.success(`${join14(testsLocalDir, name)}Entity.spec.ts created successfully`, undefined, {
9104
+ logger.success(`${join17(testsLocalDir, name)}Entity.spec.ts created successfully`, undefined, {
8903
9105
  showTimestamp: false,
8904
9106
  showArrow: false,
8905
9107
  useSymbol: true
@@ -8907,12 +9109,12 @@ class MakeEntityCommand {
8907
9109
  }
8908
9110
  }
8909
9111
  MakeEntityCommand = __legacyDecorateClassTS([
8910
- decorator13.command()
9112
+ decorator17.command()
8911
9113
  ], MakeEntityCommand);
8912
9114
  // src/commands/MakeLoggerCommand.ts
8913
- import { join as join15 } from "path";
8914
- import { decorator as decorator14 } from "@ooneex/command";
8915
- 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";
8916
9118
  import { toPascalCase as toPascalCase9 } from "@ooneex/utils";
8917
9119
 
8918
9120
  // src/templates/logger.test.txt
@@ -9017,28 +9219,28 @@ class MakeLoggerCommand {
9017
9219
  if (module) {
9018
9220
  await ensureModule(module);
9019
9221
  }
9020
- const base = module ? join15("modules", module) : ".";
9021
- const loggerLocalDir = join15(base, "src", "loggers");
9022
- const loggerDir = join15(process.cwd(), loggerLocalDir);
9023
- 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`);
9024
9226
  await Bun.write(filePath, content);
9025
9227
  const testContent = logger_test_default.replace(/{{NAME}}/g, name);
9026
- const testsLocalDir = join15(base, "tests", "loggers");
9027
- const testsDir = join15(process.cwd(), testsLocalDir);
9028
- 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`);
9029
9231
  await Bun.write(testFilePath, testContent);
9030
- const logger = new TerminalLogger14;
9031
- 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, {
9032
9234
  showTimestamp: false,
9033
9235
  showArrow: false,
9034
9236
  useSymbol: true
9035
9237
  });
9036
- logger.success(`${join15(testsLocalDir, name)}Logger.spec.ts created successfully`, undefined, {
9238
+ logger.success(`${join18(testsLocalDir, name)}Logger.spec.ts created successfully`, undefined, {
9037
9239
  showTimestamp: false,
9038
9240
  showArrow: false,
9039
9241
  useSymbol: true
9040
9242
  });
9041
- const packageJsonPath = join15(process.cwd(), "package.json");
9243
+ const packageJsonPath = join18(process.cwd(), "package.json");
9042
9244
  const packageJson = await Bun.file(packageJsonPath).json();
9043
9245
  const deps = packageJson.dependencies ?? {};
9044
9246
  const devDeps = packageJson.devDependencies ?? {};
@@ -9053,12 +9255,12 @@ class MakeLoggerCommand {
9053
9255
  }
9054
9256
  }
9055
9257
  MakeLoggerCommand = __legacyDecorateClassTS([
9056
- decorator14.command()
9258
+ decorator18.command()
9057
9259
  ], MakeLoggerCommand);
9058
9260
  // src/commands/MakeMailerCommand.ts
9059
- import { join as join16 } from "path";
9060
- import { decorator as decorator15 } from "@ooneex/command";
9061
- 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";
9062
9264
  import { toPascalCase as toPascalCase10 } from "@ooneex/utils";
9063
9265
 
9064
9266
  // src/templates/mailer/mailer.test.txt
@@ -9156,43 +9358,43 @@ class MakeMailerCommand {
9156
9358
  if (module) {
9157
9359
  await ensureModule(module);
9158
9360
  }
9159
- const base = module ? join16("modules", module) : ".";
9160
- const mailerLocalDir = join16(base, "src", "mailers");
9161
- const mailerDir = join16(process.cwd(), mailerLocalDir);
9162
- const mailerFilePath = join16(mailerDir, `${name}Mailer.ts`);
9163
- 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`);
9164
9366
  await Bun.write(mailerFilePath, mailerContent);
9165
9367
  await Bun.write(templateFilePath, templateContent);
9166
9368
  const mailerTestContent = mailer_test_default.replace(/{{NAME}}/g, name);
9167
9369
  const templateTestContent = mailer_template_test_default.replace(/{{NAME}}/g, name);
9168
- const testsLocalDir = join16(base, "tests", "mailers");
9169
- const testsDir = join16(process.cwd(), testsLocalDir);
9170
- const mailerTestFilePath = join16(testsDir, `${name}Mailer.spec.ts`);
9171
- 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`);
9172
9374
  await Bun.write(mailerTestFilePath, mailerTestContent);
9173
9375
  await Bun.write(templateTestFilePath, templateTestContent);
9174
- const logger = new TerminalLogger15;
9175
- 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, {
9176
9378
  showTimestamp: false,
9177
9379
  showArrow: false,
9178
9380
  useSymbol: true
9179
9381
  });
9180
- logger.success(`${join16(mailerLocalDir, name)}MailerTemplate.tsx created successfully`, undefined, {
9382
+ logger.success(`${join19(mailerLocalDir, name)}MailerTemplate.tsx created successfully`, undefined, {
9181
9383
  showTimestamp: false,
9182
9384
  showArrow: false,
9183
9385
  useSymbol: true
9184
9386
  });
9185
- logger.success(`${join16(testsLocalDir, name)}Mailer.spec.ts created successfully`, undefined, {
9387
+ logger.success(`${join19(testsLocalDir, name)}Mailer.spec.ts created successfully`, undefined, {
9186
9388
  showTimestamp: false,
9187
9389
  showArrow: false,
9188
9390
  useSymbol: true
9189
9391
  });
9190
- logger.success(`${join16(testsLocalDir, name)}MailerTemplate.spec.ts created successfully`, undefined, {
9392
+ logger.success(`${join19(testsLocalDir, name)}MailerTemplate.spec.ts created successfully`, undefined, {
9191
9393
  showTimestamp: false,
9192
9394
  showArrow: false,
9193
9395
  useSymbol: true
9194
9396
  });
9195
- const packageJsonPath = join16(process.cwd(), "package.json");
9397
+ const packageJsonPath = join19(process.cwd(), "package.json");
9196
9398
  const packageJson = await Bun.file(packageJsonPath).json();
9197
9399
  const deps = packageJson.dependencies ?? {};
9198
9400
  const devDeps = packageJson.devDependencies ?? {};
@@ -9207,12 +9409,12 @@ class MakeMailerCommand {
9207
9409
  }
9208
9410
  }
9209
9411
  MakeMailerCommand = __legacyDecorateClassTS([
9210
- decorator15.command()
9412
+ decorator19.command()
9211
9413
  ], MakeMailerCommand);
9212
9414
  // src/commands/MakeMiddlewareCommand.ts
9213
- import { basename as basename4, join as join17 } from "path";
9214
- import { decorator as decorator16 } from "@ooneex/command";
9215
- 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";
9216
9418
  import { toPascalCase as toPascalCase11 } from "@ooneex/utils";
9217
9419
 
9218
9420
  // src/templates/middleware.socket.txt
@@ -9301,33 +9503,33 @@ class MakeMiddlewareCommand {
9301
9503
  if (module) {
9302
9504
  await ensureModule(module);
9303
9505
  }
9304
- const base = module ? join17("modules", module) : ".";
9305
- const middlewareLocalDir = join17(base, "src", "middlewares");
9306
- const middlewareDir = join17(process.cwd(), middlewareLocalDir);
9307
- 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`);
9308
9510
  await Bun.write(filePath, content);
9309
9511
  const testContent = middleware_test_default.replace(/{{NAME}}/g, name);
9310
- const testsLocalDir = join17(base, "tests", "middlewares");
9311
- const testsDir = join17(process.cwd(), testsLocalDir);
9312
- 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`);
9313
9515
  await Bun.write(testFilePath, testContent);
9314
9516
  const modulePascalName = module ? toPascalCase11(module) : toPascalCase11(basename4(process.cwd()));
9315
- const modulePath = join17(process.cwd(), base, "src", `${modulePascalName}Module.ts`);
9517
+ const modulePath = join20(process.cwd(), base, "src", `${modulePascalName}Module.ts`);
9316
9518
  if (await Bun.file(modulePath).exists()) {
9317
9519
  await this.addToModule(modulePath, name);
9318
9520
  }
9319
- const logger = new TerminalLogger16;
9320
- 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, {
9321
9523
  showTimestamp: false,
9322
9524
  showArrow: false,
9323
9525
  useSymbol: true
9324
9526
  });
9325
- logger.success(`${join17(testsLocalDir, name)}Middleware.spec.ts created successfully`, undefined, {
9527
+ logger.success(`${join20(testsLocalDir, name)}Middleware.spec.ts created successfully`, undefined, {
9326
9528
  showTimestamp: false,
9327
9529
  showArrow: false,
9328
9530
  useSymbol: true
9329
9531
  });
9330
- const packageJsonPath = join17(process.cwd(), "package.json");
9532
+ const packageJsonPath = join20(process.cwd(), "package.json");
9331
9533
  const packageJson = await Bun.file(packageJsonPath).json();
9332
9534
  const deps = packageJson.dependencies ?? {};
9333
9535
  const devDeps = packageJson.devDependencies ?? {};
@@ -9342,21 +9544,21 @@ class MakeMiddlewareCommand {
9342
9544
  }
9343
9545
  }
9344
9546
  MakeMiddlewareCommand = __legacyDecorateClassTS([
9345
- decorator16.command()
9547
+ decorator20.command()
9346
9548
  ], MakeMiddlewareCommand);
9347
9549
  // src/commands/MakeMigrationCommand.ts
9348
- import { join as join18 } from "path";
9349
- import { decorator as decorator17 } from "@ooneex/command";
9350
- 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";
9351
9553
  import { migrationCreate } from "@ooneex/migrations";
9352
9554
 
9353
9555
  // src/templates/module/migration.up.txt
9354
9556
  var migration_up_default = `#!/usr/bin/env bun
9355
9557
 
9356
- import { migrationUp } from "@ooneex/migrations";
9558
+ import { up } from "@ooneex/migrations";
9357
9559
  import "@/migrations/migrations";
9358
9560
 
9359
- await migrationUp({
9561
+ await up({
9360
9562
  databaseUrl: Bun.env.DATABASE_URL,
9361
9563
  tableName: "migrations",
9362
9564
  });
@@ -9375,41 +9577,29 @@ class MakeMigrationCommand {
9375
9577
  if (module) {
9376
9578
  await ensureModule(module);
9377
9579
  }
9378
- const base = module ? join18("modules", module) : ".";
9580
+ const base = module ? join21("modules", module) : ".";
9379
9581
  const { migrationPath: filePath } = await migrationCreate({
9380
- migrationsDir: join18(base, "src", "migrations"),
9381
- testsDir: join18(base, "tests", "migrations")
9582
+ migrationsDir: join21(base, "src", "migrations"),
9583
+ testsDir: join21(base, "tests", "migrations")
9382
9584
  });
9383
- const binMigrationUpPath = join18(process.cwd(), base, "bin", "migration", "up.ts");
9585
+ const binMigrationUpPath = join21(process.cwd(), base, "bin", "migration", "up.ts");
9384
9586
  const binMigrationUpFile = Bun.file(binMigrationUpPath);
9385
9587
  if (!await binMigrationUpFile.exists()) {
9386
9588
  await Bun.write(binMigrationUpPath, migration_up_default);
9387
9589
  }
9388
- const packageJsonPath = join18(process.cwd(), "package.json");
9389
- const packageJsonFile = Bun.file(packageJsonPath);
9390
- if (await packageJsonFile.exists()) {
9391
- const packageJson = await packageJsonFile.json();
9392
- packageJson.scripts = packageJson.scripts || {};
9393
- packageJson.scripts["migration:up"] = "bun ./bin/migration/up.ts";
9394
- await Bun.write(packageJsonPath, JSON.stringify(packageJson, null, 2));
9395
- }
9396
- const logger = new TerminalLogger17;
9590
+ const packageJsonPath = join21(process.cwd(), base, "package.json");
9591
+ const logger = new TerminalLogger20;
9397
9592
  logger.success(`${filePath} created successfully`, undefined, {
9398
9593
  showTimestamp: false,
9399
9594
  showArrow: false,
9400
9595
  useSymbol: true
9401
9596
  });
9402
- logger.info("Run 'bun run migration:up' to execute migrations", undefined, {
9403
- showTimestamp: false,
9404
- showArrow: true,
9405
- showLevel: false
9406
- });
9407
9597
  const pkgJson = await Bun.file(packageJsonPath).json();
9408
9598
  const deps = pkgJson.dependencies ?? {};
9409
9599
  const devDeps = pkgJson.devDependencies ?? {};
9410
9600
  if (!deps["@ooneex/migrations"] && !devDeps["@ooneex/migrations"]) {
9411
9601
  const install = Bun.spawn(["bun", "add", "--dev", "@ooneex/migrations"], {
9412
- cwd: process.cwd(),
9602
+ cwd: join21(process.cwd(), base),
9413
9603
  stdout: "ignore",
9414
9604
  stderr: "inherit"
9415
9605
  });
@@ -9418,12 +9608,12 @@ class MakeMigrationCommand {
9418
9608
  }
9419
9609
  }
9420
9610
  MakeMigrationCommand = __legacyDecorateClassTS([
9421
- decorator17.command()
9611
+ decorator21.command()
9422
9612
  ], MakeMigrationCommand);
9423
9613
  // src/commands/MakePermissionCommand.ts
9424
- import { join as join19 } from "path";
9425
- import { decorator as decorator18 } from "@ooneex/command";
9426
- 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";
9427
9617
  import { toPascalCase as toPascalCase12 } from "@ooneex/utils";
9428
9618
 
9429
9619
  // src/templates/permission.test.txt
@@ -9514,28 +9704,28 @@ class MakePermissionCommand {
9514
9704
  if (module) {
9515
9705
  await ensureModule(module);
9516
9706
  }
9517
- const base = module ? join19("modules", module) : ".";
9518
- const permissionLocalDir = join19(base, "src", "permissions");
9519
- const permissionDir = join19(process.cwd(), permissionLocalDir);
9520
- 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`);
9521
9711
  await Bun.write(filePath, content);
9522
9712
  const testContent = permission_test_default.replace(/{{NAME}}/g, name);
9523
- const testsLocalDir = join19(base, "tests", "permissions");
9524
- const testsDir = join19(process.cwd(), testsLocalDir);
9525
- 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`);
9526
9716
  await Bun.write(testFilePath, testContent);
9527
- const logger = new TerminalLogger18;
9528
- 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, {
9529
9719
  showTimestamp: false,
9530
9720
  showArrow: false,
9531
9721
  useSymbol: true
9532
9722
  });
9533
- logger.success(`${join19(testsLocalDir, name)}Permission.spec.ts created successfully`, undefined, {
9723
+ logger.success(`${join22(testsLocalDir, name)}Permission.spec.ts created successfully`, undefined, {
9534
9724
  showTimestamp: false,
9535
9725
  showArrow: false,
9536
9726
  useSymbol: true
9537
9727
  });
9538
- const packageJsonPath = join19(process.cwd(), "package.json");
9728
+ const packageJsonPath = join22(process.cwd(), "package.json");
9539
9729
  const packageJson = await Bun.file(packageJsonPath).json();
9540
9730
  const deps = packageJson.dependencies ?? {};
9541
9731
  const devDeps = packageJson.devDependencies ?? {};
@@ -9550,12 +9740,12 @@ class MakePermissionCommand {
9550
9740
  }
9551
9741
  }
9552
9742
  MakePermissionCommand = __legacyDecorateClassTS([
9553
- decorator18.command()
9743
+ decorator22.command()
9554
9744
  ], MakePermissionCommand);
9555
9745
  // src/commands/MakePubSubCommand.ts
9556
- import { basename as basename5, join as join20 } from "path";
9557
- import { decorator as decorator19 } from "@ooneex/command";
9558
- 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";
9559
9749
  import { toKebabCase as toKebabCase4, toPascalCase as toPascalCase13 } from "@ooneex/utils";
9560
9750
 
9561
9751
  // src/templates/pubsub.test.txt
@@ -9663,33 +9853,33 @@ class MakePubSubCommand {
9663
9853
  if (module) {
9664
9854
  await ensureModule(module);
9665
9855
  }
9666
- const base = module ? join20("modules", module) : ".";
9667
- const pubSubLocalDir = join20(base, "src", "events");
9668
- const pubSubDir = join20(process.cwd(), pubSubLocalDir);
9669
- 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`);
9670
9860
  await Bun.write(filePath, content);
9671
9861
  const testContent = pubsub_test_default.replace(/{{NAME}}/g, name);
9672
- const testsLocalDir = join20(base, "tests", "events");
9673
- const testsDir = join20(process.cwd(), testsLocalDir);
9674
- 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`);
9675
9865
  await Bun.write(testFilePath, testContent);
9676
9866
  const modulePascalName = module ? toPascalCase13(module) : toPascalCase13(basename5(process.cwd()));
9677
- const modulePath = join20(process.cwd(), base, "src", `${modulePascalName}Module.ts`);
9867
+ const modulePath = join23(process.cwd(), base, "src", `${modulePascalName}Module.ts`);
9678
9868
  if (await Bun.file(modulePath).exists()) {
9679
9869
  await this.addToModule(modulePath, name);
9680
9870
  }
9681
- const logger = new TerminalLogger19;
9682
- 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, {
9683
9873
  showTimestamp: false,
9684
9874
  showArrow: false,
9685
9875
  useSymbol: true
9686
9876
  });
9687
- logger.success(`${join20(testsLocalDir, name)}Event.spec.ts created successfully`, undefined, {
9877
+ logger.success(`${join23(testsLocalDir, name)}Event.spec.ts created successfully`, undefined, {
9688
9878
  showTimestamp: false,
9689
9879
  showArrow: false,
9690
9880
  useSymbol: true
9691
9881
  });
9692
- const packageJsonPath = join20(process.cwd(), "package.json");
9882
+ const packageJsonPath = join23(process.cwd(), "package.json");
9693
9883
  const packageJson = await Bun.file(packageJsonPath).json();
9694
9884
  const deps = packageJson.dependencies ?? {};
9695
9885
  const devDeps = packageJson.devDependencies ?? {};
@@ -9704,13 +9894,13 @@ class MakePubSubCommand {
9704
9894
  }
9705
9895
  }
9706
9896
  MakePubSubCommand = __legacyDecorateClassTS([
9707
- decorator19.command()
9897
+ decorator23.command()
9708
9898
  ], MakePubSubCommand);
9709
9899
  // src/commands/MakeReleaseCommand.ts
9710
9900
  import { readdir } from "fs/promises";
9711
- import { join as join21 } from "path";
9712
- import { decorator as decorator20 } from "@ooneex/command";
9713
- 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";
9714
9904
  var {$ } = globalThis.Bun;
9715
9905
  var COMMIT_TYPE_TO_CATEGORY = {
9716
9906
  feat: "Added",
@@ -9733,7 +9923,7 @@ class MakeReleaseCommand {
9733
9923
  return "Release packages with version bump, changelog, and git tag";
9734
9924
  }
9735
9925
  async run() {
9736
- const logger = new TerminalLogger20;
9926
+ const logger = new TerminalLogger23;
9737
9927
  const cwd = process.cwd();
9738
9928
  const dirs = [];
9739
9929
  for (const { name, type } of [
@@ -9741,8 +9931,8 @@ class MakeReleaseCommand {
9741
9931
  { name: "modules", type: "module" }
9742
9932
  ]) {
9743
9933
  try {
9744
- const entries = await readdir(join21(cwd, name), { withFileTypes: true });
9745
- 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 })));
9746
9936
  } catch {}
9747
9937
  }
9748
9938
  const logOptions = { showTimestamp: false, showArrow: false, useSymbol: true };
@@ -9752,8 +9942,8 @@ class MakeReleaseCommand {
9752
9942
  }
9753
9943
  let releasedCount = 0;
9754
9944
  for (const dir of dirs) {
9755
- const fullDir = join21(cwd, dir.base);
9756
- const pkgJsonPath = join21(fullDir, "package.json");
9945
+ const fullDir = join24(cwd, dir.base);
9946
+ const pkgJsonPath = join24(fullDir, "package.json");
9757
9947
  const pkgJsonFile = Bun.file(pkgJsonPath);
9758
9948
  if (!await pkgJsonFile.exists()) {
9759
9949
  continue;
@@ -9771,7 +9961,7 @@ class MakeReleaseCommand {
9771
9961
  await Bun.write(pkgJsonPath, `${JSON.stringify(pkgJson, null, 2)}
9772
9962
  `);
9773
9963
  await this.updateChangelog(fullDir, newVersion, tag, commits);
9774
- 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"));
9775
9965
  await this.gitCommit(`chore(release): ${pkgJson.name}@${newVersion}`);
9776
9966
  await this.gitTag(tag, `chore(release): ${pkgJson.name}@${newVersion}`);
9777
9967
  logger.success(`${pkgJson.name}@${newVersion} released (${bumpType} bump, ${commits.length} commit(s))`, undefined, logOptions);
@@ -9868,7 +10058,7 @@ class MakeReleaseCommand {
9868
10058
  }
9869
10059
  }
9870
10060
  async updateChangelog(dir, version, tag, commits) {
9871
- const changelogPath = join21(dir, "CHANGELOG.md");
10061
+ const changelogPath = join24(dir, "CHANGELOG.md");
9872
10062
  const today = new Date().toISOString().split("T")[0];
9873
10063
  const repoUrl = await this.getRepoUrl();
9874
10064
  const grouped = new Map;
@@ -9939,12 +10129,12 @@ ${section}
9939
10129
  }
9940
10130
  }
9941
10131
  MakeReleaseCommand = __legacyDecorateClassTS([
9942
- decorator20.command()
10132
+ decorator24.command()
9943
10133
  ], MakeReleaseCommand);
9944
10134
  // src/commands/MakeRepositoryCommand.ts
9945
- import { join as join22 } from "path";
9946
- import { decorator as decorator21 } from "@ooneex/command";
9947
- 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";
9948
10138
  import { toPascalCase as toPascalCase14 } from "@ooneex/utils";
9949
10139
 
9950
10140
  // src/templates/repository.test.txt
@@ -10158,28 +10348,28 @@ class MakeRepositoryCommand {
10158
10348
  if (module) {
10159
10349
  await ensureModule(module);
10160
10350
  }
10161
- const base = module ? join22("modules", module) : ".";
10162
- const repositoriesLocalDir = join22(base, "src", "repositories");
10163
- const repositoriesDir = join22(process.cwd(), repositoriesLocalDir);
10164
- 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`);
10165
10355
  await Bun.write(filePath, content);
10166
10356
  const testContent = repository_test_default.replace(/{{NAME}}/g, name);
10167
- const testsLocalDir = join22(base, "tests", "repositories");
10168
- const testsDir = join22(process.cwd(), testsLocalDir);
10169
- 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`);
10170
10360
  await Bun.write(testFilePath, testContent);
10171
- const logger = new TerminalLogger21;
10172
- 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, {
10173
10363
  showTimestamp: false,
10174
10364
  showArrow: false,
10175
10365
  useSymbol: true
10176
10366
  });
10177
- logger.success(`${join22(testsLocalDir, name)}Repository.spec.ts created successfully`, undefined, {
10367
+ logger.success(`${join25(testsLocalDir, name)}Repository.spec.ts created successfully`, undefined, {
10178
10368
  showTimestamp: false,
10179
10369
  showArrow: false,
10180
10370
  useSymbol: true
10181
10371
  });
10182
- const packageJsonPath = join22(process.cwd(), "package.json");
10372
+ const packageJsonPath = join25(process.cwd(), "package.json");
10183
10373
  const packageJson = await Bun.file(packageJsonPath).json();
10184
10374
  const deps = packageJson.dependencies ?? {};
10185
10375
  const devDeps = packageJson.devDependencies ?? {};
@@ -10194,11 +10384,11 @@ class MakeRepositoryCommand {
10194
10384
  }
10195
10385
  }
10196
10386
  MakeRepositoryCommand = __legacyDecorateClassTS([
10197
- decorator21.command()
10387
+ decorator25.command()
10198
10388
  ], MakeRepositoryCommand);
10199
10389
  // src/commands/MakeResourceBookCommand.ts
10200
- import { join as join24 } from "path";
10201
- import { decorator as decorator23 } from "@ooneex/command";
10390
+ import { join as join27 } from "path";
10391
+ import { decorator as decorator27 } from "@ooneex/command";
10202
10392
  var {Glob } = globalThis.Bun;
10203
10393
 
10204
10394
  // src/templates/resources/book/BookEntity.txt
@@ -10894,9 +11084,9 @@ export class UpdateBookService implements IService {
10894
11084
  `;
10895
11085
 
10896
11086
  // src/commands/MakeServiceCommand.ts
10897
- import { join as join23 } from "path";
10898
- import { decorator as decorator22 } from "@ooneex/command";
10899
- 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";
10900
11090
  import { toPascalCase as toPascalCase15 } from "@ooneex/utils";
10901
11091
 
10902
11092
  // src/templates/service.test.txt
@@ -10948,28 +11138,28 @@ class MakeServiceCommand {
10948
11138
  if (module) {
10949
11139
  await ensureModule(module);
10950
11140
  }
10951
- const base = module ? join23("modules", module) : ".";
10952
- const serviceLocalDir = join23(base, "src", "services");
10953
- const serviceDir = join23(process.cwd(), serviceLocalDir);
10954
- 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`);
10955
11145
  await Bun.write(filePath, content);
10956
11146
  const testContent = service_test_default.replace(/{{NAME}}/g, name);
10957
- const testsLocalDir = join23(base, "tests", "services");
10958
- const testsDir = join23(process.cwd(), testsLocalDir);
10959
- 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`);
10960
11150
  await Bun.write(testFilePath, testContent);
10961
- const logger = new TerminalLogger22;
10962
- 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, {
10963
11153
  showTimestamp: false,
10964
11154
  showArrow: false,
10965
11155
  useSymbol: true
10966
11156
  });
10967
- logger.success(`${join23(testsLocalDir, name)}Service.spec.ts created successfully`, undefined, {
11157
+ logger.success(`${join26(testsLocalDir, name)}Service.spec.ts created successfully`, undefined, {
10968
11158
  showTimestamp: false,
10969
11159
  showArrow: false,
10970
11160
  useSymbol: true
10971
11161
  });
10972
- const packageJsonPath = join23(process.cwd(), "package.json");
11162
+ const packageJsonPath = join26(process.cwd(), "package.json");
10973
11163
  const packageJson = await Bun.file(packageJsonPath).json();
10974
11164
  const deps = packageJson.dependencies ?? {};
10975
11165
  const devDeps = packageJson.devDependencies ?? {};
@@ -10984,7 +11174,7 @@ class MakeServiceCommand {
10984
11174
  }
10985
11175
  }
10986
11176
  MakeServiceCommand = __legacyDecorateClassTS([
10987
- decorator22.command()
11177
+ decorator26.command()
10988
11178
  ], MakeServiceCommand);
10989
11179
 
10990
11180
  // src/commands/MakeResourceBookCommand.ts
@@ -10997,7 +11187,7 @@ class MakeResourceBookCommand {
10997
11187
  }
10998
11188
  async run() {
10999
11189
  const module = "book";
11000
- const base = join24("modules", module);
11190
+ const base = join27("modules", module);
11001
11191
  const makeModuleCommand = new MakeModuleCommand;
11002
11192
  await makeModuleCommand.run({ name: module, silent: true, skipMigrations: false, skipSeeds: true });
11003
11193
  const makeEntityCommand = new MakeEntityCommand;
@@ -11017,26 +11207,26 @@ class MakeResourceBookCommand {
11017
11207
  for (const controller of controllers) {
11018
11208
  await makeControllerCommand.run({ ...controller, module, isSocket: false });
11019
11209
  }
11020
- const controllersDir = join24(process.cwd(), base, "src", "controllers");
11021
- await Bun.write(join24(controllersDir, "CreateBookController.ts"), CreateBookController_default);
11022
- await Bun.write(join24(controllersDir, "GetBookController.ts"), GetBookController_default);
11023
- await Bun.write(join24(controllersDir, "ListBooksController.ts"), ListBooksController_default);
11024
- await Bun.write(join24(controllersDir, "UpdateBookController.ts"), UpdateBookController_default);
11025
- 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);
11026
11216
  const makeServiceCommand = new MakeServiceCommand;
11027
11217
  const services = ["CreateBook", "GetBook", "ListBooks", "UpdateBook", "DeleteBook"];
11028
11218
  for (const name of services) {
11029
11219
  await makeServiceCommand.run({ name, module });
11030
11220
  }
11031
- const servicesDir = join24(process.cwd(), base, "src", "services");
11032
- await Bun.write(join24(servicesDir, "CreateBookService.ts"), CreateBookService_default);
11033
- await Bun.write(join24(servicesDir, "GetBookService.ts"), GetBookService_default);
11034
- await Bun.write(join24(servicesDir, "ListBooksService.ts"), ListBooksService_default);
11035
- await Bun.write(join24(servicesDir, "UpdateBookService.ts"), UpdateBookService_default);
11036
- await Bun.write(join24(servicesDir, "DeleteBookService.ts"), DeleteBookService_default);
11037
- 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");
11038
11228
  await Bun.write(entityPath, BookEntity_default);
11039
- const migrationsDir = join24(process.cwd(), base, "src", "migrations");
11229
+ const migrationsDir = join27(process.cwd(), base, "src", "migrations");
11040
11230
  const glob = new Glob("Migration*.ts");
11041
11231
  for await (const file of glob.scan(migrationsDir)) {
11042
11232
  if (file === "migrations.ts")
@@ -11044,28 +11234,28 @@ class MakeResourceBookCommand {
11044
11234
  const name = file.replace(/\.ts$/, "");
11045
11235
  const version = name.replace("Migration", "");
11046
11236
  const content = BookMigration_default.replaceAll("{{ name }}", name).replaceAll("{{ version }}", version);
11047
- await Bun.write(join24(migrationsDir, file), content);
11237
+ await Bun.write(join27(migrationsDir, file), content);
11048
11238
  }
11049
- const repositoryPath = join24(process.cwd(), base, "src", "repositories", "BookRepository.ts");
11239
+ const repositoryPath = join27(process.cwd(), base, "src", "repositories", "BookRepository.ts");
11050
11240
  await Bun.write(repositoryPath, BookRepository_default);
11051
11241
  }
11052
11242
  }
11053
11243
  MakeResourceBookCommand = __legacyDecorateClassTS([
11054
- decorator23.command()
11244
+ decorator27.command()
11055
11245
  ], MakeResourceBookCommand);
11056
11246
  // src/commands/MakeSeedCommand.ts
11057
- import { join as join25 } from "path";
11058
- import { decorator as decorator24 } from "@ooneex/command";
11059
- 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";
11060
11250
  import { seedCreate } from "@ooneex/seeds";
11061
11251
 
11062
11252
  // src/templates/module/seed.run.txt
11063
11253
  var seed_run_default = `#!/usr/bin/env bun
11064
11254
 
11065
- import { seedRun } from "@ooneex/seeds";
11255
+ import { run } from "@ooneex/seeds";
11066
11256
  import "@/seeds/seeds";
11067
11257
 
11068
- await seedRun();
11258
+ await run();
11069
11259
  `;
11070
11260
 
11071
11261
  // src/commands/MakeSeedCommand.ts
@@ -11084,42 +11274,35 @@ class MakeSeedCommand {
11084
11274
  if (module) {
11085
11275
  await ensureModule(module);
11086
11276
  }
11087
- const base = module ? join25("modules", module) : ".";
11088
- const { seedPath: filePath } = await seedCreate({
11277
+ const base = module ? join28("modules", module) : ".";
11278
+ const { seedPath: filePath, dataPath } = await seedCreate({
11089
11279
  name,
11090
- seedsDir: join25(base, "src", "seeds"),
11091
- testsDir: join25(base, "tests", "seeds")
11280
+ seedsDir: join28(base, "src", "seeds"),
11281
+ testsDir: join28(base, "tests", "seeds")
11092
11282
  });
11093
- const binSeedRunPath = join25(process.cwd(), base, "bin", "seed", "run.ts");
11283
+ const binSeedRunPath = join28(process.cwd(), base, "bin", "seed", "run.ts");
11094
11284
  const binSeedRunFile = Bun.file(binSeedRunPath);
11095
11285
  if (!await binSeedRunFile.exists()) {
11096
11286
  await Bun.write(binSeedRunPath, seed_run_default);
11097
11287
  }
11098
- const packageJsonPath = join25(process.cwd(), "package.json");
11099
- const packageJsonFile = Bun.file(packageJsonPath);
11100
- if (await packageJsonFile.exists()) {
11101
- const packageJson = await packageJsonFile.json();
11102
- packageJson.scripts = packageJson.scripts || {};
11103
- packageJson.scripts["seed:run"] = "bun ./bin/seed/run.ts";
11104
- await Bun.write(packageJsonPath, JSON.stringify(packageJson, null, 2));
11105
- }
11106
- const logger = new TerminalLogger23;
11288
+ const packageJsonPath = join28(process.cwd(), base, "package.json");
11289
+ const logger = new TerminalLogger26;
11107
11290
  logger.success(`${filePath} created successfully`, undefined, {
11108
11291
  showTimestamp: false,
11109
11292
  showArrow: false,
11110
11293
  useSymbol: true
11111
11294
  });
11112
- logger.info("Run 'bun run seed:run' to execute seeds", undefined, {
11295
+ logger.success(`${dataPath} created successfully`, undefined, {
11113
11296
  showTimestamp: false,
11114
- showArrow: true,
11115
- showLevel: false
11297
+ showArrow: false,
11298
+ useSymbol: true
11116
11299
  });
11117
11300
  const pkgJson = await Bun.file(packageJsonPath).json();
11118
11301
  const deps = pkgJson.dependencies ?? {};
11119
11302
  const devDeps = pkgJson.devDependencies ?? {};
11120
11303
  if (!deps["@ooneex/seeds"] && !devDeps["@ooneex/seeds"]) {
11121
11304
  const install = Bun.spawn(["bun", "add", "--dev", "@ooneex/seeds"], {
11122
- cwd: process.cwd(),
11305
+ cwd: join28(process.cwd(), base),
11123
11306
  stdout: "ignore",
11124
11307
  stderr: "inherit"
11125
11308
  });
@@ -11128,12 +11311,12 @@ class MakeSeedCommand {
11128
11311
  }
11129
11312
  }
11130
11313
  MakeSeedCommand = __legacyDecorateClassTS([
11131
- decorator24.command()
11314
+ decorator28.command()
11132
11315
  ], MakeSeedCommand);
11133
11316
  // src/commands/MakeStorageCommand.ts
11134
- import { join as join26 } from "path";
11135
- import { decorator as decorator25 } from "@ooneex/command";
11136
- 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";
11137
11320
  import { toPascalCase as toPascalCase16, toSnakeCase as toSnakeCase3 } from "@ooneex/utils";
11138
11321
 
11139
11322
  // src/templates/storage.test.txt
@@ -11233,28 +11416,28 @@ class MakeStorageCommand {
11233
11416
  if (module) {
11234
11417
  await ensureModule(module);
11235
11418
  }
11236
- const base = module ? join26("modules", module) : ".";
11237
- const storageLocalDir = join26(base, "src", "storage");
11238
- const storageDir = join26(process.cwd(), storageLocalDir);
11239
- 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`);
11240
11423
  await Bun.write(filePath, content);
11241
11424
  const testContent = storage_test_default.replace(/{{NAME}}/g, name);
11242
- const testsLocalDir = join26(base, "tests", "storage");
11243
- const testsDir = join26(process.cwd(), testsLocalDir);
11244
- 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`);
11245
11428
  await Bun.write(testFilePath, testContent);
11246
- const logger = new TerminalLogger24;
11247
- 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, {
11248
11431
  showTimestamp: false,
11249
11432
  showArrow: false,
11250
11433
  useSymbol: true
11251
11434
  });
11252
- logger.success(`${join26(testsLocalDir, name)}Storage.spec.ts created successfully`, undefined, {
11435
+ logger.success(`${join29(testsLocalDir, name)}Storage.spec.ts created successfully`, undefined, {
11253
11436
  showTimestamp: false,
11254
11437
  showArrow: false,
11255
11438
  useSymbol: true
11256
11439
  });
11257
- const packageJsonPath = join26(process.cwd(), "package.json");
11440
+ const packageJsonPath = join29(process.cwd(), "package.json");
11258
11441
  const packageJson = await Bun.file(packageJsonPath).json();
11259
11442
  const deps = packageJson.dependencies ?? {};
11260
11443
  const devDeps = packageJson.devDependencies ?? {};
@@ -11269,12 +11452,12 @@ class MakeStorageCommand {
11269
11452
  }
11270
11453
  }
11271
11454
  MakeStorageCommand = __legacyDecorateClassTS([
11272
- decorator25.command()
11455
+ decorator29.command()
11273
11456
  ], MakeStorageCommand);
11274
11457
  // src/commands/MakeVectorDatabaseCommand.ts
11275
- import { join as join27 } from "path";
11276
- import { decorator as decorator26 } from "@ooneex/command";
11277
- 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";
11278
11461
  import { toPascalCase as toPascalCase17 } from "@ooneex/utils";
11279
11462
 
11280
11463
  // src/templates/vector-database.test.txt
@@ -11348,28 +11531,28 @@ class MakeVectorDatabaseCommand {
11348
11531
  if (module) {
11349
11532
  await ensureModule(module);
11350
11533
  }
11351
- const base = module ? join27("modules", module) : ".";
11352
- const vectorDatabaseLocalDir = join27(base, "src", "databases");
11353
- const vectorDatabaseDir = join27(process.cwd(), vectorDatabaseLocalDir);
11354
- 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`);
11355
11538
  await Bun.write(filePath, content);
11356
11539
  const testContent = vector_database_test_default.replace(/{{NAME}}/g, name);
11357
- const testsLocalDir = join27(base, "tests", "databases");
11358
- const testsDir = join27(process.cwd(), testsLocalDir);
11359
- 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`);
11360
11543
  await Bun.write(testFilePath, testContent);
11361
- const logger = new TerminalLogger25;
11362
- 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, {
11363
11546
  showTimestamp: false,
11364
11547
  showArrow: false,
11365
11548
  useSymbol: true
11366
11549
  });
11367
- logger.success(`${join27(testsLocalDir, name)}VectorDatabase.spec.ts created successfully`, undefined, {
11550
+ logger.success(`${join30(testsLocalDir, name)}VectorDatabase.spec.ts created successfully`, undefined, {
11368
11551
  showTimestamp: false,
11369
11552
  showArrow: false,
11370
11553
  useSymbol: true
11371
11554
  });
11372
- const packageJsonPath = join27(process.cwd(), "package.json");
11555
+ const packageJsonPath = join30(process.cwd(), "package.json");
11373
11556
  const packageJson = await Bun.file(packageJsonPath).json();
11374
11557
  const deps = packageJson.dependencies ?? {};
11375
11558
  const devDeps = packageJson.devDependencies ?? {};
@@ -11384,9 +11567,157 @@ class MakeVectorDatabaseCommand {
11384
11567
  }
11385
11568
  }
11386
11569
  MakeVectorDatabaseCommand = __legacyDecorateClassTS([
11387
- decorator26.command()
11570
+ decorator30.command()
11388
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);
11389
11720
  // src/index.ts
11390
11721
  await run();
11391
11722
 
11392
- //# debugId=813EA09F8A5BE6E864756E2164756E21
11723
+ //# debugId=1317A3FF7CEC8E3164756E2164756E21