@fluojs/cli 1.0.0-beta.4 → 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.
Files changed (40) hide show
  1. package/README.ko.md +97 -3
  2. package/README.md +97 -3
  3. package/dist/cli.d.ts +8 -0
  4. package/dist/cli.d.ts.map +1 -1
  5. package/dist/cli.js +201 -4
  6. package/dist/commands/diagnostics.d.ts +15 -0
  7. package/dist/commands/diagnostics.d.ts.map +1 -0
  8. package/dist/commands/diagnostics.js +163 -0
  9. package/dist/commands/new.js +2 -2
  10. package/dist/commands/package-manager.d.ts +9 -0
  11. package/dist/commands/package-manager.d.ts.map +1 -0
  12. package/dist/commands/package-manager.js +63 -0
  13. package/dist/commands/package-workflow.d.ts +20 -0
  14. package/dist/commands/package-workflow.d.ts.map +1 -0
  15. package/dist/commands/package-workflow.js +137 -0
  16. package/dist/commands/scripts.d.ts +38 -0
  17. package/dist/commands/scripts.d.ts.map +1 -0
  18. package/dist/commands/scripts.js +570 -0
  19. package/dist/dev-runner/node-restart-runner.d.ts +55 -0
  20. package/dist/dev-runner/node-restart-runner.d.ts.map +1 -0
  21. package/dist/dev-runner/node-restart-runner.js +317 -0
  22. package/dist/dev-runner/preserve-color-tty.d.ts +2 -0
  23. package/dist/dev-runner/preserve-color-tty.d.ts.map +1 -0
  24. package/dist/dev-runner/preserve-color-tty.js +12 -0
  25. package/dist/generators/manifest.d.ts +24 -0
  26. package/dist/generators/manifest.d.ts.map +1 -1
  27. package/dist/generators/manifest.js +9 -0
  28. package/dist/generators/resource.d.ts +10 -0
  29. package/dist/generators/resource.d.ts.map +1 -0
  30. package/dist/generators/resource.js +23 -0
  31. package/dist/generators/templates/controller.ts.ejs +5 -1
  32. package/dist/generators/templates/request-dto.ts.ejs +3 -0
  33. package/dist/new/scaffold.d.ts.map +1 -1
  34. package/dist/new/scaffold.js +193 -148
  35. package/dist/new/starter-profiles.d.ts.map +1 -1
  36. package/dist/new/starter-profiles.js +13 -13
  37. package/dist/update-check.d.ts +1 -0
  38. package/dist/update-check.d.ts.map +1 -1
  39. package/dist/update-check.js +7 -5
  40. package/package.json +2 -2
