@fluojs/cli 1.0.0-beta.5 → 1.0.0-beta.7

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.
@@ -27,21 +27,22 @@ const PUBLISHED_RUNTIME_DEPENDENCIES = {
27
27
  nats: '^2.29.3'
28
28
  };
29
29
  const PUBLISHED_INTERNAL_DEPENDENCIES = {
30
- '@fluojs/cli': '^1.0.0-beta.3',
31
- '@fluojs/config': '^1.0.0-beta.5',
32
- '@fluojs/core': '^1.0.0-beta.3',
30
+ '@fluojs/cli': '^1.0.0-beta.7',
31
+ '@fluojs/config': '^1.0.0-beta.7',
32
+ '@fluojs/core': '^1.0.0-beta.4',
33
33
  '@fluojs/di': '^1.0.0-beta.6',
34
- '@fluojs/http': '^1.0.0-beta.9',
35
- '@fluojs/microservices': '^1.0.0-beta.3',
34
+ '@fluojs/http': '^1.0.0-beta.10',
35
+ '@fluojs/microservices': '^1.0.0-beta.5',
36
36
  '@fluojs/platform-bun': '^1.0.0-beta.6',
37
37
  '@fluojs/platform-cloudflare-workers': '^1.0.0-beta.2',
38
38
  '@fluojs/platform-deno': '^1.0.0-beta.3',
39
39
  '@fluojs/platform-express': '^1.0.0-beta.6',
40
40
  '@fluojs/platform-fastify': '^1.0.0-beta.8',
41
41
  '@fluojs/platform-nodejs': '^1.0.0-beta.4',
42
- '@fluojs/runtime': '^1.0.0-beta.9',
42
+ '@fluojs/runtime': '^1.0.0-beta.11',
43
43
  '@fluojs/testing': '^1.0.0-beta.2',
44
- '@fluojs/validation': '^1.0.0-beta.2'
44
+ '@fluojs/validation': '^1.0.0-beta.3',
45
+ '@fluojs/vite': '^1.0.0-beta.2'
45
46
  };
