@hazeljs/cli 0.2.0-rc.7 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/@template/src/hello.controller.ts +1 -1
  2. package/README.md +40 -3
  3. package/dist/commands/generate-agent.d.ts +2 -0
  4. package/dist/commands/generate-agent.js +13 -7
  5. package/dist/commands/generate-ai-service.d.ts +2 -0
  6. package/dist/commands/generate-ai-service.js +8 -2
  7. package/dist/commands/generate-app.d.ts +7 -0
  8. package/dist/commands/generate-app.interactive-packages.test.d.ts +1 -0
  9. package/dist/commands/generate-app.interactive-packages.test.js +107 -0
  10. package/dist/commands/generate-app.js +141 -0
  11. package/dist/commands/generate-app.new-command.test.d.ts +1 -0
  12. package/dist/commands/generate-app.new-command.test.js +86 -0
  13. package/dist/commands/generate-app.skeleton.test.d.ts +1 -0
  14. package/dist/commands/generate-app.skeleton.test.js +51 -0
  15. package/dist/commands/generate-auth.d.ts +2 -0
  16. package/dist/commands/generate-auth.js +39 -40
  17. package/dist/commands/generate-cache.d.ts +2 -0
  18. package/dist/commands/generate-cache.js +14 -12
  19. package/dist/commands/generate-config.d.ts +2 -0
  20. package/dist/commands/generate-config.js +14 -12
  21. package/dist/commands/generate-controller.d.ts +2 -0
  22. package/dist/commands/generate-controller.js +8 -2
  23. package/dist/commands/generate-cron.d.ts +2 -0
  24. package/dist/commands/generate-cron.js +14 -12
  25. package/dist/commands/generate-crud.d.ts +2 -0
  26. package/dist/commands/generate-crud.js +44 -33
  27. package/dist/commands/generate-discovery.d.ts +2 -0
  28. package/dist/commands/generate-discovery.js +14 -12
  29. package/dist/commands/generate-dto.d.ts +2 -0
  30. package/dist/commands/generate-dto.js +15 -6
  31. package/dist/commands/generate-exception-filter.d.ts +2 -0
  32. package/dist/commands/generate-exception-filter.js +8 -2
  33. package/dist/commands/generate-guard.d.ts +2 -0
  34. package/dist/commands/generate-guard.js +8 -2
  35. package/dist/commands/generate-interceptor.d.ts +2 -0
  36. package/dist/commands/generate-interceptor.js +8 -2
  37. package/dist/commands/generate-middleware.d.ts +2 -0
  38. package/dist/commands/generate-middleware.js +11 -10
  39. package/dist/commands/generate-module.d.ts +2 -0
  40. package/dist/commands/generate-module.js +35 -31
  41. package/dist/commands/generate-pipe.d.ts +2 -0
  42. package/dist/commands/generate-pipe.js +8 -2
  43. package/dist/commands/generate-rag.d.ts +2 -0
  44. package/dist/commands/generate-rag.js +14 -12
  45. package/dist/commands/generate-repository.d.ts +2 -0
  46. package/dist/commands/generate-repository.js +8 -2
  47. package/dist/commands/generate-serverless-handler.d.ts +2 -0
  48. package/dist/commands/generate-serverless-handler.js +14 -3
  49. package/dist/commands/generate-service.d.ts +2 -0
  50. package/dist/commands/generate-service.js +8 -2
  51. package/dist/commands/generate-setup.d.ts +4 -0
  52. package/dist/commands/generate-setup.js +187 -0
  53. package/dist/commands/generate-setup.test.d.ts +1 -0
  54. package/dist/commands/generate-setup.test.js +50 -0
  55. package/dist/commands/generate-websocket-gateway.d.ts +2 -0
  56. package/dist/commands/generate-websocket-gateway.js +8 -2
  57. package/dist/commands/info.test.d.ts +1 -0
  58. package/dist/commands/info.test.js +58 -0
  59. package/dist/commands/pdf-to-audio.test.d.ts +1 -0
  60. package/dist/commands/pdf-to-audio.test.js +92 -0
  61. package/dist/commands/utility-commands.test.d.ts +1 -0
  62. package/dist/commands/utility-commands.test.js +95 -0
  63. package/dist/index.js +32 -3
  64. package/dist/index.list.test.d.ts +0 -0
  65. package/dist/index.list.test.js +70 -0
  66. package/dist/utils/generator-registry.d.ts +15 -0
  67. package/dist/utils/generator-registry.js +97 -0
  68. package/dist/utils/generator-registry.test.d.ts +1 -0
  69. package/dist/utils/generator-registry.test.js +10 -0
  70. package/dist/utils/generator.d.ts +21 -1
  71. package/dist/utils/generator.js +23 -4
  72. package/dist/utils/generator.test.js +5 -4
  73. package/package.json +2 -2
