@hazeljs/cli 0.7.9 → 0.8.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.
- 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/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.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 +161 -19
- package/dist/commands/generate-simple.test.js +8 -2
- package/dist/commands/info.test.js +1 -3
- 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 +2 -2
|
@@ -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();
|
|
@@ -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;
|
|
@@ -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,165 @@ 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: 'discovery',
|
|
138
|
+
description: 'Service discovery setup',
|
|
139
|
+
suffix: 'discovery',
|
|
140
|
+
template: templates_1.DISCOVERY_TEMPLATE,
|
|
141
|
+
nameRequired: true,
|
|
142
|
+
nextSteps: [
|
|
143
|
+
'npm install @hazeljs/discovery',
|
|
144
|
+
'Register this service as a provider in your module',
|
|
145
|
+
'Configure your discovery backend (memory, redis, consul, or kubernetes)',
|
|
146
|
+
],
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
type: 'config',
|
|
150
|
+
description: 'Config module setup',
|
|
151
|
+
suffix: 'config',
|
|
152
|
+
template: templates_1.CONFIG_TEMPLATE,
|
|
153
|
+
nameRequired: false,
|
|
154
|
+
nextSteps: [
|
|
155
|
+
'npm install @hazeljs/config',
|
|
156
|
+
'Add ConfigModule.forRoot({ envFilePath: ".env" }) to your app module imports',
|
|
157
|
+
'Create a .env file in your project root',
|
|
158
|
+
],
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
type: 'serverless',
|
|
162
|
+
description: 'Serverless handler (Lambda or Cloud Function)',
|
|
163
|
+
suffix: 'handler',
|
|
164
|
+
template: templates_1.SERVERLESS_LAMBDA_TEMPLATE,
|
|
165
|
+
alias: 'sls',
|
|
166
|
+
nameRequired: true,
|
|
167
|
+
extraOptions: ['platform'],
|
|
168
|
+
},
|
|
27
169
|
];
|
|
28
170
|
// ── Runner factory ───────────────────────────────────────────────────────────
|
|
29
171
|
class SimpleGenerator extends generator_1.Generator {
|
|
@@ -52,7 +194,7 @@ async function runSimpleGenerator(config, name, options) {
|
|
|
52
194
|
template = templates_1.SERVERLESS_CLOUD_FUNCTION_TEMPLATE;
|
|
53
195
|
}
|
|
54
196
|
const generator = new SimpleGenerator(config.suffix, template);
|
|
55
|
-
const effectiveName = config.nameRequired ? name :
|
|
197
|
+
const effectiveName = config.nameRequired ? name : name || 'app';
|
|
56
198
|
const result = await generator.generate({
|
|
57
199
|
name: effectiveName,
|
|
58
200
|
path: options.path || config.defaultPath,
|
|
@@ -87,7 +229,7 @@ function registerSimpleGenerators(generateCommand) {
|
|
|
87
229
|
}
|
|
88
230
|
cmd.action(async (nameOrOptions, maybeOptions) => {
|
|
89
231
|
const name = typeof nameOrOptions === 'string' ? nameOrOptions : '';
|
|
90
|
-
const opts = typeof nameOrOptions === 'string' ?
|
|
232
|
+
const opts = typeof nameOrOptions === 'string' ? maybeOptions || {} : nameOrOptions;
|
|
91
233
|
const result = await runSimpleGenerator(config, name, opts);
|
|
92
234
|
(0, generator_1.printGenerateResult)(result, { json: opts.json });
|
|
93
235
|
});
|
|
@@ -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');
|
package/dist/index.list.test.js
CHANGED
|
@@ -11,7 +11,13 @@ jest.mock('./commands/generate-auth', () => ({ generateAuth: jest.fn() }));
|
|
|
11
11
|
jest.mock('./commands/generate-simple', () => ({
|
|
12
12
|
registerSimpleGenerators: jest.fn(),
|
|
13
13
|
SIMPLE_GENERATORS: [
|
|
14
|
-
{
|
|
14
|
+
{
|
|
15
|
+
type: 'controller',
|
|
16
|
+
description: 'REST controller',
|
|
17
|
+
suffix: 'controller',
|
|
18
|
+
template: '',
|
|
19
|
+
nameRequired: true,
|
|
20
|
+
},
|
|
15
21
|
],
|
|
16
22
|
findSimpleGenerator: jest.fn(),
|
|
17
23
|
runSimpleGenerator: jest.fn(),
|
package/dist/index.test.js
CHANGED
|
@@ -17,7 +17,13 @@ jest.mock('./commands/generate-auth');
|
|
|
17
17
|
jest.mock('./commands/generate-simple', () => ({
|
|
18
18
|
registerSimpleGenerators: jest.fn(),
|
|
19
19
|
SIMPLE_GENERATORS: [
|
|
20
|
-
{
|
|
20
|
+
{
|
|
21
|
+
type: 'controller',
|
|
22
|
+
description: 'REST controller',
|
|
23
|
+
suffix: 'controller',
|
|
24
|
+
template: '',
|
|
25
|
+
nameRequired: true,
|
|
26
|
+
},
|
|
21
27
|
],
|
|
22
28
|
findSimpleGenerator: jest.fn(),
|
|
23
29
|
runSimpleGenerator: jest.fn(),
|
|
@@ -11,11 +11,25 @@ const generate_app_1 = require("../commands/generate-app");
|
|
|
11
11
|
const generate_simple_1 = require("../commands/generate-simple");
|
|
12
12
|
/** Complex generators that need their own files (multi-file output or custom logic) */
|
|
13
13
|
const COMPLEX_GENERATORS = [
|
|
14
|
-
{
|
|
14
|
+
{
|
|
15
|
+
type: 'app',
|
|
16
|
+
description: 'Skeleton HazelJS application (minimal template)',
|
|
17
|
+
nameRequired: true,
|
|
18
|
+
options: ['path'],
|
|
19
|
+
},
|
|
15
20
|
{ type: 'module', description: 'Module with controller, service, DTOs', nameRequired: true },
|
|
16
21
|
{ type: 'dto', description: 'Create and update DTOs', nameRequired: true },
|
|
17
|
-
{
|
|
18
|
-
|
|
22
|
+
{
|
|
23
|
+
type: 'crud',
|
|
24
|
+
description: 'Full CRUD resource (controller, service, module, DTOs)',
|
|
25
|
+
nameRequired: true,
|
|
26
|
+
options: ['route'],
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
type: 'auth',
|
|
30
|
+
description: 'Auth module (JWT guard, service, controller, DTOs)',
|
|
31
|
+
nameRequired: false,
|
|
32
|
+
},
|
|
19
33
|
];
|
|
20
34
|
/** All available generator types and their metadata (for --list). */
|
|
21
35
|
exports.GENERATOR_LIST = [
|
package/dist/utils/generator.js
CHANGED
|
@@ -93,7 +93,9 @@ function printGenerateResult(result, options) {
|
|
|
93
93
|
console.error(chalk_1.default.red(result.error ?? 'Generation failed'));
|
|
94
94
|
return;
|
|
95
95
|
}
|
|
96
|
-
const prefix = result.dryRun
|
|
96
|
+
const prefix = result.dryRun
|
|
97
|
+
? chalk_1.default.blue('[dry-run] Would create ')
|
|
98
|
+
: chalk_1.default.green('✓ Generated ');
|
|
97
99
|
for (const file of result.created) {
|
|
98
100
|
console.log(prefix + file);
|
|
99
101
|
}
|
|
@@ -75,7 +75,9 @@ describe('Generator class', () => {
|
|
|
75
75
|
path: 'src/test',
|
|
76
76
|
template: 'test template',
|
|
77
77
|
});
|
|
78
|
-
expect(mockFs.mkdirSync).toHaveBeenCalledWith(path_1.default.join(process.cwd(), 'src/test'), {
|
|
78
|
+
expect(mockFs.mkdirSync).toHaveBeenCalledWith(path_1.default.join(process.cwd(), 'src/test'), {
|
|
79
|
+
recursive: true,
|
|
80
|
+
});
|
|
79
81
|
});
|
|
80
82
|
it('should write file with rendered template', async () => {
|
|
81
83
|
await generator.generate({
|
|
@@ -31,7 +31,7 @@ exports.HAZEL_PACKAGES = [
|
|
|
31
31
|
label: 'Audit Logging (@hazeljs/audit)',
|
|
32
32
|
hint: 'import { AuditModule, ConsoleAuditTransport } from "@hazeljs/audit";\n // AuditModule.forRoot({ transports: [new ConsoleAuditTransport()] })',
|
|
33
33
|
moduleImport: "import { AuditModule, ConsoleAuditTransport } from '@hazeljs/audit';",
|
|
34
|
-
moduleExpression:
|
|
34
|
+
moduleExpression: 'AuditModule.forRoot({ transports: [new ConsoleAuditTransport()] })',
|
|
35
35
|
setupTemplate: `import { AuditModule, ConsoleAuditTransport } from '@hazeljs/audit';
|
|
36
36
|
|
|
37
37
|
// Add AuditModule.forRoot(...) to your HazelModule imports.
|
|
@@ -53,7 +53,7 @@ export const auditImports = [AuditModule.forRoot({ transports: [new ConsoleAudit
|
|
|
53
53
|
label: 'OAuth - Google/Microsoft/GitHub (@hazeljs/oauth)',
|
|
54
54
|
hint: 'import { OAuthModule } from "@hazeljs/oauth";\n // OAuthModule.forRoot({ providers: { google: {...}, microsoft: {...}, github: {...} } })',
|
|
55
55
|
moduleImport: "import { OAuthModule } from '@hazeljs/oauth';",
|
|
56
|
-
moduleExpression:
|
|
56
|
+
moduleExpression: 'OAuthModule.forRoot({ providers: { google: { clientId: process.env.GOOGLE_CLIENT_ID!, clientSecret: process.env.GOOGLE_CLIENT_SECRET!, redirectUri: process.env.OAUTH_REDIRECT_URI! } } })',
|
|
57
57
|
setupTemplate: `import { OAuthModule } from '@hazeljs/oauth';
|
|
58
58
|
|
|
59
59
|
// Example (Google). Put secrets in env vars:
|
|
@@ -154,7 +154,7 @@ export const gatewayImports = [GatewayModule];
|
|
|
154
154
|
label: 'Guardrails (@hazeljs/guardrails)',
|
|
155
155
|
hint: 'import { GuardrailsModule } from "@hazeljs/guardrails";\n // GuardrailsModule.forRoot({ redactPIIByDefault: true })',
|
|
156
156
|
moduleImport: "import { GuardrailsModule } from '@hazeljs/guardrails';",
|
|
157
|
-
moduleExpression:
|
|
157
|
+
moduleExpression: 'GuardrailsModule.forRoot({ redactPIIByDefault: true })',
|
|
158
158
|
setupTemplate: null,
|
|
159
159
|
},
|
|
160
160
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hazeljs/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Command-line interface for scaffolding and generating HazelJS applications and components",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -70,5 +70,5 @@
|
|
|
70
70
|
"type": "opencollective",
|
|
71
71
|
"url": "https://opencollective.com/hazeljs"
|
|
72
72
|
},
|
|
73
|
-
"gitHead": "
|
|
73
|
+
"gitHead": "e0ed98ca074dd4f7472816d3c32ef576900dcca6"
|
|
74
74
|
}
|