@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.
- package/@template/.eslintrc.js +4 -18
- package/@template/README.md +4 -1
- package/@template-ai-native/HazelJS-AI-Native.postman_collection.json +503 -479
- package/@template-ai-native/README.md +19 -5
- package/@template-ai-native/package.json +1 -1
- package/@template-ai-native/prisma/seed.ts +16 -9
- package/@template-ai-native/src/agent/agent.controller.ts +20 -8
- package/@template-ai-native/src/agent/travel.controller.ts +29 -9
- package/@template-ai-native/src/rag/rag.controller.ts +34 -30
- package/README.md +35 -27
- package/cli-manifest.json +1 -1
- package/dist/commands/add.test.js +16 -3
- package/dist/commands/eval.d.ts +6 -0
- package/dist/commands/eval.js +65 -0
- package/dist/commands/generate-app.interactive-packages.test.js +2 -1
- package/dist/commands/generate-app.js +5 -4
- package/dist/commands/generate-app.new-command.test.js +3 -1
- package/dist/commands/generate-app.skeleton.test.js +2 -1
- package/dist/commands/generate-auth.js +31 -13
- package/dist/commands/generate-auth.test.js +8 -9
- package/dist/commands/generate-crud.test.js +7 -9
- package/dist/commands/generate-dto.test.js +4 -4
- package/dist/commands/generate-module.test.js +10 -10
- package/dist/commands/generate-simple.js +173 -19
- package/dist/commands/generate-simple.test.js +8 -2
- package/dist/commands/info.test.js +1 -3
- package/dist/commands/templates.d.ts +1 -0
- package/dist/commands/templates.js +35 -1
- package/dist/index.js +2 -0
- package/dist/index.list.test.js +7 -1
- package/dist/index.test.js +7 -1
- package/dist/utils/generator-registry.js +17 -3
- package/dist/utils/generator.js +3 -1
- package/dist/utils/generator.test.js +3 -1
- package/dist/utils/packages-registry.js +3 -3
- 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',
|
|
43
|
-
'
|
|
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')) ||
|
|
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) => ({
|
|
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'], {
|
|
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')) ||
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
+
const email = registerDto.email.toLowerCase();
|
|
40
|
+
if (users.has(email)) {
|
|
41
|
+
throw new BadRequestError('User already exists');
|
|
42
|
+
}
|
|
39
43
|
|
|
40
|
-
|
|
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
|
|
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 {
|
|
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
|
-
|
|
57
|
-
|
|
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
|
|
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:
|
|
63
|
-
email:
|
|
77
|
+
sub: row.id,
|
|
78
|
+
email: row.email,
|
|
64
79
|
});
|
|
65
80
|
|
|
66
|
-
return {
|
|
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
|
-
{
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
{
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
{
|
|
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 :
|
|
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' ?
|
|
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', {
|
|
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', {
|
|
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";
|