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

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.
@@ -41,7 +41,8 @@ const PUBLISHED_INTERNAL_DEPENDENCIES = {
41
41
  '@fluojs/platform-nodejs': '^1.0.0-beta.4',
42
42
  '@fluojs/runtime': '^1.0.0-beta.9',
43
43
  '@fluojs/testing': '^1.0.0-beta.2',
44
- '@fluojs/validation': '^1.0.0-beta.2'
44
+ '@fluojs/validation': '^1.0.0-beta.2',
45
+ '@fluojs/vite': '^1.0.0-beta.1'
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) {
@@ -1428,12 +1427,12 @@ Generated by @fluojs/cli.
1428
1427
 
1429
1428
  - \`src/app.ts\` — shared application module with config, runtime health endpoints, and the TCP microservice registration.
1430
1429
  - \`src/main.ts\` — Fastify HTTP bootstrap that also starts the attached TCP microservice.
1431
- - \`src/health/*\` — starter-owned HTTP health slice.
1430
+ - \`src/greeting/*\` — starter-owned HTTP greeting slice.
1432
1431
  - \`src/math/*\` — starter-owned message handler slice that proves the microservice half of the topology.
1433
1432
 
1434
1433
  ## Official generated testing templates
1435
1434
 
1436
- - \`src/health/*.test.ts\` — unit templates for the HTTP slice.
1435
+ - \`src/greeting/*.test.ts\` — unit templates for the HTTP slice.
1437
1436
  - \`src/math/math.handler.test.ts\` — unit template for the microservice handler.
1438
1437
  - \`src/app.test.ts\` — hybrid verification template covering HTTP dispatch plus in-memory message routing with the same shared module contract.
1439
1438
 
@@ -1444,12 +1443,11 @@ function createMixedAppFile() {
1444
1443
  return `import { Global, Module } from '@fluojs/core';
1445
1444
  import { ConfigModule } from '@fluojs/config';
1446
1445
  import { MicroservicesModule, TcpMicroserviceTransport } from '@fluojs/microservices';
1447
- import { createHealthModule } from '@fluojs/runtime';
1446
+ import { HealthModule } from '@fluojs/runtime';
1448
1447
 
1449
- import { HealthModule } from './health/health.module';
1448
+ import { GreetingModule } from './greeting/greeting.module';
1450
1449
  import { MathHandler } from './math/math.handler';
1451
1450
 
1452
- const RuntimeHealthModule = createHealthModule();
1453
1451
  const parsedMicroservicePort = Number.parseInt(process.env.MICROSERVICE_PORT ?? '4000', 10);
1454
1452
  const microservicePort = Number.isFinite(parsedMicroservicePort) ? parsedMicroservicePort : 4000;
1455
1453
  const microserviceHost = process.env.MICROSERVICE_HOST ?? '127.0.0.1';
@@ -1461,8 +1459,8 @@ const microserviceHost = process.env.MICROSERVICE_HOST ?? '127.0.0.1';
1461
1459
  envFile: '.env',
1462
1460
  processEnv: process.env,
1463
1461
  }),
1464
- HealthModule,
1465
- RuntimeHealthModule,
1462
+ GreetingModule,
1463
+ HealthModule.forRoot(),
1466
1464
  MicroservicesModule.forRoot({
1467
1465
  transport: new TcpMicroserviceTransport({ host: microserviceHost, port: microservicePort }),
1468
1466
  }),
@@ -1499,9 +1497,9 @@ import {
1499
1497
  MicroservicesModule,
1500
1498
  type MicroserviceTransport,
1501
1499
  } from '@fluojs/microservices';
1502
- import { FluoFactory, createHealthModule } from '@fluojs/runtime';
1500
+ import { FluoFactory, HealthModule } from '@fluojs/runtime';
1503
1501
 
1504
- import { HealthModule } from './health/health.module';
1502
+ import { GreetingModule } from './greeting/greeting.module';
1505
1503
  import { MathHandler } from './math/math.handler';
1506
1504
 
1507
1505
  type TransportHandler = Parameters<MicroserviceTransport['listen']>[0];
