@hazeljs/cli 0.7.9 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/@template/.eslintrc.js +4 -18
  2. package/@template/README.md +4 -1
  3. package/@template-ai-native/HazelJS-AI-Native.postman_collection.json +503 -479
  4. package/@template-ai-native/README.md +19 -5
  5. package/@template-ai-native/package.json +1 -1
  6. package/@template-ai-native/prisma/seed.ts +16 -9
  7. package/@template-ai-native/src/agent/agent.controller.ts +20 -8
  8. package/@template-ai-native/src/agent/travel.controller.ts +29 -9
  9. package/@template-ai-native/src/rag/rag.controller.ts +34 -30
  10. package/README.md +35 -27
  11. package/cli-manifest.json +1 -1
  12. package/dist/commands/add.test.js +16 -3
  13. package/dist/commands/eval.d.ts +6 -0
  14. package/dist/commands/eval.js +65 -0
  15. package/dist/commands/generate-app.interactive-packages.test.js +2 -1
  16. package/dist/commands/generate-app.js +5 -4
  17. package/dist/commands/generate-app.new-command.test.js +3 -1
  18. package/dist/commands/generate-app.skeleton.test.js +2 -1
  19. package/dist/commands/generate-auth.js +31 -13
  20. package/dist/commands/generate-auth.test.js +8 -9
  21. package/dist/commands/generate-crud.test.js +7 -9
  22. package/dist/commands/generate-dto.test.js +4 -4
  23. package/dist/commands/generate-module.test.js +10 -10
  24. package/dist/commands/generate-simple.js +173 -19
  25. package/dist/commands/generate-simple.test.js +8 -2
  26. package/dist/commands/info.test.js +1 -3
  27. package/dist/commands/templates.d.ts +1 -0
  28. package/dist/commands/templates.js +35 -1
  29. package/dist/index.js +2 -0
  30. package/dist/index.list.test.js +7 -1
  31. package/dist/index.test.js +7 -1
  32. package/dist/utils/generator-registry.js +17 -3
  33. package/dist/utils/generator.js +3 -1
  34. package/dist/utils/generator.test.js +3 -1
  35. package/dist/utils/packages-registry.js +3 -3
  36. package/package.json +9 -5
@@ -19,7 +19,7 @@ describe('addCommand', () => {
19
19
  });
20
20
  it('should register the add command', () => {
21
21
  (0, add_1.addCommand)(program);
22
- const cmd = program.commands.find(c => c.name() === 'add');
22
+ const cmd = program.commands.find((c) => c.name() === 'add');
23
23
  expect(cmd).toBeDefined();
24
24
  });