@@ -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') {
@@ -148,15 +149,17 @@ function createProjectScripts(bootstrapPlan) {
148
149
  case 'application-bun-bun-http':
149
150
  return {
150
151
  build: 'bun build ./src/main.ts --outdir ./dist --target bun',
151
- dev: 'bun --watch src/main.ts',
152
+ dev: 'fluo dev',
153
+ start: 'bun dist/main.js',
152
154
  test: 'vitest run',
153
155
  'test:watch': 'vitest',
154
156
  typecheck: 'tsc -p tsconfig.json --noEmit'
155
157
  };
156
158
  case 'application-deno-deno-http':
157
159
  return {
158
- build: 'mkdir -p dist && deno compile --allow-env --allow-net --output dist/app src/main.ts',
159
- dev: 'deno run --allow-env --allow-net --watch src/main.ts',
160
+ build: 'deno compile --allow-env --allow-net --output dist/app src/main.ts',
161
+ dev: 'fluo dev',
162
+ start: './dist/app',
160
163
  test: 'deno test --allow-env --allow-net',
161
164
  'test:watch': 'deno test --allow-env --allow-net --watch',
162
165
  typecheck: 'deno check src/main.ts src/app.test.ts'
@@ -164,15 +167,18 @@ function createProjectScripts(bootstrapPlan) {
164
167
  case 'application-cloudflare-workers-cloudflare-workers-http':
165
168
  return {
166
169
  build: 'wrangler deploy --dry-run',
167
- dev: 'wrangler dev',
170
+ deploy: 'wrangler deploy',
171
+ dev: 'fluo dev',
172
+ preview: 'wrangler dev --remote --show-interactive-dev-session=false',
168
173
  test: 'vitest run',
169
174
  'test:watch': 'vitest',
170
175
  typecheck: 'tsc -p tsconfig.json --noEmit'
171
176
  };
172
177
  default:
173
178
  return {
174
- build: 'babel src --extensions .ts --out-dir dist --config-file ./babel.config.cjs && tsc -p tsconfig.build.json',
175
- dev: 'node --env-file=.env --watch --watch-preserve-output --import tsx src/main.ts',
179
+ build: 'fluo build',
180
+ dev: 'fluo dev',
181
+ start: 'fluo start',
176
182
  test: 'vitest run',
177
183
  'test:watch': 'vitest',
178
184
  typecheck: 'tsc -p tsconfig.json --noEmit'
@@ -199,6 +205,12 @@ function createPublishedDevDependencies(bootstrapPlan) {
199
205
  if (bootstrapPlan.profile.id === 'application-deno-deno-http') {
200
206
  return {};
201
207
  }
208
+ if (bootstrapPlan.profile.id === 'application-bun-bun-http') {
209
+ return {
210
+ ...PUBLISHED_DEV_DEPENDENCIES,
211
+ '@types/bun': '^1.2.5'
212
+ };
213
+ }
202
214
  if (bootstrapPlan.profile.id === 'application-cloudflare-workers-cloudflare-workers-http') {
203
215
  return {
204
216
  ...PUBLISHED_DEV_DEPENDENCIES,
@@ -239,7 +251,8 @@ function createProjectPackageJson(options, bootstrapPlan, releaseVersion, packag
239
251
  }
240
252
  }, null, 2);
241
253
  }
242
- function createProjectTsconfig() {
254
+ function createProjectTsconfig(bootstrapPlan) {
255
+ const types = bootstrapPlan.profile.id === 'application-bun-bun-http' ? ['node', 'bun'] : ['node'];
243
256
  return `{
244
257
  "compilerOptions": {
245
258
  "target": "ES2022",
@@ -253,7 +266,7 @@ function createProjectTsconfig() {
253
266
  "forceConsistentCasingInFileNames": true,
254
267
  "resolveJsonModule": true,
255
268
  "rootDir": "src",
256
- "types": ["node"]
269
+ "types": ${JSON.stringify(types)}
257
270
  },
258
271
  "include": ["src/**/*.ts"]
259
272
  }
@@ -281,9 +294,22 @@ function createBabelConfig() {
281
294
  `;
282
295
  }
283
296
  function createViteConfig() {
284
- return `import { defineConfig } from 'vite';
297
+ return `import { fluoDecoratorsPlugin } from '@fluojs/vite';
298
+ import { defineConfig } from 'vite';
285
299
 
286
300
  export default defineConfig({
301
+ plugins: [fluoDecoratorsPlugin()],
302
+ build: {
303
+ emptyOutDir: true,
304
+ outDir: 'dist',
305
+ rollupOptions: {
306
+ output: {
307
+ entryFileNames: 'main.js',
308
+ },
309
+ },
310
+ ssr: 'src/main.ts',
311
+ target: 'node20',
312
+ },
287
313
  server: {
288
314
  port: 5173,
289
315
  },
@@ -315,12 +341,37 @@ dist
315
341
  coverage
316
342
  `;
317
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
+ }
318
369
  function createHttpProjectReadme(options) {
319
370
  const starter = describeApplicationStarter(options);
320
371
  const entrypointLabel = starter.entrypoint;
321
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('(... )', '(...)');
322
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`;
323
- 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.`;
324
375
  return `# ${options.projectName}
325
376
 
326
377
  Generated by @fluojs/cli.
@@ -328,19 +379,16 @@ Generated by @fluojs/cli.
328
379
  - Starter contract: ${starterContract}
329
380
  - Default baseline: when you omit \`--platform\`, \`fluo new\` still generates the Node.js + Fastify HTTP starter by default
330
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
331
- - Package manager: install/run commands can use ${options.packageManager}; runtime choice stays explicit and is independent from the package manager you picked
382
+ ${createHttpPackageManagerLine(options)}
332
383
  - Runtime dependency set: generated manifest entries match the selected runtime contract instead of inheriting the Node-only starter recipe
333
384
  ${corsLine}
334
385
  - Observability: /health and /ready endpoints are included by default
335
386
  - Runtime path: bootstrapApplication -> handler mapping -> dispatcher -> middleware -> guard -> interceptor -> controller
336
- - 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(...)\`)
337
388
 
338
389
  ## Commands
339
390
 
340
- - Dev: ${createRunCommand(options.packageManager, 'dev')}
341
- - Build: ${createRunCommand(options.packageManager, 'build')}
342
- - Typecheck: ${createRunCommand(options.packageManager, 'typecheck')}
343
- - Test: ${createRunCommand(options.packageManager, 'test')}
391
+ ${createHttpCommandsSection(options)}
344
392
 
345
393
  ## Generator example
346
394
 
@@ -452,7 +500,7 @@ Generated by @fluojs/cli.
452
500
  - Transport: \`${starter.readmeTransportLabel}\` is the generated runnable starter contract for this project
453
501
  - Runtime: \`node\`
454
502
  - Platform: \`none\` because the microservice starter boots through \`@fluojs/microservices\`, not an HTTP adapter
455
- - Package manager: install/run commands can use ${options.packageManager}; ${starter.packageManagerNote}
503
+ - 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; ${starter.packageManagerNote}
456
504
  - Messaging contract: \`src/math/math.handler.ts\` exposes a \`${starter.pattern}\` message pattern and the generated tests verify it through an in-memory transport so the starter stays testable without external brokers
457
505
  - Entrypoint contract: ${starter.entrypointNote}
458
506
  - Starter contract note: ${starter.starterNote}
@@ -461,6 +509,7 @@ Generated by @fluojs/cli.
461
509
  ## Commands
462
510
 
463
511
  - Dev: ${createRunCommand(options.packageManager, 'dev')}
512
+ - Start: ${createRunCommand(options.packageManager, 'start')}
464
513
  - Build: ${createRunCommand(options.packageManager, 'build')}
465
514
  - Typecheck: ${createRunCommand(options.packageManager, 'typecheck')}
466
515
  - Test: ${createRunCommand(options.packageManager, 'test')}
@@ -502,30 +551,26 @@ function createAppFile(options) {
502
551
  const importSuffix = options.runtime === 'deno' ? '.ts' : '';
503
552
  if (options.runtime === 'cloudflare-workers') {
504
553
  return `import { Global, Module } from '@fluojs/core';
505
- import { createHealthModule } from '@fluojs/runtime';
506
-
507
- import { HealthModule } from './health/health.module';
554
+ import { HealthModule } from '@fluojs/runtime';
508
555
 
509
- const RuntimeHealthModule = createHealthModule();
556
+ import { GreetingModule } from './greeting/greeting.module';
510
557
 
511
558
  @Global()
512
559
  @Module({
513
560
  imports: [
514
- HealthModule,
515
- RuntimeHealthModule,
561
+ GreetingModule,
562
+ HealthModule.forRoot(),
516
563
  ],
517
564
  })
518
565
  export class AppModule {}
519
566
  `;
520
567
  }
521
- 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';
522
569
  return `import { Global, Module } from '@fluojs/core';
523
570
  import { ConfigModule } from '@fluojs/config';
524
- import { createHealthModule } from '@fluojs/runtime';
525
-
526
- import { HealthModule } from './health/health.module${importSuffix}';
571
+ import { HealthModule } from '@fluojs/runtime';
527
572
 
528
- const RuntimeHealthModule = createHealthModule();
573
+ import { GreetingModule } from './greeting/greeting.module${importSuffix}';
529
574
 
530
575
  @Global()
531
576
  @Module({
@@ -534,132 +579,134 @@ const RuntimeHealthModule = createHealthModule();
534
579
  envFile: '.env',
535
580
  processEnv: ${processEnvValue},
536
581
  }),
537
- HealthModule,
538
- RuntimeHealthModule,
582
+ GreetingModule,
583
+ HealthModule.forRoot(),
539
584
  ],
540
585
  })
541
586
  export class AppModule {}
542
587
  `;
543
588
  }
544
- function createHealthResponseDtoFile() {
545
- return `export class HealthResponseDto {
546
- ok!: boolean;
547
- service!: string;
589
+ function createGreetingResponseDtoFile() {
590
+ return `export class GreetingResponseDto {
591
+ message!: string;
592
+ framework!: 'fluo';
593
+ project!: string;
548
594
  }
549
595
  `;
550
596
  }
551
- function createHealthRepoFile(projectName, importSuffix = '') {
552
- return `import type { HealthResponseDto } from './health.response.dto${importSuffix}';
597
+ function createGreetingRepoFile(projectName, importSuffix = '') {
598
+ return `import type { GreetingResponseDto } from './greeting.response.dto${importSuffix}';
553
599
 
554
- export class HealthRepo {
555
- findHealth(): HealthResponseDto {
600
+ export class GreetingRepo {
601
+ findGreeting(): GreetingResponseDto {
556
602
  return {
557
- ok: true,
558
- service: ${JSON.stringify(projectName)},
603
+ message: 'Hello from fluo',
604
+ framework: 'fluo',
605
+ project: ${JSON.stringify(projectName)},
559
606
  };
560
607
  }
561
608
  }
562
609
  `;
563
610
  }
564
- function createHealthRepoTestFile() {
611
+ function createGreetingRepoTestFile() {
565
612
  return `import { describe, expect, it } from 'vitest';
566
613
 
567
- import { HealthRepo } from './health.repo';
614
+ import { GreetingRepo } from './greeting.repo';
568
615
 
569
- describe('HealthRepo', () => {
570
- it('returns health data', () => {
571
- const repo = new HealthRepo();
572
- 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) });
573
620
  });
574
621
  });
575
622
  `;
576
623
  }
577
- function createHealthServiceFile(importSuffix = '') {
624
+ function createGreetingServiceFile(importSuffix = '') {
578
625
  return `import { Inject } from '@fluojs/core';
579
- import type { HealthResponseDto } from './health.response.dto${importSuffix}';
626
+ import type { GreetingResponseDto } from './greeting.response.dto${importSuffix}';
580
627
 
581
- import { HealthRepo } from './health.repo${importSuffix}';
628
+ import { GreetingRepo } from './greeting.repo${importSuffix}';
582
629
 
583
- @Inject(HealthRepo)
584
- export class HealthService {
585
- constructor(private readonly repo: HealthRepo) {}
630
+ @Inject(GreetingRepo)
631
+ export class GreetingService {
632
+ constructor(private readonly repo: GreetingRepo) {}
586
633
 
587
- getHealth(): HealthResponseDto {
588
- return this.repo.findHealth();
634
+ getGreeting(): GreetingResponseDto {
635
+ return this.repo.findGreeting();
589
636
  }
590
637
  }
591
638
  `;
592
639
  }
593
- function createHealthServiceTestFile() {
640
+ function createGreetingServiceTestFile() {
594
641
  return `import { describe, expect, it } from 'vitest';
595
642
 
596
- import { HealthService } from './health.service';
597
- import { HealthRepo } from './health.repo';
643
+ import { GreetingService } from './greeting.service';
644
+ import { GreetingRepo } from './greeting.repo';
598
645
 
599
- class FakeHealthRepo {
600
- findHealth() {
601
- return { ok: true, service: 'test' };
646
+ class FakeGreetingRepo {
647
+ findGreeting() {
648
+ return { message: 'Hello from fluo', framework: 'fluo' as const, project: 'test' };
602
649
  }
603
650
  }
604
651
 
605
- describe('HealthService', () => {
652
+ describe('GreetingService', () => {
606
653
  it('delegates to the repo', () => {
607
- const service = new HealthService(new FakeHealthRepo() as HealthRepo);
608
- 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' });
609
656
  });
610
657
  });
611
658
  `;
612
659
  }
613
- function createHealthControllerFile(importSuffix = '') {
660
+ function createGreetingControllerFile(importSuffix = '') {
614
661
  return `import { Inject } from '@fluojs/core';
615
662
  import { Controller, Get } from '@fluojs/http';
616
663
 
617
- import { HealthService } from './health.service${importSuffix}';
618
- import { HealthResponseDto } from './health.response.dto${importSuffix}';
664
+ import { GreetingService } from './greeting.service${importSuffix}';
665
+ import { GreetingResponseDto } from './greeting.response.dto${importSuffix}';
619
666
 
620
- @Inject(HealthService)
621
- @Controller('/health-info')
622
- export class HealthController {
623
- constructor(private readonly service: HealthService) {}
667
+ @Inject(GreetingService)
668
+ @Controller('/greeting')
669
+ export class GreetingController {
670
+ constructor(private readonly service: GreetingService) {}
624
671
 
625
672
  @Get('/')
626
- getHealth(): HealthResponseDto {
627
- return this.service.getHealth();
673
+ getGreeting(): GreetingResponseDto {
674
+ return this.service.getGreeting();
628
675
  }
629
676
  }
630
677
  `;
631
678
  }
632
- function createHealthControllerTestFile() {
679
+ function createGreetingControllerTestFile() {
633
680
  return `import { describe, expect, it } from 'vitest';
634
681
 
635
- import { HealthController } from './health.controller';
682
+ import { GreetingController } from './greeting.controller';
636
683
 
637
- class FakeHealthService {
638
- getHealth() {
639
- return { ok: true, service: 'test' };
684
+ class FakeGreetingService {
685
+ getGreeting() {
686
+ return { message: 'Hello from fluo', framework: 'fluo' as const, project: 'test' };
640
687
  }
641
688
  }
642
689
 
643
- describe('HealthController', () => {
690
+ describe('GreetingController', () => {
644
691
  it('delegates to the service', () => {
645
- const controller = new HealthController(new FakeHealthService() as never);
646
- 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' });
647
694
  });
648
695
  });
649
696
  `;
650
697
  }
651
- function createHealthModuleFile(importSuffix = '') {
698
+ function createGreetingModuleFile(importSuffix = '') {
652
699
  return `import { Module } from '@fluojs/core';
653
700
 
654
- import { HealthController } from './health.controller${importSuffix}';
655
- import { HealthRepo } from './health.repo${importSuffix}';
656
- 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}';
657
704
 
658
705
  @Module({
659
- controllers: [HealthController],
660
- providers: [HealthRepo, HealthService],
706
+ controllers: [GreetingController],
707
+ providers: [GreetingRepo, GreetingService],
661
708
  })
662
- export class HealthModule {}
709
+ export class GreetingModule {}
663
710
  `;
664
711
  }
665
712
  function createMainFile(options) {
@@ -1365,12 +1412,13 @@ Generated by @fluojs/cli.
1365
1412
 
1366
1413
  - Shape: \`mixed\`
1367
1414
  - Supported topology: \`single-package\` generates one shared Node.js package where the Fastify HTTP application also starts an attached TCP microservice from the same \`src/main.ts\`
1368
- - Runtime contract: \`src/app.ts\` owns one shared module graph, and \`src/main.ts\` bootstraps the HTTP API before calling \`connectMicroservice()\` and \`startAllMicroservices()\`
1415
+ - Runtime contract: \`src/app.ts\` owns one shared module graph, and \`src/main.ts\` bootstraps the HTTP API before calling \`connectMicroservice()\` and \`startAllMicroservices()\`; ${createRunCommand(options.packageManager, 'dev')}, ${createRunCommand(options.packageManager, 'build')}, and ${createRunCommand(options.packageManager, 'start')} delegate to \`fluo dev\`, \`fluo build\`, and \`fluo start\`, which default \`NODE_ENV\` when unset
1369
1416
  - Intentional limitation: the first mixed release supports only the explicit Fastify HTTP + attached TCP microservice contract; other mixed transports or separate multi-entrypoint layouts fail validation instead of generating partial scaffolds
1370
1417
 
1371
1418
  ## Commands
1372
1419
 
1373
1420
  - Dev: ${createRunCommand(options.packageManager, 'dev')}
1421
+ - Start: ${createRunCommand(options.packageManager, 'start')}
1374
1422
  - Build: ${createRunCommand(options.packageManager, 'build')}
1375
1423
  - Typecheck: ${createRunCommand(options.packageManager, 'typecheck')}
1376
1424
  - Test: ${createRunCommand(options.packageManager, 'test')}
@@ -1379,12 +1427,12 @@ Generated by @fluojs/cli.
1379
1427
 
1380
1428
  - \`src/app.ts\` — shared application module with config, runtime health endpoints, and the TCP microservice registration.
1381
1429
  - \`src/main.ts\` — Fastify HTTP bootstrap that also starts the attached TCP microservice.
1382
- - \`src/health/*\` — starter-owned HTTP health slice.
1430
+ - \`src/greeting/*\` — starter-owned HTTP greeting slice.
1383
1431
  - \`src/math/*\` — starter-owned message handler slice that proves the microservice half of the topology.
1384
1432
 
1385
1433
  ## Official generated testing templates
1386
1434
 
1387
- - \`src/health/*.test.ts\` — unit templates for the HTTP slice.
1435
+ - \`src/greeting/*.test.ts\` — unit templates for the HTTP slice.
1388
1436
  - \`src/math/math.handler.test.ts\` — unit template for the microservice handler.
1389
1437
  - \`src/app.test.ts\` — hybrid verification template covering HTTP dispatch plus in-memory message routing with the same shared module contract.
1390
1438
 
@@ -1395,12 +1443,11 @@ function createMixedAppFile() {
1395
1443
  return `import { Global, Module } from '@fluojs/core';
1396
1444
  import { ConfigModule } from '@fluojs/config';
1397
1445
  import { MicroservicesModule, TcpMicroserviceTransport } from '@fluojs/microservices';
1398
- import { createHealthModule } from '@fluojs/runtime';
1446
+ import { HealthModule } from '@fluojs/runtime';
1399
1447
 
1400
- import { HealthModule } from './health/health.module';
1448
+ import { GreetingModule } from './greeting/greeting.module';
1401
1449
  import { MathHandler } from './math/math.handler';
1402
1450
 
1403
- const RuntimeHealthModule = createHealthModule();
1404
1451
  const parsedMicroservicePort = Number.parseInt(process.env.MICROSERVICE_PORT ?? '4000', 10);
1405
1452
  const microservicePort = Number.isFinite(parsedMicroservicePort) ? parsedMicroservicePort : 4000;
1406
1453
  const microserviceHost = process.env.MICROSERVICE_HOST ?? '127.0.0.1';
@@ -1412,8 +1459,8 @@ const microserviceHost = process.env.MICROSERVICE_HOST ?? '127.0.0.1';
1412
1459
  envFile: '.env',
1413
1460
  processEnv: process.env,
1414
1461
  }),
1415
- HealthModule,
1416
- RuntimeHealthModule,
1462
+ GreetingModule,
1463
+ HealthModule.forRoot(),
1417
1464
  MicroservicesModule.forRoot({
1418
1465
  transport: new TcpMicroserviceTransport({ host: microserviceHost, port: microservicePort }),
1419
1466
  }),
@@ -1450,9 +1497,9 @@ import {
1450
1497
  MicroservicesModule,
1451
1498
  type MicroserviceTransport,
1452
1499
  } from '@fluojs/microservices';
1453
- import { FluoFactory, createHealthModule } from '@fluojs/runtime';
1500
+ import { FluoFactory, HealthModule } from '@fluojs/runtime';
1454
1501
 
1455
- import { HealthModule } from './health/health.module';
1502
+ import { GreetingModule } from './greeting/greeting.module';
1456
1503
  import { MathHandler } from './math/math.handler';
1457
1504
 
1458
1505
  type TransportHandler = Parameters<MicroserviceTransport['listen']>[0];
@@ -1527,8 +1574,6 @@ function createResponse(): FrameworkResponse & { body?: unknown } {
1527
1574
  describe('AppModule mixed starter', () => {
1528
1575
  it('keeps HTTP routes and message handlers in one explicit topology contract', async () => {
1529
1576
  const transport = new InMemoryLoopbackTransport();
1530
- const RuntimeHealthModule = createHealthModule();
1531
-
1532
1577
  @Global()
1533
1578
  @Module({
1534
1579
  imports: [
@@ -1536,8 +1581,8 @@ describe('AppModule mixed starter', () => {
1536
1581
  envFile: '.env',
1537
1582
  processEnv: process.env,
1538
1583
  }),
1539
- HealthModule,
1540
- RuntimeHealthModule,
1584
+ GreetingModule,
1585
+ HealthModule.forRoot(),
1541
1586
  MicroservicesModule.forRoot({ transport }),
1542
1587
  ],
1543
1588
  providers: [MathHandler],
@@ -1545,13 +1590,13 @@ describe('AppModule mixed starter', () => {
1545
1590
  class TestAppModule {}
1546
1591
 
1547
1592
  const app = await FluoFactory.create(TestAppModule, {});
1548
- const healthResponse = createResponse();
1593
+ const greetingResponse = createResponse();
1549
1594
 
1550
1595
  const microservice = await app.connectMicroservice();
1551
1596
 
1552
- await Promise.all([app.startAllMicroservices(), app.dispatch(createRequest('/health-info/'), healthResponse)]);
1597
+ await Promise.all([app.startAllMicroservices(), app.dispatch(createRequest('/greeting/'), greetingResponse)]);
1553
1598
 
1554
- 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) });
1555
1600
  await expect(transport.send('math.sum', { a: 20, b: 22 })).resolves.toBe(42);
1556
1601
  expect(microservice.state).toBe('ready');
1557
1602
 
@@ -1622,13 +1667,13 @@ describe('AppModule', () => {
1622
1667
  await app.close();
1623
1668
  });
1624
1669
 
1625
- it('dispatches the health-info route', async () => {
1670
+ it('dispatches the greeting route', async () => {
1626
1671
  const app = await FluoFactory.create(AppModule, {});
1627
1672
  const response = createResponse();
1628
1673
 
1629
- await app.dispatch(createRequest('/health-info/'), response);
1674
+ await app.dispatch(createRequest('/greeting/'), response);
1630
1675
 
1631
- 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) });
1632
1677
 
1633
1678
  await app.close();
1634
1679
  });
@@ -1654,8 +1699,8 @@ describe('AppModule e2e', () => {
1654
1699
  body: { status: 'ready' },
1655
1700
  status: 200,
1656
1701
  });
1657
- await expect(app.dispatch({ method: 'GET', path: '/health-info/' })).resolves.toMatchObject({
1658
- 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) },
1659
1704
  status: 200,
1660
1705
  });
1661
1706
 
@@ -1714,7 +1759,7 @@ Deno.test('AppModule dispatches the generated Deno starter routes', async () =>
1714
1759
 
1715
1760
  await app.dispatch({ body: undefined, cookies: {}, headers: {}, method: 'GET', params: {}, path: '/health', query: {}, raw: {}, url: '/health' }, healthResponse as never);
1716
1761
  await app.dispatch({ body: undefined, cookies: {}, headers: {}, method: 'GET', params: {}, path: '/ready', query: {}, raw: {}, url: '/ready' }, readyResponse as never);
1717
- 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);
1718
1763
 
1719
1764
  if (JSON.stringify(healthResponse.body) !== JSON.stringify({ status: 'ok' })) {
1720
1765
  throw new Error('Expected /health to return status ok.');
@@ -1725,12 +1770,12 @@ Deno.test('AppModule dispatches the generated Deno starter routes', async () =>
1725
1770
  }
1726
1771
 
1727
1772
  if (typeof infoResponse.body !== 'object' || infoResponse.body === null) {
1728
- throw new Error('Expected /health-info/ to return an object body.');
1773
+ throw new Error('Expected /greeting/ to return an object body.');
1729
1774
  }
1730
1775
 
1731
- const infoBody = infoResponse.body as { ok?: unknown; service?: unknown };
1732
- if (infoBody.ok !== true || typeof infoBody.service !== 'string') {
1733
- 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.');
1734
1779
  }
1735
1780
 
1736
1781
  await app.close();
@@ -1816,7 +1861,7 @@ function emitSharedScaffoldFiles(options, bootstrapPlan, releaseVersion, package
1816
1861
  }];
1817
1862
  if (bootstrapPlan.profile.id !== 'application-deno-deno-http') {
1818
1863
  sharedFiles.push({
1819
- content: createProjectTsconfig(),
1864
+ content: createProjectTsconfig(bootstrapPlan),
1820
1865
  path: 'tsconfig.json'
1821
1866
  }, {
1822
1867
  content: createProjectTsconfigBuild(),
@@ -1856,20 +1901,20 @@ function emitApplicationScaffoldFiles(options) {
1856
1901
  content: createMainFile(options),
1857
1902
  path: entrypointPath
1858
1903
  }, {
1859
- content: createHealthResponseDtoFile(),
1860
- path: 'src/health/health.response.dto.ts'
1904
+ content: createGreetingResponseDtoFile(),
1905
+ path: 'src/greeting/greeting.response.dto.ts'
1861
1906
  }, {
1862
- content: createHealthRepoFile(options.projectName, importSuffix),
1863
- path: 'src/health/health.repo.ts'
1907
+ content: createGreetingRepoFile(options.projectName, importSuffix),
1908
+ path: 'src/greeting/greeting.repo.ts'
1864
1909
  }, {
1865
- content: createHealthServiceFile(importSuffix),
1866
- path: 'src/health/health.service.ts'
1910
+ content: createGreetingServiceFile(importSuffix),
1911
+ path: 'src/greeting/greeting.service.ts'
1867
1912
  }, {
1868
- content: createHealthControllerFile(importSuffix),
1869
- path: 'src/health/health.controller.ts'
1913
+ content: createGreetingControllerFile(importSuffix),
1914
+ path: 'src/greeting/greeting.controller.ts'
1870
1915
  }, {
1871
- content: createHealthModuleFile(importSuffix),
1872
- path: 'src/health/health.module.ts'
1916
+ content: createGreetingModuleFile(importSuffix),
1917
+ path: 'src/greeting/greeting.module.ts'
1873
1918
  }];
1874
1919
  if (options.runtime === 'deno') {
1875
1920
  files.push({
@@ -1879,14 +1924,14 @@ function emitApplicationScaffoldFiles(options) {
1879
1924
  return files;
1880
1925
  }
1881
1926
  files.push({
1882
- content: createHealthRepoTestFile(),
1883
- path: 'src/health/health.repo.test.ts'
1927
+ content: createGreetingRepoTestFile(),
1928
+ path: 'src/greeting/greeting.repo.test.ts'
1884
1929
  }, {
1885
- content: createHealthServiceTestFile(),
1886
- path: 'src/health/health.service.test.ts'
1930
+ content: createGreetingServiceTestFile(),
1931
+ path: 'src/greeting/greeting.service.test.ts'
1887
1932
  }, {
1888
- content: createHealthControllerTestFile(),
1889
- path: 'src/health/health.controller.test.ts'
1933
+ content: createGreetingControllerTestFile(),
1934
+ path: 'src/greeting/greeting.controller.test.ts'
1890
1935
  }, {
1891
1936
  content: createAppTestFile(importSuffix),
1892
1937
  path: 'src/app.test.ts'
@@ -1942,29 +1987,29 @@ function emitScaffoldFilesForRecipe(options, recipeId) {
1942
1987
  content: createMixedMainFile(),
1943
1988
  path: 'src/main.ts'
1944
1989
  }, {
1945
- content: createHealthResponseDtoFile(),
1946
- path: 'src/health/health.response.dto.ts'
1990
+ content: createGreetingResponseDtoFile(),
1991
+ path: 'src/greeting/greeting.response.dto.ts'
1947
1992
  }, {
1948
- content: createHealthRepoFile(options.projectName),
1949
- path: 'src/health/health.repo.ts'
1993
+ content: createGreetingRepoFile(options.projectName),
1994
+ path: 'src/greeting/greeting.repo.ts'
1950
1995
  }, {
1951
- content: createHealthRepoTestFile(),
1952
- path: 'src/health/health.repo.test.ts'
1996
+ content: createGreetingRepoTestFile(),
1997
+ path: 'src/greeting/greeting.repo.test.ts'
1953
1998
  }, {
1954
- content: createHealthServiceFile(),
1955
- path: 'src/health/health.service.ts'
1999
+ content: createGreetingServiceFile(),
2000
+ path: 'src/greeting/greeting.service.ts'
1956
2001
  }, {
1957
- content: createHealthServiceTestFile(),
1958
- path: 'src/health/health.service.test.ts'
2002
+ content: createGreetingServiceTestFile(),
2003
+ path: 'src/greeting/greeting.service.test.ts'
1959
2004
  }, {
1960
- content: createHealthControllerFile(),
1961
- path: 'src/health/health.controller.ts'
2005
+ content: createGreetingControllerFile(),
2006
+ path: 'src/greeting/greeting.controller.ts'
1962
2007
  }, {
1963
- content: createHealthControllerTestFile(),
1964
- path: 'src/health/health.controller.test.ts'
2008
+ content: createGreetingControllerTestFile(),
2009
+ path: 'src/greeting/greeting.controller.test.ts'
1965
2010
  }, {
1966
- content: createHealthModuleFile(),
1967
- path: 'src/health/health.module.ts'
2011
+ content: createGreetingModuleFile(),
2012
+ path: 'src/greeting/greeting.module.ts'
1968
2013
  }, {
1969
2014
  content: createMathHandlerFile({
1970
2015
  transport: 'tcp'