@@ -1,6 +1,6 @@
1
1
  import { Controller, Get } from '@hazeljs/core';
2
2
 
3
- @Controller('/hello')
3
+ @Controller('hello')
4
4
  export class HelloController {
5
5
  @Get()
6
6
  hello() {
package/README.md CHANGED
@@ -44,13 +44,20 @@ The CLI provides commands to generate various HazelJS components, create new app
44
44
 
45
45
  ### Project Management
46
46
 
47
- #### Create New Application
47
+ #### Create new application
48
48
 
49
+ **Skeleton app (quick start, like create-next-app):**
50
+ ```bash
51
+ hazel g app <appName>
52
+ # Then: cd <appName> && npm install && npm run dev
53
+ ```
54
+
55
+ **Full setup with interactive package selection:**
49
56
  ```bash
50
57
  hazel new <appName> [options]
51
58
  ```
52
59
 
53
- Creates a new HazelJS application with optional interactive setup.
60
+ Creates a new HazelJS application. Use `hazel g app <name>` for a minimal skeleton; use `hazel new <name> -i` for interactive setup with optional packages.
54
61
 
55
62
  **Options:**
56
63
  - `-d, --dest <path>` - Destination path (default: current directory)
@@ -209,8 +216,35 @@ Or using the shorter alias:
209
216
  hazel g <component> <name> [options]
210
217
  ```
211
218
 
219
+ #### Generation options
220
+
221
+ - **One pattern:** `hazel g <type> <name> [--path <path>] [--dry-run] [--json]`
222
+ - **List types:** `hazel g --list` — list all generator types
223
+ - **List as JSON:** `hazel g --list --list-json` — output `{ "generators": [ ... ] }`
224
+ - **Result as JSON:** add `--json` to any generator to get `{ "ok", "created", "nextSteps" }` on stdout
225
+ - **Stable options:** `-p, --path`, `--dry-run`, and `--json` work the same for every generator.
226
+
227
+ ```bash
228
+ # See what you can generate
229
+ hazel g --list
230
+ hazel g --list --list-json
231
+
232
+ # Generate with JSON result
233
+ hazel g controller users --json
234
+ hazel g crud product --path src/products --json
235
+
236
+ # Dry run (no files written)
237
+ hazel g module orders --dry-run
238
+ ```
239
+
212
240
  ### Available Generators
213
241
 
242
+ #### Skeleton app
243
+ - `app` - Generate a skeleton HazelJS application (minimal template, like create-next-app). Use `hazel g app my-app` then `cd my-app && npm install && npm run dev`.
244
+
245
+ #### Package setup
246
+ - `setup` / `st` - Generate a minimal setup starter file for a HazelJS package (e.g. `hazel g setup swagger`).
247
+
214
248
  #### Core Components
215
249
  - `controller` / `c` - Generate a new controller
216
250
  - `service` / `s` - Generate a new service
@@ -232,6 +266,8 @@ hazel g <component> <name> [options]
232
266
  ### Generator Options
233
267
 
234
268
  - `-p, --path <path>` - Specify the path where the component should be generated (default: 'src')
269
+ - `--dry-run` - Preview files without writing them
270
+ - `--json` - Output result as JSON (created paths and next steps)
235
271
  - `-r, --route <route>` - Specify the route path (for CRUD generator)
236
272
  - `--platform <platform>` - For serverless, specify platform: `lambda` or `cloud-function` (default: 'lambda')
237
273
 
@@ -532,7 +568,8 @@ hazel start [-d] [-p <port>] # Start application
532
568
  hazel test [pattern] [-w] [-c] # Run tests
533
569
 
534
570
  # Code Generation (alias: g)
535
- hazel g crud <name> # Complete CRUD resource
571
+ hazel g app <name> # Skeleton application
572
+ hazel g crud <name> # Complete CRUD resource
536
573
  hazel g controller <name> # Controller
537
574
  hazel g service <name> # Service
538
575
  hazel g module <name> # Module
@@ -1,2 +1,4 @@
1
1
  import { Command } from 'commander';
2
+ import { GenerateResult, GenerateCLIOptions } from '../utils/generator';
3
+ export declare function runAgent(name: string, options: GenerateCLIOptions): Promise<GenerateResult>;
2
4
  export declare function generateAgent(program: Command): void;
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runAgent = runAgent;
3
4
  exports.generateAgent = generateAgent;
4
5
  const generator_1 = require("../utils/generator");
5
6
  const AGENT_TEMPLATE = `import { Agent, Tool } from '@hazeljs/agent';
@@ -40,19 +41,24 @@ class AgentGenerator extends generator_1.Generator {
40
41
  return AGENT_TEMPLATE;
41
42
  }
42
43
  }
44
+ async function runAgent(name, options) {
45
+ const generator = new AgentGenerator();
46
+ return generator.generate({
47
+ name,
48
+ path: options.path,
49
+ dryRun: options.dryRun,
50
+ data: { description: `A ${name} agent` },
51
+ });
52
+ }
43
53
  function generateAgent(program) {
44
54
  program
45
55
  .command('agent <name>')
46
56
  .description('Generate a new AI agent with @Agent and @Tool decorators')
47
57
  .option('-p, --path <path>', 'Path where the agent should be generated')
48
58
  .option('--dry-run', 'Preview files without writing them')
59
+ .option('--json', 'Output result as JSON')
49
60
  .action(async (name, options) => {
50
- const generator = new AgentGenerator();
51
- await generator.generate({
52
- name,
53
- path: options.path,
54
- dryRun: options.dryRun,
55
- data: { description: `A ${name} agent` },
56
- });
61
+ const result = await runAgent(name, options);
62
+ (0, generator_1.printGenerateResult)(result, { json: options.json });
57
63
  });
58
64
  }
@@ -1,2 +1,4 @@
1
1
  import { Command } from 'commander';
2
+ import { GenerateResult, GenerateCLIOptions } from '../utils/generator';
3
+ export declare function runAIService(name: string, options: GenerateCLIOptions): Promise<GenerateResult>;
2
4
  export declare function generateAIService(program: Command): void;
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runAIService = runAIService;
3
4
  exports.generateAIService = generateAIService;
4
5
  const generator_1 = require("../utils/generator");
5
6
  const AI_SERVICE_TEMPLATE = `import { Service } from '@hazeljs/core';
@@ -34,6 +35,10 @@ class AIServiceGenerator extends generator_1.Generator {
34
35
  return AI_SERVICE_TEMPLATE;
35
36
  }
36
37
  }
38
+ async function runAIService(name, options) {
39
+ const generator = new AIServiceGenerator();
40
+ return generator.generate({ name, path: options.path, dryRun: options.dryRun });
41
+ }
37
42
  function generateAIService(program) {
38
43
  program
39
44
  .command('ai-service <name>')
@@ -41,8 +46,9 @@ function generateAIService(program) {
41
46
  .alias('ai')
42
47
  .option('-p, --path <path>', 'Path where the AI service should be generated')
43
48
  .option('--dry-run', 'Preview files without writing them')
49
+ .option('--json', 'Output result as JSON')
44
50
  .action(async (name, options) => {
45
- const generator = new AIServiceGenerator();
46
- await generator.generate({ name, path: options.path, dryRun: options.dryRun });
51
+ const result = await runAIService(name, options);
52
+ (0, generator_1.printGenerateResult)(result, { json: options.json });
47
53
  });
48
54
  }
@@ -1,2 +1,9 @@
1
1
  import { Command } from 'commander';
2
+ import type { GenerateResult, GenerateCLIOptions } from '../utils/generator';
2
3
  export declare function generateApp(program: Command): void;
4
+ /** Run the skeleton app generator (used by `hazel g app <name>`). Creates a minimal app, no install/git. */
5
+ export declare function runApp(name: string, options: GenerateCLIOptions & {
6
+ path?: string;
7
+ }): Promise<GenerateResult>;
8
+ /** Register `app <name>` under the generate command (skeleton app, like create-next-app). */
9
+ export declare function registerGenerateApp(generateCommand: Command): void;
@@ -0,0 +1,107 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const fs_1 = __importDefault(require("fs"));
7
+ const path_1 = __importDefault(require("path"));
8
+ const child_process_1 = require("child_process");
9
+ const inquirer_1 = __importDefault(require("inquirer"));
10
+ const commander_1 = require("commander");
11
+ const generate_app_1 = require("./generate-app");
12
+ jest.mock('fs');
13
+ jest.mock('child_process', () => ({ execSync: jest.fn() }));
14
+ jest.mock('inquirer');
15
+ describe('generateApp interactive package wiring', () => {
16
+ const mockFs = fs_1.default;
17
+ const mockExec = child_process_1.execSync;
18
+ const mockInquirer = inquirer_1.default;
19
+ let exitSpy;
20
+ let logSpy;
21
+ beforeAll(() => {
22
+ exitSpy = jest.spyOn(process, 'exit').mockImplementation(() => undefined);
23
+ logSpy = jest.spyOn(console, 'log').mockImplementation(() => undefined);
24
+ });
25
+ afterAll(() => {
26
+ exitSpy.mockRestore();
27
+ logSpy.mockRestore();
28
+ });
29
+ beforeEach(() => {
30
+ jest.clearAllMocks();
31
+ mockExec.mockImplementation(() => undefined);
32
+ mockFs.mkdirSync.mockImplementation(() => undefined);
33
+ mockFs.copyFileSync.mockImplementation(() => undefined);
34
+ mockFs.writeFileSync.mockImplementation(() => undefined);
35
+ mockFs.readFileSync.mockReturnValue(JSON.stringify({ name: 'x', description: 'y' }));
36
+ const allPackages = [
37
+ '@hazeljs/config',
38
+ '@hazeljs/swagger',
39
+ '@hazeljs/prisma',
40
+ '@hazeljs/typeorm',
41
+ '@hazeljs/audit',
42
+ '@hazeljs/auth',
43
+ '@hazeljs/oauth',
44
+ '@hazeljs/cache',
45
+ '@hazeljs/cron',
46
+ '@hazeljs/websocket',
47
+ '@hazeljs/ai',
48
+ '@hazeljs/agent',
49
+ '@hazeljs/rag',
50
+ '@hazeljs/pdf-to-audio',
51
+ '@hazeljs/data',
52
+ '@hazeljs/event-emitter',
53
+ '@hazeljs/gateway',
54
+ '@hazeljs/graphql',
55
+ '@hazeljs/grpc',
56
+ '@hazeljs/kafka',
57
+ '@hazeljs/messaging',
58
+ '@hazeljs/ml',
59
+ ];
60
+ mockInquirer.prompt.mockResolvedValue({
61
+ description: 'My app',
62
+ author: 'Me',
63
+ license: 'Apache-2.0',
64
+ packages: allPackages,
65
+ });
66
+ // Simulate template exists and has some files/dirs
67
+ mockFs.existsSync.mockImplementation((p) => {
68
+ const s = String(p);
69
+ if (s.includes(path_1.default.join('@template')))
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')))
72
+ return false; // dest doesn't exist
73
+ if (s.endsWith(path_1.default.join('my-app', 'package.json')))
74
+ return true; // updatePackageJson path
75
+ if (s.endsWith(path_1.default.join('my-app', 'src')))
76
+ return true;
77
+ return false;
78
+ });
79
+ mockFs.readdirSync.mockImplementation((p) => {
80
+ const s = String(p);
81
+ if (s.includes('@template') && s.endsWith('@template'))
82
+ return ['package.json', 'src'];
83
+ if (s.includes('@template') && s.endsWith(path_1.default.join('@template', 'src')))
84
+ return ['index.ts', 'app.module.ts', 'hello.controller.ts'];
85
+ return [];
86
+ });
87
+ mockFs.lstatSync.mockImplementation((p) => {
88
+ const s = String(p);
89
+ if (s.endsWith('src'))
90
+ return { isDirectory: () => true };
91
+ return { isDirectory: () => false };
92
+ });
93
+ });
94
+ it('covers interactive selection and scaffolding boilerplate', async () => {
95
+ const program = new commander_1.Command();
96
+ (0, generate_app_1.generateApp)(program);
97
+ await program.parseAsync(['new', 'my-app', '--dest', '.', '--interactive'], { from: 'user' });
98
+ expect(mockInquirer.prompt).toHaveBeenCalled();
99
+ // git init + npm install should be attempted (mocked)
100
+ expect(mockExec).toHaveBeenCalled();
101
+ // boilerplate writes (app.module/index/env files) should happen
102
+ expect(mockFs.writeFileSync).toHaveBeenCalled();
103
+ expect(exitSpy).not.toHaveBeenCalled();
104
+ const out = logSpy.mock.calls.map((c) => String(c[0])).join('\n');
105
+ expect(out).toContain('Project created successfully');
106
+ });
107
+ });
@@ -4,11 +4,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.generateApp = generateApp;
7
+ exports.runApp = runApp;
8
+ exports.registerGenerateApp = registerGenerateApp;
7
9
  const fs_1 = __importDefault(require("fs"));
8
10
  const path_1 = __importDefault(require("path"));
9
11
  const child_process_1 = require("child_process");
10
12
  const chalk_1 = __importDefault(require("chalk"));
11
13
  const inquirer_1 = __importDefault(require("inquirer"));
14
+ const generator_1 = require("../utils/generator");
12
15
  function copyRecursiveSync(src, dest) {
13
16
  if (fs_1.default.existsSync(src)) {
14
17
  fs_1.default.mkdirSync(dest, { recursive: true });
@@ -34,6 +37,94 @@ function updatePackageJson(destPath, appName, description) {
34
37
  fs_1.default.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
35
38
  }
36
39
  }
40
+ /** Create a minimal skeleton app at destPath (template copy + package.json). No git, no install. */
41
+ function createSkeletonAtDest(destPath, appName, description) {
42
+ const templatePath = path_1.default.join(__dirname, '../../@template');
43
+ if (fs_1.default.existsSync(templatePath)) {
44
+ copyRecursiveSync(templatePath, destPath);
45
+ updatePackageJson(destPath, appName, description);
46
+ }
47
+ else {
48
+ fs_1.default.mkdirSync(destPath, { recursive: true });
49
+ fs_1.default.mkdirSync(path_1.default.join(destPath, 'src'), { recursive: true });
50
+ const packageJson = {
51
+ name: appName,
52
+ version: '0.1.0',
53
+ description,
54
+ main: 'dist/index.js',
55
+ scripts: {
56
+ build: 'tsc',
57
+ start: 'node dist/index.js',
58
+ dev: 'ts-node-dev --respawn --transpile-only src/index.ts',
59
+ test: 'jest',
60
+ lint: 'eslint "src/**/*.ts"',
61
+ 'lint:fix': 'eslint "src/**/*.ts" --fix',
62
+ format: 'prettier --write "src/**/*.ts"',
63
+ },
64
+ dependencies: { '@hazeljs/core': '^0.2.0', 'reflect-metadata': '^0.2.2' },
65
+ devDependencies: {
66
+ '@types/jest': '^29.5.12',
67
+ '@types/node': '^20.0.0',
68
+ '@typescript-eslint/eslint-plugin': '^8.18.2',
69
+ '@typescript-eslint/parser': '^8.18.2',
70
+ eslint: '^8.56.0',
71
+ 'eslint-config-prettier': '^9.1.0',
72
+ 'eslint-plugin-prettier': '^5.1.3',
73
+ jest: '^29.7.0',
74
+ prettier: '^3.2.5',
75
+ 'ts-jest': '^29.1.2',
76
+ 'ts-node-dev': '^2.0.0',
77
+ typescript: '^5.3.3',
78
+ },
79
+ };
80
+ fs_1.default.writeFileSync(path_1.default.join(destPath, 'package.json'), JSON.stringify(packageJson, null, 2));
81
+ const indexContent = `import 'reflect-metadata';
82
+ import { HazelApp, HazelModule, Controller, Get } from '@hazeljs/core';
83
+
84
+ @Controller('/')
85
+ export class AppController {
86
+ @Get()
87
+ hello() {
88
+ return { message: 'Hello from HazelJS!' };
89
+ }
90
+ }
91
+
92
+ @HazelModule({
93
+ controllers: [AppController],
94
+ })
95
+ export class AppModule {}
96
+
97
+ async function bootstrap() {
98
+ const app = new HazelApp(AppModule);
99
+ app.enableCors({ origin: '*', methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'] });
100
+ const port = parseInt(process.env.PORT || '3000', 10);
101
+ await app.listen(port);
102
+ }
103
+
104
+ bootstrap();
105
+ `;
106
+ fs_1.default.writeFileSync(path_1.default.join(destPath, 'src', 'index.ts'), indexContent);
107
+ const tsConfig = {
108
+ compilerOptions: {
109
+ target: 'ES2020',
110
+ module: 'commonjs',
111
+ lib: ['ES2020'],
112
+ outDir: './dist',
113
+ rootDir: './src',
114
+ strict: true,
115
+ esModuleInterop: true,
116
+ skipLibCheck: true,
117
+ forceConsistentCasingInFileNames: true,
118
+ experimentalDecorators: true,
119
+ emitDecoratorMetadata: true,
120
+ },
121
+ include: ['src/**/*'],
122
+ exclude: ['node_modules', 'dist'],
123
+ };
124
+ fs_1.default.writeFileSync(path_1.default.join(destPath, 'tsconfig.json'), JSON.stringify(tsConfig, null, 2));
125
+ fs_1.default.writeFileSync(path_1.default.join(destPath, '.gitignore'), 'node_modules/\ndist/\n.env\n.DS_Store\ncoverage/\n*.log\n');
126
+ }
127
+ }
37
128
  function scaffoldPackageBoilerplate(destPath, packages) {
38
129
  const srcPath = path_1.default.join(destPath, 'src');
39
130
  // Build up enhanced app.module.ts imports based on selected packages
@@ -446,3 +537,53 @@ coverage/
446
537
  }
447
538
  });
448
539
  }
540
+ /** Run the skeleton app generator (used by `hazel g app <name>`). Creates a minimal app, no install/git. */
541
+ async function runApp(name, options) {
542
+ const parentDir = options.path || '.';
543
+ const destPath = path_1.default.join(process.cwd(), parentDir, name);
544
+ if (options.dryRun) {
545
+ return {
546
+ ok: true,
547
+ created: [destPath],
548
+ dryRun: true,
549
+ nextSteps: [`cd ${name}`, 'npm install', 'npm run dev'],
550
+ };
551
+ }
552
+ if (fs_1.default.existsSync(destPath)) {
553
+ return {
554
+ ok: false,
555
+ created: [],
556
+ error: `Destination already exists: ${destPath}`,
557
+ };
558
+ }
559
+ try {
560
+ createSkeletonAtDest(destPath, name, 'A HazelJS application');
561
+ return {
562
+ ok: true,
563
+ created: [destPath],
564
+ nextSteps: [`cd ${name}`, 'npm install', 'npm run dev'],
565
+ };
566
+ }
567
+ catch (error) {
568
+ return {
569
+ ok: false,
570
+ created: [],
571
+ error: error instanceof Error ? error.message : String(error),
572
+ };
573
+ }
574
+ }
575
+ /** Register `app <name>` under the generate command (skeleton app, like create-next-app). */
576
+ function registerGenerateApp(generateCommand) {
577
+ generateCommand
578
+ .command('app <name>')
579
+ .description('Generate a skeleton HazelJS application (minimal template, no install)')
580
+ .option('-p, --path <path>', 'Parent directory for the app', '.')
581
+ .option('--dry-run', 'Preview without writing files')
582
+ .option('--json', 'Output result as JSON')
583
+ .action(async (name, options) => {
584
+ const result = await runApp(name, options);
585
+ (0, generator_1.printGenerateResult)(result, { json: options.json });
586
+ if (!result.ok)
587
+ process.exit(1);
588
+ });
589
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const fs_1 = __importDefault(require("fs"));
7
+ const child_process_1 = require("child_process");
8
+ const inquirer_1 = __importDefault(require("inquirer"));
9
+ const commander_1 = require("commander");
10
+ const generate_app_1 = require("./generate-app");
11
+ jest.mock('fs');
12
+ jest.mock('child_process', () => ({ execSync: jest.fn() }));
13
+ jest.mock('inquirer');
14
+ describe('generateApp (hazel new)', () => {
15
+ const mockFs = fs_1.default;
16
+ const mockExec = child_process_1.execSync;
17
+ const mockInquirer = inquirer_1.default;
18
+ let exitSpy;
19
+ let logSpy;
20
+ let errSpy;
21
+ beforeAll(() => {
22
+ exitSpy = jest.spyOn(process, 'exit').mockImplementation(() => undefined);
23
+ logSpy = jest.spyOn(console, 'log').mockImplementation(() => undefined);
24
+ errSpy = jest.spyOn(console, 'error').mockImplementation(() => undefined);
25
+ });
26
+ afterAll(() => {
27
+ exitSpy.mockRestore();
28
+ logSpy.mockRestore();
29
+ errSpy.mockRestore();
30
+ });
31
+ beforeEach(() => {
32
+ jest.clearAllMocks();
33
+ mockExec.mockImplementation(() => undefined);
34
+ mockFs.mkdirSync.mockImplementation(() => undefined);
35
+ mockFs.writeFileSync.mockImplementation(() => undefined);
36
+ mockFs.copyFileSync.mockImplementation(() => undefined);
37
+ mockFs.readdirSync.mockReturnValue([]);
38
+ mockFs.lstatSync.mockReturnValue({ isDirectory: () => false });
39
+ mockInquirer.prompt.mockResolvedValue({});
40
+ });
41
+ it('exits if destination already exists', async () => {
42
+ // dest exists
43
+ mockFs.existsSync.mockImplementation((p) => String(p).endsWith('my-app'));
44
+ const program = new commander_1.Command();
45
+ (0, generate_app_1.generateApp)(program);
46
+ await program.parseAsync(['new', 'my-app', '--dest', '.'], { from: 'user' });
47
+ expect(exitSpy).toHaveBeenCalledWith(1);
48
+ });
49
+ it('creates basic structure when template missing and skip install/git', async () => {
50
+ mockFs.existsSync.mockImplementation((p) => {
51
+ const s = String(p);
52
+ if (s.includes('@template'))
53
+ return false;
54
+ if (s.endsWith('my-app'))
55
+ return false;
56
+ return false;
57
+ });
58
+ const program = new commander_1.Command();
59
+ (0, generate_app_1.generateApp)(program);
60
+ await program.parseAsync(['new', 'my-app', '--dest', '.', '--skip-install', '--skip-git'], { from: 'user' });
61
+ expect(mockFs.mkdirSync).toHaveBeenCalled();
62
+ expect(mockFs.writeFileSync).toHaveBeenCalled();
63
+ expect(mockExec).not.toHaveBeenCalled(); // skipped install + git
64
+ expect(exitSpy).not.toHaveBeenCalled();
65
+ });
66
+ it('runs interactive prompt when -i is set', async () => {
67
+ mockFs.existsSync.mockImplementation((p) => {
68
+ const s = String(p);
69
+ if (s.includes('@template'))
70
+ return false;
71
+ if (s.endsWith('my-app'))
72
+ return false;
73
+ return false;
74
+ });
75
+ mockInquirer.prompt.mockResolvedValue({
76
+ description: 'My app',
77
+ author: 'Me',
78
+ license: 'MIT',
79
+ packages: [],
80
+ });
81
+ const program = new commander_1.Command();
82
+ (0, generate_app_1.generateApp)(program);
83
+ await program.parseAsync(['new', 'my-app', '--dest', '.', '--skip-install', '--skip-git', '--interactive'], { from: 'user' });
84
+ expect(mockInquirer.prompt).toHaveBeenCalled();
85
+ });
86
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const fs_1 = __importDefault(require("fs"));
7
+ const path_1 = __importDefault(require("path"));
8
+ const generate_app_1 = require("./generate-app");
9
+ jest.mock('fs');
10
+ describe('runApp (skeleton app)', () => {
11
+ const mockFs = fs_1.default;
12
+ beforeEach(() => {
13
+ jest.clearAllMocks();
14
+ mockFs.mkdirSync.mockImplementation(() => undefined);
15
+ mockFs.writeFileSync.mockImplementation(() => undefined);
16
+ mockFs.copyFileSync.mockImplementation(() => undefined);
17
+ mockFs.readdirSync.mockReturnValue([]);
18
+ mockFs.lstatSync.mockReturnValue({ isDirectory: () => false });
19
+ });
20
+ it('should return dry-run result without touching fs', async () => {
21
+ const result = await (0, generate_app_1.runApp)('my-app', { path: '.', dryRun: true });
22
+ expect(result.ok).toBe(true);
23
+ expect(result.dryRun).toBe(true);
24
+ expect(result.created[0]).toContain(path_1.default.join(process.cwd(), 'my-app'));
25
+ expect(mockFs.mkdirSync).not.toHaveBeenCalled();
26
+ expect(mockFs.writeFileSync).not.toHaveBeenCalled();
27
+ });
28
+ it('should fail if destination already exists', async () => {
29
+ mockFs.existsSync.mockReturnValue(true);
30
+ const result = await (0, generate_app_1.runApp)('my-app', { path: '.', dryRun: false });
31
+ expect(result.ok).toBe(false);
32
+ expect(result.error).toContain('Destination already exists');
33
+ });
34
+ it('should create skeleton when destination does not exist', async () => {
35
+ // destPath doesn't exist; templatePath also doesn't exist -> fallback basic structure
36
+ mockFs.existsSync.mockImplementation((p) => {
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')))
39
+ return false;
40
+ if (s.includes('@template'))
41
+ return false;
42
+ return false;
43
+ });
44
+ const result = await (0, generate_app_1.runApp)('my-app', { path: '.', dryRun: false });
45
+ expect(result.ok).toBe(true);
46
+ expect(result.created).toHaveLength(1);
47
+ expect(mockFs.mkdirSync).toHaveBeenCalled();
48
+ expect(mockFs.writeFileSync).toHaveBeenCalled();
49
+ expect(result.nextSteps).toEqual(['cd my-app', 'npm install', 'npm run dev']);
50
+ });
51
+ });
@@ -1,2 +1,4 @@
1
1
  import { Command } from 'commander';
2
+ import { GenerateResult, GenerateCLIOptions } from '../utils/generator';
3
+ export declare function runAuth(_name: string, options: GenerateCLIOptions): Promise<GenerateResult>;
2
4
  export declare function generateAuth(command: Command): void;