@@ -1576,8 +1574,6 @@ function createResponse(): FrameworkResponse & { body?: unknown } {
1576
1574
  describe('AppModule mixed starter', () => {
1577
1575
  it('keeps HTTP routes and message handlers in one explicit topology contract', async () => {
1578
1576
  const transport = new InMemoryLoopbackTransport();
1579
- const RuntimeHealthModule = createHealthModule();
1580
-
1581
1577
  @Global()
1582
1578
  @Module({
1583
1579
  imports: [
@@ -1585,8 +1581,8 @@ describe('AppModule mixed starter', () => {
1585
1581
  envFile: '.env',
1586
1582
  processEnv: process.env,
1587
1583
  }),
1588
- HealthModule,
1589
- RuntimeHealthModule,
1584
+ GreetingModule,
1585
+ HealthModule.forRoot(),
1590
1586
  MicroservicesModule.forRoot({ transport }),
1591
1587
  ],
1592
1588
  providers: [MathHandler],
@@ -1594,13 +1590,13 @@ describe('AppModule mixed starter', () => {
1594
1590
  class TestAppModule {}
1595
1591
 
1596
1592
  const app = await FluoFactory.create(TestAppModule, {});
1597
- const healthResponse = createResponse();
1593
+ const greetingResponse = createResponse();
1598
1594
 
1599
1595
  const microservice = await app.connectMicroservice();
1600
1596
 
1601
- await Promise.all([app.startAllMicroservices(), app.dispatch(createRequest('/health-info/'), healthResponse)]);
1597
+ await Promise.all([app.startAllMicroservices(), app.dispatch(createRequest('/greeting/'), greetingResponse)]);
1602
1598
 
1603
- expect(healthResponse.body).toEqual({ ok: true, service: expect.any(String) });
1599
+ expect(greetingResponse.body).toEqual({ message: 'Hello from fluo', framework: 'fluo', project: expect.any(String) });
1604
1600
  await expect(transport.send('math.sum', { a: 20, b: 22 })).resolves.toBe(42);
1605
1601
  expect(microservice.state).toBe('ready');
1606
1602
 
@@ -1671,13 +1667,13 @@ describe('AppModule', () => {
1671
1667
  await app.close();
1672
1668
  });
1673
1669
 
1674
- it('dispatches the health-info route', async () => {
1670
+ it('dispatches the greeting route', async () => {
1675
1671
  const app = await FluoFactory.create(AppModule, {});
1676
1672
  const response = createResponse();
1677
1673
 
1678
- await app.dispatch(createRequest('/health-info/'), response);
1674
+ await app.dispatch(createRequest('/greeting/'), response);
1679
1675
 
1680
- expect(response.body).toEqual({ ok: true, service: expect.any(String) });
1676
+ expect(response.body).toEqual({ message: 'Hello from fluo', framework: 'fluo', project: expect.any(String) });
1681
1677
 
1682
1678
  await app.close();
1683
1679
  });
@@ -1703,8 +1699,8 @@ describe('AppModule e2e', () => {
1703
1699
  body: { status: 'ready' },
1704
1700
  status: 200,
1705
1701
  });
1706
- await expect(app.dispatch({ method: 'GET', path: '/health-info/' })).resolves.toMatchObject({
1707
- body: { ok: true, service: expect.any(String) },
1702
+ await expect(app.dispatch({ method: 'GET', path: '/greeting/' })).resolves.toMatchObject({
1703
+ body: { message: 'Hello from fluo', framework: 'fluo', project: expect.any(String) },
1708
1704
  status: 200,
1709
1705
  });
1710
1706
 
@@ -1763,7 +1759,7 @@ Deno.test('AppModule dispatches the generated Deno starter routes', async () =>
1763
1759
 
1764
1760
  await app.dispatch({ body: undefined, cookies: {}, headers: {}, method: 'GET', params: {}, path: '/health', query: {}, raw: {}, url: '/health' }, healthResponse as never);
1765
1761
  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);
1762
+ await app.dispatch({ body: undefined, cookies: {}, headers: {}, method: 'GET', params: {}, path: '/greeting/', query: {}, raw: {}, url: '/greeting/' }, infoResponse as never);
1767
1763
 
1768
1764
  if (JSON.stringify(healthResponse.body) !== JSON.stringify({ status: 'ok' })) {
1769
1765
  throw new Error('Expected /health to return status ok.');
@@ -1774,12 +1770,12 @@ Deno.test('AppModule dispatches the generated Deno starter routes', async () =>
1774
1770
  }
1775
1771
 
1776
1772
  if (typeof infoResponse.body !== 'object' || infoResponse.body === null) {
1777
- throw new Error('Expected /health-info/ to return an object body.');
1773
+ throw new Error('Expected /greeting/ to return an object body.');
1778
1774
  }
1779
1775
 
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.');
1776
+ const infoBody = infoResponse.body as { framework?: unknown; message?: unknown; project?: unknown };
1777
+ if (infoBody.message !== 'Hello from fluo' || infoBody.framework !== 'fluo' || typeof infoBody.project !== 'string') {
1778
+ throw new Error('Expected /greeting/ to return the generated service payload.');
1783
1779
  }
1784
1780
 
1785
1781
  await app.close();
@@ -1865,7 +1861,7 @@ function emitSharedScaffoldFiles(options, bootstrapPlan, releaseVersion, package
1865
1861
  }];
1866
1862
  if (bootstrapPlan.profile.id !== 'application-deno-deno-http') {
1867
1863
  sharedFiles.push({
1868
- content: createProjectTsconfig(),
1864
+ content: createProjectTsconfig(bootstrapPlan),
1869
1865
  path: 'tsconfig.json'
1870
1866
  }, {
1871
1867
  content: createProjectTsconfigBuild(),
@@ -1905,20 +1901,20 @@ function emitApplicationScaffoldFiles(options) {
1905
1901
  content: createMainFile(options),
1906
1902
  path: entrypointPath
1907
1903
  }, {
1908
- content: createHealthResponseDtoFile(),
1909
- path: 'src/health/health.response.dto.ts'
1904
+ content: createGreetingResponseDtoFile(),
1905
+ path: 'src/greeting/greeting.response.dto.ts'
1910
1906
  }, {
1911
- content: createHealthRepoFile(options.projectName, importSuffix),
1912
- path: 'src/health/health.repo.ts'
1907
+ content: createGreetingRepoFile(options.projectName, importSuffix),
1908
+ path: 'src/greeting/greeting.repo.ts'
1913
1909
  }, {
1914
- content: createHealthServiceFile(importSuffix),
1915
- path: 'src/health/health.service.ts'
1910
+ content: createGreetingServiceFile(importSuffix),
1911
+ path: 'src/greeting/greeting.service.ts'
1916
1912
  }, {
1917
- content: createHealthControllerFile(importSuffix),
1918
- path: 'src/health/health.controller.ts'
1913
+ content: createGreetingControllerFile(importSuffix),
1914
+ path: 'src/greeting/greeting.controller.ts'
1919
1915
  }, {
1920
- content: createHealthModuleFile(importSuffix),
1921
- path: 'src/health/health.module.ts'
1916
+ content: createGreetingModuleFile(importSuffix),
1917
+ path: 'src/greeting/greeting.module.ts'
1922
1918
  }];
1923
1919
  if (options.runtime === 'deno') {
1924
1920
  files.push({
@@ -1928,14 +1924,14 @@ function emitApplicationScaffoldFiles(options) {
1928
1924
  return files;
1929
1925
  }
1930
1926
  files.push({
1931
- content: createHealthRepoTestFile(),
1932
- path: 'src/health/health.repo.test.ts'
1927
+ content: createGreetingRepoTestFile(),
1928
+ path: 'src/greeting/greeting.repo.test.ts'
1933
1929
  }, {
1934
- content: createHealthServiceTestFile(),
1935
- path: 'src/health/health.service.test.ts'
1930
+ content: createGreetingServiceTestFile(),
1931
+ path: 'src/greeting/greeting.service.test.ts'
1936
1932
  }, {
1937
- content: createHealthControllerTestFile(),
1938
- path: 'src/health/health.controller.test.ts'
1933
+ content: createGreetingControllerTestFile(),
1934
+ path: 'src/greeting/greeting.controller.test.ts'
1939
1935
  }, {
1940
1936
  content: createAppTestFile(importSuffix),
1941
1937
  path: 'src/app.test.ts'
@@ -1991,29 +1987,29 @@ function emitScaffoldFilesForRecipe(options, recipeId) {
1991
1987
  content: createMixedMainFile(),
1992
1988
  path: 'src/main.ts'
1993
1989
  }, {
1994
- content: createHealthResponseDtoFile(),
1995
- path: 'src/health/health.response.dto.ts'
1990
+ content: createGreetingResponseDtoFile(),
1991
+ path: 'src/greeting/greeting.response.dto.ts'
1996
1992
  }, {
1997
- content: createHealthRepoFile(options.projectName),
1998
- path: 'src/health/health.repo.ts'
1993
+ content: createGreetingRepoFile(options.projectName),
1994
+ path: 'src/greeting/greeting.repo.ts'
1999
1995
  }, {
2000
- content: createHealthRepoTestFile(),
2001
- path: 'src/health/health.repo.test.ts'
1996
+ content: createGreetingRepoTestFile(),
1997
+ path: 'src/greeting/greeting.repo.test.ts'
2002
1998
  }, {
2003
- content: createHealthServiceFile(),
2004
- path: 'src/health/health.service.ts'
1999
+ content: createGreetingServiceFile(),
2000
+ path: 'src/greeting/greeting.service.ts'
2005
2001
  }, {
2006
- content: createHealthServiceTestFile(),
2007
- path: 'src/health/health.service.test.ts'
2002
+ content: createGreetingServiceTestFile(),
2003
+ path: 'src/greeting/greeting.service.test.ts'
2008
2004
  }, {
2009
- content: createHealthControllerFile(),
2010
- path: 'src/health/health.controller.ts'
2005
+ content: createGreetingControllerFile(),
2006
+ path: 'src/greeting/greeting.controller.ts'
2011
2007
  }, {
2012
- content: createHealthControllerTestFile(),
2013
- path: 'src/health/health.controller.test.ts'
2008
+ content: createGreetingControllerTestFile(),
2009
+ path: 'src/greeting/greeting.controller.test.ts'
2014
2010
  }, {
2015
- content: createHealthModuleFile(),
2016
- path: 'src/health/health.module.ts'
2011
+ content: createGreetingModuleFile(),
2012
+ path: 'src/greeting/greeting.module.ts'
2017
2013
  }, {
2018
2014
  content: createMathHandlerFile({
2019
2015
  transport: 'tcp'