46
47
  function describeApplicationStarter(options) {
47
48
  if (options.runtime === 'bun') {
@@ -147,27 +148,28 @@ function createProjectScripts(bootstrapPlan) {
147
148
  switch (bootstrapPlan.profile.id) {
148
149
  case 'application-bun-bun-http':
149
150
  return {
150
- build: 'fluo build',
151
+ build: 'bun build ./src/main.ts --outdir ./dist --target bun',
151
152
  dev: 'fluo dev',
152
- start: 'fluo start',
153
+ start: 'bun dist/main.js',
153
154
  test: 'vitest run',
154
155
  'test:watch': 'vitest',
155
156
  typecheck: 'tsc -p tsconfig.json --noEmit'
156
157
  };
157
158
  case 'application-deno-deno-http':
158
159
  return {
159
- build: 'fluo build',
160
+ build: 'deno compile --allow-env --allow-net --output dist/app src/main.ts',
160
161
  dev: 'fluo dev',
161
- start: 'fluo start',
162
+ start: './dist/app',
162
163
  test: 'deno test --allow-env --allow-net',
163
164
  'test:watch': 'deno test --allow-env --allow-net --watch',
164
165
  typecheck: 'deno check src/main.ts src/app.test.ts'
165
166
  };
166
167
  case 'application-cloudflare-workers-cloudflare-workers-http':
167
168
  return {
168
- build: 'fluo build',
169
+ build: 'wrangler deploy --dry-run',
170
+ deploy: 'wrangler deploy',
169
171
  dev: 'fluo dev',
170
- start: 'fluo start',
172
+ preview: 'wrangler dev --remote --show-interactive-dev-session=false',
171
173
  test: 'vitest run',
172
174
  'test:watch': 'vitest',
173
175
  typecheck: 'tsc -p tsconfig.json --noEmit'
@@ -203,6 +205,12 @@ function createPublishedDevDependencies(bootstrapPlan) {
203
205
  if (bootstrapPlan.profile.id === 'application-deno-deno-http') {
204
206
  return {};
205
207
  }
208
+ if (bootstrapPlan.profile.id === 'application-bun-bun-http') {
209
+ return {
210
+ ...PUBLISHED_DEV_DEPENDENCIES,
211
+ '@types/bun': '^1.2.5'
212
+ };
213
+ }
206
214
  if (bootstrapPlan.profile.id === 'application-cloudflare-workers-cloudflare-workers-http') {
207
215
  return {
208
216
  ...PUBLISHED_DEV_DEPENDENCIES,
@@ -243,7 +251,8 @@ function createProjectPackageJson(options, bootstrapPlan, releaseVersion, packag
243
251
  }
244
252
  }, null, 2);
245
253
  }
246
- function createProjectTsconfig() {
254
+ function createProjectTsconfig(bootstrapPlan) {
255
+ const types = bootstrapPlan.profile.id === 'application-bun-bun-http' ? ['node', 'bun'] : ['node'];
247
256
  return `{
248
257
  "compilerOptions": {
249
258
  "target": "ES2022",
@@ -257,7 +266,7 @@ function createProjectTsconfig() {
257
266
  "forceConsistentCasingInFileNames": true,
258
267
  "resolveJsonModule": true,
259
268
  "rootDir": "src",
260
- "types": ["node"]
269
+ "types": ${JSON.stringify(types)}
261
270
  },
262
271
  "include": ["src/**/*.ts"]
263
272
  }
@@ -285,36 +294,9 @@ function createBabelConfig() {
285
294
  `;
286
295
  }
287
296
  function createViteConfig() {
288
- return `import { transformAsync } from '@babel/core';
289
- import type { Plugin } from 'vite';
297
+ return `import { fluoDecoratorsPlugin } from '@fluojs/vite';
290
298
  import { defineConfig } from 'vite';
291
299
 
292
- function fluoDecoratorsPlugin(): Plugin {
293
- return {
294
- name: 'fluo-babel-decorators',
295
- async transform(code: string, id: string) {
296
- if (!id.endsWith('.ts') || id.includes('.test.')) {
297
- return null;
298
- }
299
-
300
- const result = await transformAsync(code, {
301
- babelrc: false,
302
- configFile: false,
303
- filename: id,
304
- plugins: [['@babel/plugin-proposal-decorators', { version: '2023-11' }]],
305
- presets: [['@babel/preset-typescript', { allowDeclareFields: true }]],
306
- sourceMaps: true,
307
- });
308
-
309
- if (!result?.code) {
310
- return null;
311
- }
312
-
313
- return { code: result.code, map: result.map };
314
- },
315
- };
316
- }
317
-
318
300
  export default defineConfig({
319
301
  plugins: [fluoDecoratorsPlugin()],
320
302
  build: {
@@ -359,12 +341,37 @@ dist
359
341
  coverage
360
342
  `;
361
343
  }
344
+ function createHttpPackageManagerLine(options) {
345
+ switch (options.runtime) {
346
+ case 'bun':
347
+ return `- Package manager: install/run commands can use ${options.packageManager}; ${createRunCommand(options.packageManager, 'dev')} keeps the \`fluo dev\` abstraction while defaulting to Bun's native watch loop (\`bun --watch src/main.ts\`) for fewer Node-supervised dev processes, \`fluo dev --runner fluo\` restores the Node-backed fluo restart supervisor when you need its debounce/hash reporter contract, and ${createRunCommand(options.packageManager, 'build')} plus ${createRunCommand(options.packageManager, 'start')} run Bun-native production commands (\`bun build ./src/main.ts --outdir ./dist --target bun\` then \`bun dist/main.js\`) so deployment targets do not need Node just to start the built app`;
348
+ case 'deno':
349
+ return `- Package manager: install/run commands can use ${options.packageManager}; ${createRunCommand(options.packageManager, 'dev')} keeps the \`fluo dev\` abstraction while defaulting to Deno's native watch loop (\`deno run --watch --allow-env --allow-net src/main.ts\`) for fewer Node-supervised dev processes, \`fluo dev --runner fluo\` restores the Node-backed fluo restart supervisor when you need its debounce/hash reporter contract, and ${createRunCommand(options.packageManager, 'build')} plus ${createRunCommand(options.packageManager, 'start')} run Deno-native production commands (\`deno compile --allow-env --allow-net --output dist/app src/main.ts\` then \`./dist/app\`) so deployment targets can run the compiled app without Node`;
350
+ case 'cloudflare-workers':
351
+ return `- Package manager: install/run commands can use ${options.packageManager}; ${createRunCommand(options.packageManager, 'dev')} keeps the \`fluo dev\` abstraction while defaulting to Wrangler's native dev loop (\`wrangler dev --show-interactive-dev-session=false\`) for Wrangler-owned reloads, \`fluo dev --runner fluo\` restores the Node-backed fluo restart supervisor when you need its debounce/hash reporter contract, and ${createRunCommand(options.packageManager, 'preview')} plus ${createRunCommand(options.packageManager, 'deploy')} run Wrangler-native preview/deploy commands; Wrangler tooling requires Node/npm-compatible tooling locally, but deployed Workers run on Cloudflare's isolate runtime`;
352
+ default:
353
+ return `- Package manager: install/run commands can use ${options.packageManager}; ${createRunCommand(options.packageManager, 'dev')}, ${createRunCommand(options.packageManager, 'build')}, and ${createRunCommand(options.packageManager, 'start')} delegate to \`fluo dev\`, \`fluo build\`, and \`fluo start\`, which own the generated runtime lifecycle and default \`NODE_ENV\` when unset`;
354
+ }
355
+ }
356
+ function createHttpCommandsSection(options) {
357
+ const commands = [`- Dev: ${createRunCommand(options.packageManager, 'dev')}`, `- Build: ${createRunCommand(options.packageManager, 'build')}`];
358
+ if (options.runtime === 'bun' || options.runtime === 'deno' || options.runtime === 'cloudflare-workers') {
359
+ commands.push('- Dev fluo restart fallback: fluo dev --runner fluo');
360
+ }
361
+ if (options.runtime === 'cloudflare-workers') {
362
+ commands.push(`- Preview: ${createRunCommand(options.packageManager, 'preview')}`, `- Deploy: ${createRunCommand(options.packageManager, 'deploy')}`);
363
+ } else {
364
+ commands.push(`- Start: ${createRunCommand(options.packageManager, 'start')}`);
365
+ }
366
+ commands.push(`- Typecheck: ${createRunCommand(options.packageManager, 'typecheck')}`, `- Test: ${createRunCommand(options.packageManager, 'test')}`);
367
+ return commands.join('\n');
368
+ }
362
369
  function createHttpProjectReadme(options) {
363
370
  const starter = describeApplicationStarter(options);
364
371
  const entrypointLabel = starter.entrypoint;
365
372
  const starterContract = options.runtime === 'deno' ? `\`${entrypointLabel}\` boots the selected first-class application starter: ${starter.runtimeLabel} + ${starter.platformLabel} via \`runDenoApplication(...)\`` : options.runtime === 'cloudflare-workers' ? `\`${entrypointLabel}\` exports the selected first-class application starter: ${starter.runtimeLabel} + ${starter.platformLabel} via \`createCloudflareWorkerEntrypoint(...)\`` : `\`${entrypointLabel}\` wires the selected first-class application starter: ${starter.runtimeLabel} + ${starter.platformLabel} via \`${starter.adapterFactory}(... )\``.replace('(... )', '(...)');
366
373
  const corsLine = options.runtime === 'cloudflare-workers' ? '- CORS: defaults to allowOrigin `*`; pass a `cors` option to `createCloudflareWorkerEntrypoint(..., { cors })` when you need edge-specific restrictions' : options.runtime === 'deno' ? '- CORS: defaults to allowOrigin `*`; configure it through the Deno HTTP bootstrap path before exposing the adapter in production' : `- CORS: defaults to allowOrigin '*'; pass a \`cors\` option to \`FluoFactory.create(..., { cors, adapter: ${starter.adapterFactory}(...) })\` to restrict origins`;
367
- const testingSection = options.runtime === 'deno' ? `## Official generated testing templates\n\n- \`src/app.test.ts\` — Deno-native integration-style dispatch verification for the generated runtime + starter routes.\n\nUse this test when you need confidence that the generated Deno entrypoint and module graph still agree on the same HTTP contract.` : `## Official generated testing templates\n\n- \`src/health/*.test.ts\` — unit templates for the starter-owned health slice.\n- \`src/app.test.ts\` — integration-style dispatch template for runtime + starter routes.\n- \`src/app.e2e.test.ts\` — e2e-style template powered by \`createTestApp\` from \`@fluojs/testing\`.\n- \`${createExecCommand(options.packageManager, 'fluo g repo User')}\` also adds:\n - \`src/users/user.repo.test.ts\` (unit template)\n - \`src/users/user.repo.slice.test.ts\` (slice/integration template via \`createTestingModule\`)\n\nUse unit templates for fast logic checks. Use slice/e2e templates when you need module wiring and route-level confidence.`;
374
+ const testingSection = options.runtime === 'deno' ? `## Official generated testing templates\n\n- \`src/app.test.ts\` — Deno-native integration-style dispatch verification for the generated runtime + starter routes.\n\nUse this test when you need confidence that the generated Deno entrypoint and module graph still agree on the same HTTP contract.` : `## Official generated testing templates\n\n- \`src/greeting/*.test.ts\` — unit templates for the starter-owned greeting slice.\n- \`src/app.test.ts\` — integration-style dispatch template for runtime + starter routes.\n- \`src/app.e2e.test.ts\` — e2e-style template powered by \`createTestApp\` from \`@fluojs/testing\`.\n- \`${createExecCommand(options.packageManager, 'fluo g repo User')}\` also adds:\n - \`src/users/user.repo.test.ts\` (unit template)\n - \`src/users/user.repo.slice.test.ts\` (slice/integration template via \`createTestingModule\`)\n\nUse unit templates for fast logic checks. Use slice/e2e templates when you need module wiring and route-level confidence.`;
368
375
  return `# ${options.projectName}
369
376
 
370
377
  Generated by @fluojs/cli.
@@ -372,20 +379,16 @@ Generated by @fluojs/cli.
372
379
  - Starter contract: ${starterContract}
373
380
  - Default baseline: when you omit \`--platform\`, \`fluo new\` still generates the Node.js + Fastify HTTP starter by default
374
381
  - Broader runtime/adapter package coverage is documented in the fluo docs and package READMEs; this generated starter intentionally describes only the wired starter path above
375
- - Package manager: install/run commands can use ${options.packageManager}; ${createRunCommand(options.packageManager, 'dev')}, ${createRunCommand(options.packageManager, 'build')}, and ${createRunCommand(options.packageManager, 'start')} delegate to \`fluo dev\`, \`fluo build\`, and \`fluo start\`, which own the generated runtime lifecycle and default \`NODE_ENV\` when unset
382
+ ${createHttpPackageManagerLine(options)}
376
383
  - Runtime dependency set: generated manifest entries match the selected runtime contract instead of inheriting the Node-only starter recipe
377
384
  ${corsLine}
378
385
  - Observability: /health and /ready endpoints are included by default
379
386
  - Runtime path: bootstrapApplication -> handler mapping -> dispatcher -> middleware -> guard -> interceptor -> controller
380
- - Naming policy: runtime module entrypoints use governed canonical names (\`forRoot(...)\`, optional \`forRootAsync(...)\`, \`register(...)\`, \`forFeature(...)\`); helper/builders stay \`create*\` (for example \`createHealthModule()\`, \`createTestingModule(...)\`)
387
+ - Naming policy: runtime module entrypoints use governed canonical names (\`forRoot(...)\`, optional \`forRootAsync(...)\`, \`register(...)\`, \`forFeature(...)\`); helper/builders stay \`create*\` (for example \`createTestingModule(...)\`)
381
388
 
382
389
  ## Commands
383
390
 
384
- - Dev: ${createRunCommand(options.packageManager, 'dev')}
385
- - Start: ${createRunCommand(options.packageManager, 'start')}
386
- - Build: ${createRunCommand(options.packageManager, 'build')}
387
- - Typecheck: ${createRunCommand(options.packageManager, 'typecheck')}
388
- - Test: ${createRunCommand(options.packageManager, 'test')}
391
+ ${createHttpCommandsSection(options)}
389
392
 
390
393
  ## Generator example
391
394
 
@@ -548,30 +551,26 @@ function createAppFile(options) {
548
551
  const importSuffix = options.runtime === 'deno' ? '.ts' : '';
549
552
  if (options.runtime === 'cloudflare-workers') {
550
553
  return `import { Global, Module } from '@fluojs/core';
551
- import { createHealthModule } from '@fluojs/runtime';
552
-
553
- import { HealthModule } from './health/health.module';
554
+ import { HealthModule } from '@fluojs/runtime';
554
555
 
555
- const RuntimeHealthModule = createHealthModule();
556
+ import { GreetingModule } from './greeting/greeting.module';
556
557
 
557
558
  @Global()
558
559
  @Module({
559
560
  imports: [
560
- HealthModule,
561
- RuntimeHealthModule,
561
+ GreetingModule,
562
+ HealthModule.forRoot(),
562
563
  ],
563
564
  })
564
565
  export class AppModule {}
565
566
  `;
566
567
  }
567
- const processEnvValue = options.runtime === 'bun' ? 'Bun.env' : options.runtime === 'deno' ? 'Deno.env.toObject()' : 'process.env';
568
+ const processEnvValue = options.runtime === 'deno' ? 'Deno.env.toObject()' : 'process.env';
568
569
  return `import { Global, Module } from '@fluojs/core';
569
570
  import { ConfigModule } from '@fluojs/config';
570
- import { createHealthModule } from '@fluojs/runtime';
571
+ import { HealthModule } from '@fluojs/runtime';
571
572
 
572
- import { HealthModule } from './health/health.module${importSuffix}';
573
-
574
- const RuntimeHealthModule = createHealthModule();
573
+ import { GreetingModule } from './greeting/greeting.module${importSuffix}';
575
574
 
576
575
  @Global()
577
576
  @Module({
@@ -580,134 +579,134 @@ const RuntimeHealthModule = createHealthModule();
580
579
  envFile: '.env',
581
580
  processEnv: ${processEnvValue},
582
581
  }),
583
- HealthModule,
584
- RuntimeHealthModule,
582
+ GreetingModule,
583
+ HealthModule.forRoot(),
585
584
  ],
586
585
  })
587
586
  export class AppModule {}
588
587
  `;
589
588
  }
590
- function createHealthResponseDtoFile() {
591
- return `export class HealthResponseDto {
592
- ok!: boolean;
593
- service!: string;
589
+ function createGreetingResponseDtoFile() {
590
+ return `export class GreetingResponseDto {
591
+ message!: string;
592
+ framework!: 'fluo';
593
+ project!: string;
594
594
  }
595
595
  `;
596
596
  }
597
- function createHealthRepoFile(projectName, importSuffix = '') {
598
- return `import type { HealthResponseDto } from './health.response.dto${importSuffix}';
597
+ function createGreetingRepoFile(projectName, importSuffix = '') {
598
+ return `import type { GreetingResponseDto } from './greeting.response.dto${importSuffix}';
599
599
 
600
- export class HealthRepo {
601
- findHealth(): HealthResponseDto {
600
+ export class GreetingRepo {
601
+ findGreeting(): GreetingResponseDto {
602
602
  return {
603
- ok: true,
604
- service: ${JSON.stringify(projectName)},
603
+ message: 'Hello from fluo',
604
+ framework: 'fluo',
605
+ project: ${JSON.stringify(projectName)},
605
606
  };
606
607
  }
607
608
  }
608
609
  `;
609
610
  }
610
- function createHealthRepoTestFile() {
611
+ function createGreetingRepoTestFile() {
611
612
  return `import { describe, expect, it } from 'vitest';
612
613
 
613
- import { HealthRepo } from './health.repo';
614
+ import { GreetingRepo } from './greeting.repo';
614
615
 
615
- describe('HealthRepo', () => {
616
- it('returns health data', () => {
617
- const repo = new HealthRepo();
618
- expect(repo.findHealth()).toEqual({ ok: true, service: expect.any(String) });
616
+ describe('GreetingRepo', () => {
617
+ it('returns greeting data', () => {
618
+ const repo = new GreetingRepo();
619
+ expect(repo.findGreeting()).toEqual({ message: 'Hello from fluo', framework: 'fluo', project: expect.any(String) });
619
620
  });
620
621
  });
621
622
  `;
622
623
  }
623
- function createHealthServiceFile(importSuffix = '') {
624
+ function createGreetingServiceFile(importSuffix = '') {
624
625
  return `import { Inject } from '@fluojs/core';
625
- import type { HealthResponseDto } from './health.response.dto${importSuffix}';
626
+ import type { GreetingResponseDto } from './greeting.response.dto${importSuffix}';
626
627
 
627
- import { HealthRepo } from './health.repo${importSuffix}';
628
+ import { GreetingRepo } from './greeting.repo${importSuffix}';
628
629
 
629
- @Inject(HealthRepo)
630
- export class HealthService {
631
- constructor(private readonly repo: HealthRepo) {}
630
+ @Inject(GreetingRepo)
631
+ export class GreetingService {
632
+ constructor(private readonly repo: GreetingRepo) {}
632
633
 
633
- getHealth(): HealthResponseDto {
634
- return this.repo.findHealth();
634
+ getGreeting(): GreetingResponseDto {
635
+ return this.repo.findGreeting();
635
636
  }
636
637
  }
637
638
  `;
638
639
  }
639
- function createHealthServiceTestFile() {
640
+ function createGreetingServiceTestFile() {
640
641
  return `import { describe, expect, it } from 'vitest';
641
642
 
642
- import { HealthService } from './health.service';
643
- import { HealthRepo } from './health.repo';
643
+ import { GreetingService } from './greeting.service';
644
+ import { GreetingRepo } from './greeting.repo';
644
645
 
645
- class FakeHealthRepo {
646
- findHealth() {
647
- return { ok: true, service: 'test' };
646
+ class FakeGreetingRepo {
647
+ findGreeting() {
648
+ return { message: 'Hello from fluo', framework: 'fluo' as const, project: 'test' };
648
649
  }
649
650
  }
650
651
 
651
- describe('HealthService', () => {
652
+ describe('GreetingService', () => {
652
653
  it('delegates to the repo', () => {
653
- const service = new HealthService(new FakeHealthRepo() as HealthRepo);
654
- expect(service.getHealth()).toEqual({ ok: true, service: 'test' });
654
+ const service = new GreetingService(new FakeGreetingRepo() as GreetingRepo);
655
+ expect(service.getGreeting()).toEqual({ message: 'Hello from fluo', framework: 'fluo', project: 'test' });
655
656
  });
656
657
  });
657
658
  `;
658
659
  }
659
- function createHealthControllerFile(importSuffix = '') {
660
- return `import { ensureMetadataSymbol, Inject } from '@fluojs/core';
660
+ function createGreetingControllerFile(importSuffix = '') {
661
+ return `import { Inject } from '@fluojs/core';
661
662
  import { Controller, Get } from '@fluojs/http';
662
663
 
663
- import { HealthService } from './health.service${importSuffix}';
664
- import { HealthResponseDto } from './health.response.dto${importSuffix}';
665
-
666
- ensureMetadataSymbol();
664
+ import { GreetingService } from './greeting.service${importSuffix}';
665
+ import { GreetingResponseDto } from './greeting.response.dto${importSuffix}';
667
666
 
668
- @Inject(HealthService)
669
- @Controller('/health-info')
670
- export class HealthController {
671
- constructor(private readonly service: HealthService) {}
667
+ @Inject(GreetingService)
668
+ @Controller('/greeting')
669
+ export class GreetingController {
670
+ constructor(private readonly service: GreetingService) {}
672
671
 
673
672
  @Get('/')
674
- getHealth(): HealthResponseDto {
675
- return this.service.getHealth();
673
+ getGreeting(): GreetingResponseDto {
674
+ return this.service.getGreeting();
676
675
  }
677
676
  }
678
677
  `;
679
678
  }
680
- function createHealthControllerTestFile() {
679
+ function createGreetingControllerTestFile() {
681
680
  return `import { describe, expect, it } from 'vitest';
682
681
 
683
- import { HealthController } from './health.controller';
682
+ import { GreetingController } from './greeting.controller';
684
683
 
685
- class FakeHealthService {
686
- getHealth() {
687
- return { ok: true, service: 'test' };
684
+ class FakeGreetingService {
685
+ getGreeting() {
686
+ return { message: 'Hello from fluo', framework: 'fluo' as const, project: 'test' };
688
687
  }
689
688
  }
690
689
 
691
- describe('HealthController', () => {
690
+ describe('GreetingController', () => {
692
691
  it('delegates to the service', () => {
693
- const controller = new HealthController(new FakeHealthService() as never);
694
- expect(controller.getHealth()).toEqual({ ok: true, service: 'test' });
692
+ const controller = new GreetingController(new FakeGreetingService() as never);
693
+ expect(controller.getGreeting()).toEqual({ message: 'Hello from fluo', framework: 'fluo', project: 'test' });
695
694
  });
696
695
  });
697
696
  `;
698
697
  }
699
- function createHealthModuleFile(importSuffix = '') {
698
+ function createGreetingModuleFile(importSuffix = '') {
700
699
  return `import { Module } from '@fluojs/core';
701
700
 
702
- import { HealthController } from './health.controller${importSuffix}';
703
- import { HealthRepo } from './health.repo${importSuffix}';
704
- import { HealthService } from './health.service${importSuffix}';
701
+ import { GreetingController } from './greeting.controller${importSuffix}';
702
+ import { GreetingRepo } from './greeting.repo${importSuffix}';
703
+ import { GreetingService } from './greeting.service${importSuffix}';
705
704
 
706
705
  @Module({
707
- controllers: [HealthController],
708
- providers: [HealthRepo, HealthService],
706
+ controllers: [GreetingController],
707
+ providers: [GreetingRepo, GreetingService],
709
708
  })
710
- export class HealthModule {}
709
+ export class GreetingModule {}
711
710
  `;
712
711
  }
713
712
  function createMainFile(options) {
@@ -742,6 +741,12 @@ export default {
742
741
  `;
743
742
  }
744
743
  const portExpression = options.runtime === 'bun' ? "Bun.env.PORT ?? '3000'" : "process.env.PORT ?? '3000'";
744
+ const loggerGuidance = options.runtime === 'node' ? `
745
+ // Application logging defaults to the pretty console logger when logger is omitted.
746
+ // JSON logs are opt-in:
747
+ // import { createJsonApplicationLogger } from '@fluojs/runtime/node';
748
+ // Then pass \`logger: createJsonApplicationLogger()\` to FluoFactory.create(...).
749
+ ` : '';
745
750
  return `import { ${starter.adapterFactory} } from '${starter.packageName}';
746
751
  import { FluoFactory } from '@fluojs/runtime';
747
752
 
@@ -749,6 +754,7 @@ import { AppModule } from './app';
749
754
 
750
755
  // The generated starter wires the selected first-class fluo new application path:
751
756
  // ${starter.runtimeLabel} + ${starter.platformLabel} via ${starter.adapterFactory}(...).
757
+ ${loggerGuidance}
752
758
 
753
759
  const parsedPort = Number.parseInt(${portExpression}, 10);
754
760
  const port = Number.isFinite(parsedPort) ? parsedPort : 3000;
@@ -1428,12 +1434,12 @@ Generated by @fluojs/cli.
1428
1434
 
1429
1435
  - \`src/app.ts\` — shared application module with config, runtime health endpoints, and the TCP microservice registration.
1430
1436
  - \`src/main.ts\` — Fastify HTTP bootstrap that also starts the attached TCP microservice.
1431
- - \`src/health/*\` — starter-owned HTTP health slice.
1437
+ - \`src/greeting/*\` — starter-owned HTTP greeting slice.
1432
1438
  - \`src/math/*\` — starter-owned message handler slice that proves the microservice half of the topology.
1433
1439
 
1434
1440
  ## Official generated testing templates
1435
1441
 
1436
- - \`src/health/*.test.ts\` — unit templates for the HTTP slice.
1442
+ - \`src/greeting/*.test.ts\` — unit templates for the HTTP slice.
1437
1443
  - \`src/math/math.handler.test.ts\` — unit template for the microservice handler.
1438
1444
  - \`src/app.test.ts\` — hybrid verification template covering HTTP dispatch plus in-memory message routing with the same shared module contract.
1439
1445
 
@@ -1444,12 +1450,11 @@ function createMixedAppFile() {
1444
1450
  return `import { Global, Module } from '@fluojs/core';
1445
1451
  import { ConfigModule } from '@fluojs/config';
1446
1452
  import { MicroservicesModule, TcpMicroserviceTransport } from '@fluojs/microservices';
1447
- import { createHealthModule } from '@fluojs/runtime';
1453
+ import { HealthModule } from '@fluojs/runtime';
1448
1454
 
1449
- import { HealthModule } from './health/health.module';
1455
+ import { GreetingModule } from './greeting/greeting.module';
1450
1456
  import { MathHandler } from './math/math.handler';
1451
1457
 
1452
- const RuntimeHealthModule = createHealthModule();
1453
1458
  const parsedMicroservicePort = Number.parseInt(process.env.MICROSERVICE_PORT ?? '4000', 10);
1454
1459
  const microservicePort = Number.isFinite(parsedMicroservicePort) ? parsedMicroservicePort : 4000;
1455
1460
  const microserviceHost = process.env.MICROSERVICE_HOST ?? '127.0.0.1';
@@ -1461,8 +1466,8 @@ const microserviceHost = process.env.MICROSERVICE_HOST ?? '127.0.0.1';
1461
1466
  envFile: '.env',
1462
1467
  processEnv: process.env,
1463
1468
  }),
1464
- HealthModule,
1465
- RuntimeHealthModule,
1469
+ GreetingModule,
1470
+ HealthModule.forRoot(),
1466
1471
  MicroservicesModule.forRoot({
1467
1472
  transport: new TcpMicroserviceTransport({ host: microserviceHost, port: microservicePort }),
1468
1473
  }),
@@ -1499,9 +1504,9 @@ import {
1499
1504
  MicroservicesModule,
1500
1505
  type MicroserviceTransport,
1501
1506
  } from '@fluojs/microservices';
1502
- import { FluoFactory, createHealthModule } from '@fluojs/runtime';
1507
+ import { FluoFactory, HealthModule } from '@fluojs/runtime';
1503
1508
 
1504
- import { HealthModule } from './health/health.module';
1509
+ import { GreetingModule } from './greeting/greeting.module';
1505
1510
  import { MathHandler } from './math/math.handler';
1506
1511
 
1507
1512
  type TransportHandler = Parameters<MicroserviceTransport['listen']>[0];
@@ -1576,8 +1581,6 @@ function createResponse(): FrameworkResponse & { body?: unknown } {
1576
1581
  describe('AppModule mixed starter', () => {
1577
1582
  it('keeps HTTP routes and message handlers in one explicit topology contract', async () => {
1578
1583
  const transport = new InMemoryLoopbackTransport();
1579
- const RuntimeHealthModule = createHealthModule();
1580
-
1581
1584
  @Global()
1582
1585
  @Module({
1583
1586
  imports: [
@@ -1585,8 +1588,8 @@ describe('AppModule mixed starter', () => {
1585
1588
  envFile: '.env',
1586
1589
  processEnv: process.env,
1587
1590
  }),
1588
- HealthModule,
1589
- RuntimeHealthModule,
1591
+ GreetingModule,
1592
+ HealthModule.forRoot(),
1590
1593
  MicroservicesModule.forRoot({ transport }),
1591
1594
  ],
1592
1595
  providers: [MathHandler],
@@ -1594,13 +1597,13 @@ describe('AppModule mixed starter', () => {
1594
1597
  class TestAppModule {}
1595
1598
 
1596
1599
  const app = await FluoFactory.create(TestAppModule, {});
1597
- const healthResponse = createResponse();
1600
+ const greetingResponse = createResponse();
1598
1601
 
1599
1602
  const microservice = await app.connectMicroservice();
1600
1603
 
1601
- await Promise.all([app.startAllMicroservices(), app.dispatch(createRequest('/health-info/'), healthResponse)]);
1604
+ await Promise.all([app.startAllMicroservices(), app.dispatch(createRequest('/greeting/'), greetingResponse)]);
1602
1605
 
1603
- expect(healthResponse.body).toEqual({ ok: true, service: expect.any(String) });
1606
+ expect(greetingResponse.body).toEqual({ message: 'Hello from fluo', framework: 'fluo', project: expect.any(String) });
1604
1607
  await expect(transport.send('math.sum', { a: 20, b: 22 })).resolves.toBe(42);
1605
1608
  expect(microservice.state).toBe('ready');
1606
1609
 
@@ -1671,13 +1674,13 @@ describe('AppModule', () => {
1671
1674
  await app.close();
1672
1675
  });
1673
1676
 
1674
- it('dispatches the health-info route', async () => {
1677
+ it('dispatches the greeting route', async () => {
1675
1678
  const app = await FluoFactory.create(AppModule, {});
1676
1679
  const response = createResponse();
1677
1680
 
1678
- await app.dispatch(createRequest('/health-info/'), response);
1681
+ await app.dispatch(createRequest('/greeting/'), response);
1679
1682
 
1680
- expect(response.body).toEqual({ ok: true, service: expect.any(String) });
1683
+ expect(response.body).toEqual({ message: 'Hello from fluo', framework: 'fluo', project: expect.any(String) });
1681
1684
 
1682
1685
  await app.close();
1683
1686
  });
@@ -1703,8 +1706,8 @@ describe('AppModule e2e', () => {
1703
1706
  body: { status: 'ready' },
1704
1707
  status: 200,
1705
1708
  });
1706
- await expect(app.dispatch({ method: 'GET', path: '/health-info/' })).resolves.toMatchObject({
1707
- body: { ok: true, service: expect.any(String) },
1709
+ await expect(app.dispatch({ method: 'GET', path: '/greeting/' })).resolves.toMatchObject({
1710
+ body: { message: 'Hello from fluo', framework: 'fluo', project: expect.any(String) },
1708
1711
  status: 200,
1709
1712
  });
1710
1713
 
@@ -1763,7 +1766,7 @@ Deno.test('AppModule dispatches the generated Deno starter routes', async () =>
1763
1766
 
1764
1767
  await app.dispatch({ body: undefined, cookies: {}, headers: {}, method: 'GET', params: {}, path: '/health', query: {}, raw: {}, url: '/health' }, healthResponse as never);
1765
1768
  await app.dispatch({ body: undefined, cookies: {}, headers: {}, method: 'GET', params: {}, path: '/ready', query: {}, raw: {}, url: '/ready' }, readyResponse as never);
1766
- await app.dispatch({ body: undefined, cookies: {}, headers: {}, method: 'GET', params: {}, path: '/health-info/', query: {}, raw: {}, url: '/health-info/' }, infoResponse as never);
1769
+ await app.dispatch({ body: undefined, cookies: {}, headers: {}, method: 'GET', params: {}, path: '/greeting/', query: {}, raw: {}, url: '/greeting/' }, infoResponse as never);
1767
1770
 
1768
1771
  if (JSON.stringify(healthResponse.body) !== JSON.stringify({ status: 'ok' })) {
1769
1772
  throw new Error('Expected /health to return status ok.');
@@ -1774,12 +1777,12 @@ Deno.test('AppModule dispatches the generated Deno starter routes', async () =>
1774
1777
  }
1775
1778
 
1776
1779
  if (typeof infoResponse.body !== 'object' || infoResponse.body === null) {
1777
- throw new Error('Expected /health-info/ to return an object body.');
1780
+ throw new Error('Expected /greeting/ to return an object body.');
1778
1781
  }
1779
1782
 
1780
- const infoBody = infoResponse.body as { ok?: unknown; service?: unknown };
1781
- if (infoBody.ok !== true || typeof infoBody.service !== 'string') {
1782
- throw new Error('Expected /health-info/ to return the generated service payload.');
1783
+ const infoBody = infoResponse.body as { framework?: unknown; message?: unknown; project?: unknown };
1784
+ if (infoBody.message !== 'Hello from fluo' || infoBody.framework !== 'fluo' || typeof infoBody.project !== 'string') {
1785
+ throw new Error('Expected /greeting/ to return the generated service payload.');
1783
1786
  }
1784
1787
 
1785
1788
  await app.close();
@@ -1865,7 +1868,7 @@ function emitSharedScaffoldFiles(options, bootstrapPlan, releaseVersion, package
1865
1868
  }];
1866
1869
  if (bootstrapPlan.profile.id !== 'application-deno-deno-http') {
1867
1870
  sharedFiles.push({
1868
- content: createProjectTsconfig(),
1871
+ content: createProjectTsconfig(bootstrapPlan),
1869
1872
  path: 'tsconfig.json'
1870
1873
  }, {
1871
1874
  content: createProjectTsconfigBuild(),
@@ -1905,20 +1908,20 @@ function emitApplicationScaffoldFiles(options) {
1905
1908
  content: createMainFile(options),
1906
1909
  path: entrypointPath
1907
1910
  }, {
1908
- content: createHealthResponseDtoFile(),
1909
- path: 'src/health/health.response.dto.ts'
1911
+ content: createGreetingResponseDtoFile(),
1912
+ path: 'src/greeting/greeting.response.dto.ts'
1910
1913
  }, {
1911
- content: createHealthRepoFile(options.projectName, importSuffix),
1912
- path: 'src/health/health.repo.ts'
1914
+ content: createGreetingRepoFile(options.projectName, importSuffix),
1915
+ path: 'src/greeting/greeting.repo.ts'
1913
1916
  }, {
1914
- content: createHealthServiceFile(importSuffix),
1915
- path: 'src/health/health.service.ts'
1917
+ content: createGreetingServiceFile(importSuffix),
1918
+ path: 'src/greeting/greeting.service.ts'
1916
1919
  }, {
1917
- content: createHealthControllerFile(importSuffix),
1918
- path: 'src/health/health.controller.ts'
1920
+ content: createGreetingControllerFile(importSuffix),
1921
+ path: 'src/greeting/greeting.controller.ts'
1919
1922
  }, {
1920
- content: createHealthModuleFile(importSuffix),
1921
- path: 'src/health/health.module.ts'
1923
+ content: createGreetingModuleFile(importSuffix),
1924
+ path: 'src/greeting/greeting.module.ts'
1922
1925
  }];
1923
1926
  if (options.runtime === 'deno') {
1924
1927
  files.push({
@@ -1928,14 +1931,14 @@ function emitApplicationScaffoldFiles(options) {
1928
1931
  return files;
1929
1932
  }
1930
1933
  files.push({
1931
- content: createHealthRepoTestFile(),
1932
- path: 'src/health/health.repo.test.ts'
1934
+ content: createGreetingRepoTestFile(),
1935
+ path: 'src/greeting/greeting.repo.test.ts'
1933
1936
  }, {
1934
- content: createHealthServiceTestFile(),
1935
- path: 'src/health/health.service.test.ts'
1937
+ content: createGreetingServiceTestFile(),
1938
+ path: 'src/greeting/greeting.service.test.ts'
1936
1939
  }, {
1937
- content: createHealthControllerTestFile(),
1938
- path: 'src/health/health.controller.test.ts'
1940
+ content: createGreetingControllerTestFile(),
1941
+ path: 'src/greeting/greeting.controller.test.ts'
1939
1942
  }, {
1940
1943
  content: createAppTestFile(importSuffix),
1941
1944
  path: 'src/app.test.ts'
@@ -1991,29 +1994,29 @@ function emitScaffoldFilesForRecipe(options, recipeId) {
1991
1994
  content: createMixedMainFile(),
1992
1995
  path: 'src/main.ts'
1993
1996
  }, {
1994
- content: createHealthResponseDtoFile(),
1995
- path: 'src/health/health.response.dto.ts'
1997
+ content: createGreetingResponseDtoFile(),
1998
+ path: 'src/greeting/greeting.response.dto.ts'
1996
1999
  }, {
1997
- content: createHealthRepoFile(options.projectName),
1998
- path: 'src/health/health.repo.ts'
2000
+ content: createGreetingRepoFile(options.projectName),
2001
+ path: 'src/greeting/greeting.repo.ts'
1999
2002
  }, {
2000
- content: createHealthRepoTestFile(),
2001
- path: 'src/health/health.repo.test.ts'
2003
+ content: createGreetingRepoTestFile(),
2004
+ path: 'src/greeting/greeting.repo.test.ts'
2002
2005
  }, {
2003
- content: createHealthServiceFile(),
2004
- path: 'src/health/health.service.ts'
2006
+ content: createGreetingServiceFile(),
2007
+ path: 'src/greeting/greeting.service.ts'
2005
2008
  }, {
2006
- content: createHealthServiceTestFile(),
2007
- path: 'src/health/health.service.test.ts'
2009
+ content: createGreetingServiceTestFile(),
2010
+ path: 'src/greeting/greeting.service.test.ts'
2008
2011
  }, {
2009
- content: createHealthControllerFile(),
2010
- path: 'src/health/health.controller.ts'
2012
+ content: createGreetingControllerFile(),
2013
+ path: 'src/greeting/greeting.controller.ts'
2011
2014
  }, {
2012
- content: createHealthControllerTestFile(),
2013
- path: 'src/health/health.controller.test.ts'
2015
+ content: createGreetingControllerTestFile(),
2016
+ path: 'src/greeting/greeting.controller.test.ts'
2014
2017
  }, {
2015
- content: createHealthModuleFile(),
2016
- path: 'src/health/health.module.ts'
2018
+ content: createGreetingModuleFile(),
2019
+ path: 'src/greeting/greeting.module.ts'
2017
2020
  }, {
2018
2021
  content: createMathHandlerFile({
2019
2022
  transport: 'tcp'