25
25
  it('should install a known package', async () => {
@@ -39,8 +39,21 @@ describe('addCommand', () => {
39
39
  (0, add_1.addCommand)(program);
40
40
  // Test that all documented packages are available
41
41
  const expectedPackages = [
42
- 'ai', 'agent', 'auth', 'cache', 'config', 'cron',
43
- 'discovery', 'mcp', 'prompts', 'prisma', 'typeorm', 'rag', 'serverless', 'swagger', 'websocket'
42
+ 'ai',
43
+ 'agent',
44
+ 'auth',
45
+ 'cache',
46
+ 'config',
47
+ 'cron',
48
+ 'discovery',
49
+ 'mcp',
50
+ 'prompts',
51
+ 'prisma',
52
+ 'typeorm',
53
+ 'rag',
54
+ 'serverless',
55
+ 'swagger',
56
+ 'websocket',
44
57
  ];
45
58
  for (const pkg of expectedPackages) {
46
59
  mockExecSync.mockClear();
@@ -0,0 +1,6 @@
1
+ import { Command } from 'commander';
2
+ /**
3
+ * `hazel eval <dataset.json>` — load a golden dataset and run a placeholder pass-through runner.
4
+ * Wire your own runner in app code using @hazeljs/eval (see runGoldenDataset).
5
+ */
6
+ export declare function registerEvalCommand(program: Command): void;
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.registerEvalCommand = registerEvalCommand;
37
+ const path = __importStar(require("path"));
38
+ /**
39
+ * `hazel eval <dataset.json>` — load a golden dataset and run a placeholder pass-through runner.
40
+ * Wire your own runner in app code using @hazeljs/eval (see runGoldenDataset).
41
+ */
42
+ function registerEvalCommand(program) {
43
+ program
44
+ .command('eval')
45
+ .description('Run a golden dataset JSON through @hazeljs/eval (smoke check; supply your runner in code)')
46
+ .argument('<dataset>', 'Path to golden dataset JSON')
47
+ .option('--ci', 'Set exit code 1 when eval fails thresholds')
48
+ .action(async (dataset, opts) => {
49
+ try {
50
+ const { loadGoldenDatasetFromJson, runGoldenDataset, reportEvalForCi } = await Promise.resolve().then(() => __importStar(require('@hazeljs/eval')));
51
+ const ds = loadGoldenDatasetFromJson(path.resolve(process.cwd(), dataset));
52
+ const result = await runGoldenDataset(ds, async ({ input }) => ({
53
+ output: input,
54
+ toolCalls: [],
55
+ retrievedIds: [],
56
+ }), { minAverageScore: 0 });
57
+ reportEvalForCi(result, { exitOnFail: Boolean(opts.ci) });
58
+ }
59
+ catch (e) {
60
+ // eslint-disable-next-line no-console
61
+ console.error(e);
62
+ process.exitCode = 1;
63
+ }
64
+ });
65
+ }
@@ -68,7 +68,8 @@ describe('generateApp interactive package wiring', () => {
68
68
  const s = String(p);
69
69
  if (s.includes(path_1.default.join('@template')))
70
70
  return true; // template exists
71
- if (s.endsWith(path_1.default.join('.', 'my-app')) || s.endsWith(path_1.default.join(process.cwd(), '.', 'my-app')))
71
+ if (s.endsWith(path_1.default.join('.', 'my-app')) ||
72
+ s.endsWith(path_1.default.join(process.cwd(), '.', 'my-app')))
72
73
  return false; // dest doesn't exist
73
74
  if (s.endsWith(path_1.default.join('my-app', 'package.json')))
74
75
  return true; // updatePackageJson path
@@ -151,9 +151,7 @@ function scaffoldPackageBoilerplate(destPath, packages) {
151
151
  moduleImports.push(meta.moduleExpression);
152
152
  }
153
153
  // Generate the enhanced app.module.ts
154
- const importsSection = moduleImports.length > 0
155
- ? `\n imports: [\n ${moduleImports.join(',\n ')},\n ],`
156
- : '';
154
+ const importsSection = moduleImports.length > 0 ? `\n imports: [\n ${moduleImports.join(',\n ')},\n ],` : '';
157
155
  const appModule = `${imports.join('\n')}
158
156
 
159
157
  @HazelModule({${importsSection}
@@ -235,7 +233,10 @@ function generateApp(program) {
235
233
  type: 'checkbox',
236
234
  name: 'packages',
237
235
  message: 'Select additional HazelJS packages to install:',
238
- choices: packages_registry_1.HAZEL_PACKAGES.map((p) => ({ name: p.label, value: p.npm })),
236
+ choices: packages_registry_1.HAZEL_PACKAGES.map((p) => ({
237
+ name: p.label,
238
+ value: p.npm,
239
+ })),
239
240
  },
240
241
  ]);
241
242
  projectConfig = { ...projectConfig, ...answers };
@@ -57,7 +57,9 @@ describe('generateApp (hazel new)', () => {
57
57
  });
58
58
  const program = new commander_1.Command();
59
59
  (0, generate_app_1.generateApp)(program);
60
- await program.parseAsync(['new', 'my-app', '--dest', '.', '--skip-install', '--skip-git'], { from: 'user' });
60
+ await program.parseAsync(['new', 'my-app', '--dest', '.', '--skip-install', '--skip-git'], {
61
+ from: 'user',
62
+ });
61
63
  expect(mockFs.mkdirSync).toHaveBeenCalled();
62
64
  expect(mockFs.writeFileSync).toHaveBeenCalled();
63
65
  expect(mockExec).not.toHaveBeenCalled(); // skipped install + git
@@ -35,7 +35,8 @@ describe('runApp (skeleton app)', () => {
35
35
  // destPath doesn't exist; templatePath also doesn't exist -> fallback basic structure
36
36
  mockFs.existsSync.mockImplementation((p) => {
37
37
  const s = String(p);
38
- if (s.endsWith(path_1.default.join('.', 'my-app')) || s.endsWith(path_1.default.join(process.cwd(), '.', 'my-app')))
38
+ if (s.endsWith(path_1.default.join('.', 'my-app')) ||
39
+ s.endsWith(path_1.default.join(process.cwd(), '.', 'my-app')))
39
40
  return false;
40
41
  if (s.includes('@template'))
41
42
  return false;
@@ -22,9 +22,13 @@ export class AuthModule {}
22
22
  `;
23
23
  const AUTH_SERVICE_TEMPLATE = `import { Service, BadRequestError, UnauthorizedError } from '@hazeljs/core';
24
24
  import { JwtService } from '@hazeljs/auth';
25
+ import * as bcrypt from 'bcryptjs';
25
26
  import { RegisterDto } from './dto/register.dto';
26
27
  import { LoginDto } from './dto/login.dto';
27
28
 
29
+ /** Replace with your DB / Prisma / TypeORM repository */
30
+ const users = new Map<string, { id: string; name: string; email: string; passwordHash: string }>();
31
+
28
32
  @Service()
29
33
  export class AuthService {
30
34
  constructor(
@@ -32,38 +36,52 @@ export class AuthService {
32
36
  ) {}
33
37
 
34
38
  async register(registerDto: RegisterDto) {
35
- // TODO: Check if user already exists in your database
36
-
37
- // TODO: Hash the password (e.g., with bcryptjs)
38
- // const hashedPassword = await bcrypt.hash(registerDto.password, 10);
39
+ const email = registerDto.email.toLowerCase();
40
+ if (users.has(email)) {
41
+ throw new BadRequestError('User already exists');
42
+ }
39
43
 
40
- // TODO: Create the user in your database
44
+ const passwordHash = await bcrypt.hash(registerDto.password, 10);
41
45
  const user = {
42
46
  id: Date.now().toString(),
43
47
  name: registerDto.name,
44
- email: registerDto.email,
48
+ email,
49
+ passwordHash,
45
50
  };
51
+ users.set(email, user);
46
52
 
47
53
  const accessToken = this.jwtService.sign({
48
54
  sub: user.id,
49
55
  email: user.email,
50
56
  });
51
57
 
52
- return { user, accessToken };
58
+ return {
59
+ user: { id: user.id, name: user.name, email: user.email },
60
+ accessToken,
61
+ };
53
62
  }
54
63
 
55
64
  async login(loginDto: LoginDto) {
56
- // TODO: Find user by email in your database
57
- // TODO: Verify password with bcrypt.compare()
65
+ const email = loginDto.email.toLowerCase();
66
+ const row = users.get(email);
67
+ if (!row) {
68
+ throw new UnauthorizedError('Invalid credentials');
69
+ }
58
70
 
59
- const user = { id: '1', name: 'User', email: loginDto.email };
71
+ const ok = await bcrypt.compare(loginDto.password, row.passwordHash);
72
+ if (!ok) {
73
+ throw new UnauthorizedError('Invalid credentials');
74
+ }
60
75
 
61
76
  const accessToken = this.jwtService.sign({
62
- sub: user.id,
63
- email: user.email,
77
+ sub: row.id,
78
+ email: row.email,
64
79
  });
65
80
 
66
- return { user, accessToken };
81
+ return {
82
+ user: { id: row.id, name: row.name, email: row.email },
83
+ accessToken,
84
+ };
67
85
  }
68
86
  }
69
87
  `;
@@ -24,19 +24,18 @@ describe('generateAuth', () => {
24
24
  it('should generate all auth files', async () => {
25
25
  (0, generate_auth_1.generateAuth)(program);
26
26
  await program.parseAsync(['node', 'test', 'auth']);
27
- const writtenFiles = mockFs.writeFileSync.mock.calls.map(call => call[0]);
28
- expect(writtenFiles.some(f => f.includes('auth.module.ts'))).toBe(true);
29
- expect(writtenFiles.some(f => f.includes('auth.service.ts'))).toBe(true);
30
- expect(writtenFiles.some(f => f.includes('auth.controller.ts'))).toBe(true);
31
- expect(writtenFiles.some(f => f.includes('jwt-auth.guard.ts'))).toBe(true);
32
- expect(writtenFiles.some(f => f.includes('register.dto.ts'))).toBe(true);
33
- expect(writtenFiles.some(f => f.includes('login.dto.ts'))).toBe(true);
27
+ const writtenFiles = mockFs.writeFileSync.mock.calls.map((call) => call[0]);
28
+ expect(writtenFiles.some((f) => f.includes('auth.module.ts'))).toBe(true);
29
+ expect(writtenFiles.some((f) => f.includes('auth.service.ts'))).toBe(true);
30
+ expect(writtenFiles.some((f) => f.includes('auth.controller.ts'))).toBe(true);
31
+ expect(writtenFiles.some((f) => f.includes('jwt-auth.guard.ts'))).toBe(true);
32
+ expect(writtenFiles.some((f) => f.includes('register.dto.ts'))).toBe(true);
33
+ expect(writtenFiles.some((f) => f.includes('login.dto.ts'))).toBe(true);
34
34
  });
35
35
  it('should use @hazeljs/auth imports in guard', async () => {
36
36
  (0, generate_auth_1.generateAuth)(program);
37
37
  await program.parseAsync(['node', 'test', 'auth']);
38
- const guardContent = mockFs.writeFileSync.mock.calls
39
- .find(call => call[0].includes('jwt-auth.guard'))?.[1];
38
+ const guardContent = mockFs.writeFileSync.mock.calls.find((call) => call[0].includes('jwt-auth.guard'))?.[1];
40
39
  expect(guardContent).toContain("from '@hazeljs/auth'");
41
40
  expect(guardContent).toContain('JwtService');
42
41
  });
@@ -24,17 +24,16 @@ describe('generateCrud', () => {
24
24
  it('should generate all CRUD files', async () => {
25
25
  (0, generate_crud_1.generateCrud)(program);
26
26
  await program.parseAsync(['node', 'test', 'crud', 'product']);
27
- const writtenFiles = mockFs.writeFileSync.mock.calls.map(call => call[0]);
28
- expect(writtenFiles.some(f => f.includes('product.controller.ts'))).toBe(true);
29
- expect(writtenFiles.some(f => f.includes('product.service.ts'))).toBe(true);
30
- expect(writtenFiles.some(f => f.includes('product.dto.ts'))).toBe(true);
31
- expect(writtenFiles.some(f => f.includes('product.module.ts'))).toBe(true);
27
+ const writtenFiles = mockFs.writeFileSync.mock.calls.map((call) => call[0]);
28
+ expect(writtenFiles.some((f) => f.includes('product.controller.ts'))).toBe(true);
29
+ expect(writtenFiles.some((f) => f.includes('product.service.ts'))).toBe(true);
30
+ expect(writtenFiles.some((f) => f.includes('product.dto.ts'))).toBe(true);
31
+ expect(writtenFiles.some((f) => f.includes('product.module.ts'))).toBe(true);
32
32
  });
33
33
  it('should use @hazeljs/core imports', async () => {
34
34
  (0, generate_crud_1.generateCrud)(program);
35
35
  await program.parseAsync(['node', 'test', 'crud', 'product']);
36
- const controllerContent = mockFs.writeFileSync.mock.calls
37
- .find(call => call[0].includes('controller'))?.[1];
36
+ const controllerContent = mockFs.writeFileSync.mock.calls.find((call) => call[0].includes('controller'))?.[1];
38
37
  expect(controllerContent).toContain("from '@hazeljs/core'");
39
38
  });
40
39
  it('should support --dry-run flag', async () => {
@@ -45,8 +44,7 @@ describe('generateCrud', () => {
45
44
  it('should support custom route path', async () => {
46
45
  (0, generate_crud_1.generateCrud)(program);
47
46
  await program.parseAsync(['node', 'test', 'crud', 'product', '-r', 'api/products']);
48
- const controllerContent = mockFs.writeFileSync.mock.calls
49
- .find(call => call[0].includes('controller'))?.[1];
47
+ const controllerContent = mockFs.writeFileSync.mock.calls.find((call) => call[0].includes('controller'))?.[1];
50
48
  expect(controllerContent).toContain('api/products');
51
49
  });
52
50
  });
@@ -22,7 +22,7 @@ describe('generateDto', () => {
22
22
  });
23
23
  it('should register the dto command with alias', () => {
24
24
  (0, generate_dto_1.generateDto)(program);
25
- const cmd = program.commands.find(c => c.name() === 'dto');
25
+ const cmd = program.commands.find((c) => c.name() === 'dto');
26
26
  expect(cmd).toBeDefined();
27
27
  expect(cmd?.alias()).toBe('d');
28
28
  });
@@ -30,9 +30,9 @@ describe('generateDto', () => {
30
30
  (0, generate_dto_1.generateDto)(program);
31
31
  await program.parseAsync(['node', 'test', 'dto', 'user']);
32
32
  expect(mockFs.writeFileSync).toHaveBeenCalledTimes(2);
33
- const writtenFiles = mockFs.writeFileSync.mock.calls.map(call => call[0]);
34
- expect(writtenFiles.some(f => f.includes('user.dto.ts'))).toBe(true);
35
- expect(writtenFiles.some(f => f.includes('update-user.dto.ts'))).toBe(true);
33
+ const writtenFiles = mockFs.writeFileSync.mock.calls.map((call) => call[0]);
34
+ expect(writtenFiles.some((f) => f.includes('user.dto.ts'))).toBe(true);
35
+ expect(writtenFiles.some((f) => f.includes('update-user.dto.ts'))).toBe(true);
36
36
  });
37
37
  it('should include class-validator decorators', async () => {
38
38
  (0, generate_dto_1.generateDto)(program);
@@ -23,27 +23,27 @@ describe('generateModule', () => {
23
23
  });
24
24
  it('should register the module command with alias', () => {
25
25
  (0, generate_module_1.generateModule)(program);
26
- const cmd = program.commands.find(c => c.name() === 'module');
26
+ const cmd = program.commands.find((c) => c.name() === 'module');
27
27
  expect(cmd).toBeDefined();
28
28
  expect(cmd?.alias()).toBe('m');
29
29
  });
30
30
  it('should generate all module files', async () => {
31
31
  (0, generate_module_1.generateModule)(program);
32
32
  await program.parseAsync(['node', 'test', 'module', 'user']);
33
- const writtenFiles = mockFs.writeFileSync.mock.calls.map(call => call[0]);
34
- expect(writtenFiles.some(f => f.includes('user.module.ts'))).toBe(true);
35
- expect(writtenFiles.some(f => f.includes('user.controller.ts'))).toBe(true);
36
- expect(writtenFiles.some(f => f.includes('user.service.ts'))).toBe(true);
37
- expect(writtenFiles.some(f => f.includes('create-user.dto.ts'))).toBe(true);
38
- expect(writtenFiles.some(f => f.includes('update-user.dto.ts'))).toBe(true);
33
+ const writtenFiles = mockFs.writeFileSync.mock.calls.map((call) => call[0]);
34
+ expect(writtenFiles.some((f) => f.includes('user.module.ts'))).toBe(true);
35
+ expect(writtenFiles.some((f) => f.includes('user.controller.ts'))).toBe(true);
36
+ expect(writtenFiles.some((f) => f.includes('user.service.ts'))).toBe(true);
37
+ expect(writtenFiles.some((f) => f.includes('create-user.dto.ts'))).toBe(true);
38
+ expect(writtenFiles.some((f) => f.includes('update-user.dto.ts'))).toBe(true);
39
39
  });
40
40
  it('should use @hazeljs/core imports in all generated files', async () => {
41
41
  (0, generate_module_1.generateModule)(program);
42
42
  await program.parseAsync(['node', 'test', 'module', 'user']);
43
- const writtenContents = mockFs.writeFileSync.mock.calls.map(call => call[1]);
44
- const moduleContent = writtenContents.find(c => c.includes('HazelModule'));
43
+ const writtenContents = mockFs.writeFileSync.mock.calls.map((call) => call[1]);
44
+ const moduleContent = writtenContents.find((c) => c.includes('HazelModule'));
45
45
  expect(moduleContent).toContain("from '@hazeljs/core'");
46
- const controllerContent = writtenContents.find(c => c.includes('Controller'));
46
+ const controllerContent = writtenContents.find((c) => c.includes('Controller'));
47
47
  expect(controllerContent).toContain("from '@hazeljs/core'");
48
48
  });
49
49
  it('should support --dry-run flag', async () => {
@@ -7,23 +7,177 @@ exports.findSimpleGenerator = findSimpleGenerator;
7
7
  const generator_1 = require("../utils/generator");
8
8
  const templates_1 = require("./templates");
9
9
  exports.SIMPLE_GENERATORS = [
10
- { type: 'controller', description: 'REST controller', suffix: 'controller', template: templates_1.CONTROLLER_TEMPLATE, alias: 'c', nameRequired: true },
11
- { type: 'service', description: 'Service class', suffix: 'service', template: templates_1.SERVICE_TEMPLATE, alias: 's', nameRequired: true },
12
- { type: 'guard', description: 'Guard (e.g. auth)', suffix: 'guard', template: templates_1.GUARD_TEMPLATE, alias: 'gu', nameRequired: true },
13
- { type: 'interceptor', description: 'Interceptor', suffix: 'interceptor', template: templates_1.INTERCEPTOR_TEMPLATE, alias: 'i', nameRequired: true },
14
- { type: 'middleware', description: 'Middleware', suffix: 'middleware', template: templates_1.MIDDLEWARE_TEMPLATE, alias: 'mw', defaultPath: 'src/middleware', nameRequired: true, nextSteps: ['Import the middleware in your module or apply it globally.'] },
15
- { type: 'pipe', description: 'Validation/transform pipe', suffix: 'pipe', template: templates_1.PIPE_TEMPLATE, nameRequired: true },
16
- { type: 'filter', description: 'Exception filter', suffix: 'filter', template: templates_1.EXCEPTION_FILTER_TEMPLATE, alias: 'f', nameRequired: true },
17
- { type: 'repository', description: 'Prisma repository', suffix: 'repository', template: templates_1.REPOSITORY_TEMPLATE, alias: 'repo', nameRequired: true },
18
- { type: 'gateway', description: 'WebSocket gateway', suffix: 'gateway', template: templates_1.WEBSOCKET_GATEWAY_TEMPLATE, alias: 'ws', nameRequired: true },
19
- { type: 'ai-service', description: 'AI service with decorators', suffix: 'ai-service', template: templates_1.AI_SERVICE_TEMPLATE, alias: 'ai', nameRequired: true },
20
- { type: 'agent', description: 'AI agent with @Agent and @Tool', suffix: 'agent', template: templates_1.AGENT_TEMPLATE, nameRequired: true, extraData: (name) => ({ description: `A ${name} agent` }) },
21
- { type: 'cache', description: 'Cache service with decorators', suffix: 'cache', template: templates_1.CACHE_SERVICE_TEMPLATE, nameRequired: true, nextSteps: ['npm install @hazeljs/cache', 'Add CacheModule to your module imports', 'Configure the cache strategy (memory, redis, or multi-tier)'] },
22
- { type: 'cron', description: 'Cron/scheduled job service', suffix: 'cron', template: templates_1.CRON_SERVICE_TEMPLATE, alias: 'job', nameRequired: true, nextSteps: ['npm install @hazeljs/cron', 'Add CronModule to your module imports', 'Register this service as a provider'] },
23
- { type: 'rag', description: 'RAG (Retrieval-Augmented Generation) service', suffix: 'rag', template: templates_1.RAG_SERVICE_TEMPLATE, nameRequired: true, nextSteps: ['npm install @hazeljs/rag', 'Register this service as a provider in your module', 'Configure your embedding provider and vector store'] },
24
- { type: 'discovery', description: 'Service discovery setup', suffix: 'discovery', template: templates_1.DISCOVERY_TEMPLATE, nameRequired: true, nextSteps: ['npm install @hazeljs/discovery', 'Register this service as a provider in your module', 'Configure your discovery backend (memory, redis, consul, or kubernetes)'] },
25
- { type: 'config', description: 'Config module setup', suffix: 'config', template: templates_1.CONFIG_TEMPLATE, nameRequired: false, nextSteps: ['npm install @hazeljs/config', 'Add ConfigModule.forRoot({ envFilePath: ".env" }) to your app module imports', 'Create a .env file in your project root'] },
26
- { type: 'serverless', description: 'Serverless handler (Lambda or Cloud Function)', suffix: 'handler', template: templates_1.SERVERLESS_LAMBDA_TEMPLATE, alias: 'sls', nameRequired: true, extraOptions: ['platform'] },
10
+ {
11
+ type: 'controller',
12
+ description: 'REST controller',
13
+ suffix: 'controller',
14
+ template: templates_1.CONTROLLER_TEMPLATE,
15
+ alias: 'c',
16
+ nameRequired: true,
17
+ },
18
+ {
19
+ type: 'service',
20
+ description: 'Service class',
21
+ suffix: 'service',
22
+ template: templates_1.SERVICE_TEMPLATE,
23
+ alias: 's',
24
+ nameRequired: true,
25
+ },
26
+ {
27
+ type: 'guard',
28
+ description: 'Guard (e.g. auth)',
29
+ suffix: 'guard',
30
+ template: templates_1.GUARD_TEMPLATE,
31
+ alias: 'gu',
32
+ nameRequired: true,
33
+ },
34
+ {
35
+ type: 'interceptor',
36
+ description: 'Interceptor',
37
+ suffix: 'interceptor',
38
+ template: templates_1.INTERCEPTOR_TEMPLATE,
39
+ alias: 'i',
40
+ nameRequired: true,
41
+ },
42
+ {
43
+ type: 'middleware',
44
+ description: 'Middleware',
45
+ suffix: 'middleware',
46
+ template: templates_1.MIDDLEWARE_TEMPLATE,
47
+ alias: 'mw',
48
+ defaultPath: 'src/middleware',
49
+ nameRequired: true,
50
+ nextSteps: ['Import the middleware in your module or apply it globally.'],
51
+ },
52
+ {
53
+ type: 'pipe',
54
+ description: 'Validation/transform pipe',
55
+ suffix: 'pipe',
56
+ template: templates_1.PIPE_TEMPLATE,
57
+ nameRequired: true,
58
+ },
59
+ {
60
+ type: 'filter',
61
+ description: 'Exception filter',
62
+ suffix: 'filter',
63
+ template: templates_1.EXCEPTION_FILTER_TEMPLATE,
64
+ alias: 'f',
65
+ nameRequired: true,
66
+ },
67
+ {
68
+ type: 'repository',
69
+ description: 'Prisma repository',
70
+ suffix: 'repository',
71
+ template: templates_1.REPOSITORY_TEMPLATE,
72
+ alias: 'repo',
73
+ nameRequired: true,
74
+ },
75
+ {
76
+ type: 'gateway',
77
+ description: 'WebSocket gateway',
78
+ suffix: 'gateway',
79
+ template: templates_1.WEBSOCKET_GATEWAY_TEMPLATE,
80
+ alias: 'ws',
81
+ nameRequired: true,
82
+ },
83
+ {
84
+ type: 'ai-service',
85
+ description: 'AI service with decorators',
86
+ suffix: 'ai-service',
87
+ template: templates_1.AI_SERVICE_TEMPLATE,
88
+ alias: 'ai',
89
+ nameRequired: true,
90
+ },
91
+ {
92
+ type: 'agent',
93
+ description: 'AI agent with @Agent and @Tool',
94
+ suffix: 'agent',
95
+ template: templates_1.AGENT_TEMPLATE,
96
+ nameRequired: true,
97
+ extraData: (name) => ({ description: `A ${name} agent` }),
98
+ },
99
+ {
100
+ type: 'cache',
101
+ description: 'Cache service with decorators',
102
+ suffix: 'cache',
103
+ template: templates_1.CACHE_SERVICE_TEMPLATE,
104
+ nameRequired: true,
105
+ nextSteps: [
106
+ 'npm install @hazeljs/cache',
107
+ 'Add CacheModule to your module imports',
108
+ 'Configure the cache strategy (memory, redis, or multi-tier)',
109
+ ],
110
+ },
111
+ {
112
+ type: 'cron',
113
+ description: 'Cron/scheduled job service',
114
+ suffix: 'cron',
115
+ template: templates_1.CRON_SERVICE_TEMPLATE,
116
+ alias: 'job',
117
+ nameRequired: true,
118
+ nextSteps: [
119
+ 'npm install @hazeljs/cron',
120
+ 'Add CronModule to your module imports',
121
+ 'Register this service as a provider',
122
+ ],
123
+ },
124
+ {
125
+ type: 'rag',
126
+ description: 'RAG (Retrieval-Augmented Generation) service',
127
+ suffix: 'rag',
128
+ template: templates_1.RAG_SERVICE_TEMPLATE,
129
+ nameRequired: true,
130
+ nextSteps: [
131
+ 'npm install @hazeljs/rag',
132
+ 'Register this service as a provider in your module',
133
+ 'Configure your embedding provider and vector store',
134
+ ],
135
+ },
136
+ {
137
+ type: 'rag-pipeline',
138
+ description: 'RAG pipeline service (RAGPipeline.from + memory store)',
139
+ suffix: 'rag-pipeline',
140
+ template: templates_1.RAG_PIPELINE_TEMPLATE,
141
+ nameRequired: true,
142
+ nextSteps: [
143
+ 'npm install @hazeljs/rag',
144
+ 'Wire llm() to HazelAI or AIService',
145
+ 'For production vectors, use HazelAI persistence.rag or construct RAGPipeline with your VectorStore',
146
+ ],
147
+ },
148
+ {
149
+ type: 'discovery',
150
+ description: 'Service discovery setup',
151
+ suffix: 'discovery',
152
+ template: templates_1.DISCOVERY_TEMPLATE,
153
+ nameRequired: true,
154
+ nextSteps: [
155
+ 'npm install @hazeljs/discovery',
156
+ 'Register this service as a provider in your module',
157
+ 'Configure your discovery backend (memory, redis, consul, or kubernetes)',
158
+ ],
159
+ },
160
+ {
161
+ type: 'config',
162
+ description: 'Config module setup',
163
+ suffix: 'config',
164
+ template: templates_1.CONFIG_TEMPLATE,
165
+ nameRequired: false,
166
+ nextSteps: [
167
+ 'npm install @hazeljs/config',
168
+ 'Add ConfigModule.forRoot({ envFilePath: ".env" }) to your app module imports',
169
+ 'Create a .env file in your project root',
170
+ ],
171
+ },
172
+ {
173
+ type: 'serverless',
174
+ description: 'Serverless handler (Lambda or Cloud Function)',
175
+ suffix: 'handler',
176
+ template: templates_1.SERVERLESS_LAMBDA_TEMPLATE,
177
+ alias: 'sls',
178
+ nameRequired: true,
179
+ extraOptions: ['platform'],
180
+ },
27
181
  ];
28
182
  // ── Runner factory ───────────────────────────────────────────────────────────
29
183
  class SimpleGenerator extends generator_1.Generator {
@@ -52,7 +206,7 @@ async function runSimpleGenerator(config, name, options) {
52
206
  template = templates_1.SERVERLESS_CLOUD_FUNCTION_TEMPLATE;
53
207
  }
54
208
  const generator = new SimpleGenerator(config.suffix, template);
55
- const effectiveName = config.nameRequired ? name : (name || 'app');
209
+ const effectiveName = config.nameRequired ? name : name || 'app';
56
210
  const result = await generator.generate({
57
211
  name: effectiveName,
58
212
  path: options.path || config.defaultPath,
@@ -87,7 +241,7 @@ function registerSimpleGenerators(generateCommand) {
87
241
  }
88
242
  cmd.action(async (nameOrOptions, maybeOptions) => {
89
243
  const name = typeof nameOrOptions === 'string' ? nameOrOptions : '';
90
- const opts = typeof nameOrOptions === 'string' ? (maybeOptions || {}) : nameOrOptions;
244
+ const opts = typeof nameOrOptions === 'string' ? maybeOptions || {} : nameOrOptions;
91
245
  const result = await runSimpleGenerator(config, name, opts);
92
246
  (0, generator_1.printGenerateResult)(result, { json: opts.json });
93
247
  });
@@ -73,13 +73,19 @@ describe('generate-simple', () => {
73
73
  });
74
74
  it('handles serverless with cloud-function platform', async () => {
75
75
  const config = (0, generate_simple_1.findSimpleGenerator)('serverless');
76
- const result = await (0, generate_simple_1.runSimpleGenerator)(config, 'api', { dryRun: true, platform: 'cloud-function' });
76
+ const result = await (0, generate_simple_1.runSimpleGenerator)(config, 'api', {
77
+ dryRun: true,
78
+ platform: 'cloud-function',
79
+ });
77
80
  expect(result.ok).toBe(true);
78
81
  expect(result.created[0]).toContain('api.handler.ts');
79
82
  });
80
83
  it('generates with custom path', async () => {
81
84
  const config = (0, generate_simple_1.findSimpleGenerator)('service');
82
- const result = await (0, generate_simple_1.runSimpleGenerator)(config, 'order', { path: 'src/orders', dryRun: true });
85
+ const result = await (0, generate_simple_1.runSimpleGenerator)(config, 'order', {
86
+ path: 'src/orders',
87
+ dryRun: true,
88
+ });
83
89
  expect(result.ok).toBe(true);
84
90
  expect(result.created[0]).toContain('src/orders');
85
91
  });
@@ -47,9 +47,7 @@ describe('infoCommand', () => {
47
47
  const program = new commander_1.Command();
48
48
  (0, info_1.infoCommand)(program);
49
49
  await program.parseAsync(['info'], { from: 'user' });
50
- const out = logSpy.mock.calls
51
- .map((c) => c.map((x) => String(x)).join(' '))
52
- .join('\n');
50
+ const out = logSpy.mock.calls.map((c) => c.map((x) => String(x)).join(' ')).join('\n');
53
51
  expect(out).toContain('Project Information');
54
52
  expect(out).toContain('@hazeljs/core');
55
53
  expect(out).toContain('@hazeljs/swagger');
@@ -28,6 +28,7 @@ export declare const AGENT_TEMPLATE = "import { Agent, Tool } from '@hazeljs/age
28
28
  export declare const CACHE_SERVICE_TEMPLATE = "import { Service } from '@hazeljs/core';\nimport { CacheService, Cacheable, CacheEvict } from '@hazeljs/cache';\n\n@Service()\nexport class {{className}}CacheService {\n constructor(private readonly cacheService: CacheService) {}\n\n @Cacheable({ key: '{{fileName}}:all', ttl: 60 })\n async findAll() {\n // This result will be cached for 60 seconds\n return [];\n }\n\n @Cacheable({ key: '{{fileName}}:{{=<% %>=}}#{id}<%={{ }}=%>', ttl: 300 })\n async findOne(id: string) {\n // This result will be cached for 5 minutes\n return { id };\n }\n\n @CacheEvict({ key: '{{fileName}}:all' })\n async create(data: any) {\n // Creating a new item evicts the list cache\n return data;\n }\n\n async clearAll() {\n await this.cacheService.clear();\n }\n}\n";
29
29
  export declare const CRON_SERVICE_TEMPLATE = "import { Service } from '@hazeljs/core';\nimport { Cron, CronExpression } from '@hazeljs/cron';\n\n@Service()\nexport class {{className}}CronService {\n @Cron(CronExpression.EVERY_MINUTE)\n handleEveryMinute() {\n console.log('[{{className}}Cron] Running every minute...');\n // Add your cron job logic here\n }\n\n @Cron('0 0 * * *') // Every day at midnight\n handleDaily() {\n console.log('[{{className}}Cron] Running daily task...');\n // Add your daily task logic here\n }\n\n @Cron(CronExpression.EVERY_HOUR)\n handleHourly() {\n console.log('[{{className}}Cron] Running hourly cleanup...');\n // Add your hourly task logic here\n }\n}\n";
30
30
  export declare const RAG_SERVICE_TEMPLATE = "import { Service } from '@hazeljs/core';\nimport { RAGPipeline, MemoryVectorStore } from '@hazeljs/rag';\n\n@Service()\nexport class {{className}}RagService {\n private pipeline: RAGPipeline;\n\n constructor() {\n // Initialize with a memory vector store (swap for Pinecone, Qdrant, etc. in production)\n const vectorStore = new MemoryVectorStore();\n\n this.pipeline = new RAGPipeline({\n vectorStore,\n topK: 5,\n });\n }\n\n async addDocument(content: string, metadata?: Record<string, unknown>) {\n // Add a document to the vector store for retrieval\n await this.pipeline.addDocument({\n content,\n metadata: metadata || {},\n });\n }\n\n async query(question: string) {\n // Retrieve relevant documents and generate a response\n const results = await this.pipeline.query(question);\n return results;\n }\n}\n";
31
+ export declare const RAG_PIPELINE_TEMPLATE = "import { Service } from '@hazeljs/core';\nimport { RAGPipeline } from '@hazeljs/rag';\n\n/**\n * RAG pipeline scaffold \u2014 uses {@link RAGPipeline.from} with in-memory vectors.\n * Swap persistence via HazelAI `persistence.rag` or construct {@link RAGPipeline} with Pinecone/Qdrant/Weaviate/Chroma.\n */\n@Service()\nexport class {{className}}RagPipelineService {\n private pipeline: RAGPipeline | null = null;\n\n async ensurePipeline(): Promise<RAGPipeline> {\n if (this.pipeline) return this.pipeline;\n this.pipeline = RAGPipeline.from({\n provider: 'openai',\n vectorStore: 'memory',\n topK: 5,\n chunkSize: 1000,\n chunkOverlap: 200,\n llm: async (prompt: string) => {\n // Wire to your LLM (HazelAI, AIService, or HTTP)\n return prompt;\n },\n });\n await this.pipeline.initialize();\n return this.pipeline;\n }\n\n async query(question: string) {\n const p = await this.ensurePipeline();\n return p.query(question);\n }\n}\n";
31
32
  export declare const DISCOVERY_TEMPLATE = "import { Service } from '@hazeljs/core';\nimport { ServiceRegistry, DiscoveryClient } from '@hazeljs/discovery';\n\n@Service()\nexport class {{className}}DiscoveryService {\n constructor(\n private readonly registry: ServiceRegistry,\n private readonly client: DiscoveryClient,\n ) {}\n\n async registerService() {\n await this.registry.register({\n name: '{{fileName}}-service',\n host: 'localhost',\n port: 3000,\n metadata: {\n version: '1.0.0',\n },\n });\n }\n\n async discoverService(serviceName: string) {\n const instances = await this.client.getInstances(serviceName);\n return instances;\n }\n}\n";
32
33
  export declare const CONFIG_TEMPLATE = "import { HazelModule } from '@hazeljs/core';\nimport { ConfigModule, ConfigService } from '@hazeljs/config';\n\n// Import ConfigModule.forRoot() in your app module:\n//\n// @HazelModule({\n// imports: [\n// ConfigModule.forRoot({\n// envFilePath: '.env',\n// }),\n// ],\n// })\n//\n// Then inject ConfigService wherever you need it:\n//\n// constructor(private readonly config: ConfigService) {}\n//\n// Usage:\n// this.config.get('DATABASE_URL');\n// this.config.get('PORT', '3000'); // with default value\n\nexport { ConfigModule, ConfigService };\n";
33
34
  export declare const SERVERLESS_LAMBDA_TEMPLATE = "import { createLambdaHandler } from '@hazeljs/serverless';\nimport { AppModule } from './app.module';\n\nexport const handler = createLambdaHandler(AppModule);\n";