@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.
- package/README.ko.md +40 -9
- package/README.md +40 -9
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +34 -4
- package/dist/commands/scripts.d.ts.map +1 -1
- package/dist/commands/scripts.js +187 -35
- package/dist/dev-runner/node-restart-runner.d.ts +5 -0
- package/dist/dev-runner/node-restart-runner.d.ts.map +1 -1
- package/dist/dev-runner/node-restart-runner.js +72 -3
- package/dist/dev-runner/preserve-color-tty.d.ts +2 -0
- package/dist/dev-runner/preserve-color-tty.d.ts.map +1 -0
- package/dist/dev-runner/preserve-color-tty.js +12 -0
- package/dist/new/scaffold.d.ts.map +1 -1
- package/dist/new/scaffold.js +173 -177
- package/dist/new/starter-profiles.d.ts.map +1 -1
- package/dist/new/starter-profiles.js +13 -13
- package/package.json +2 -2
package/dist/new/scaffold.js
CHANGED
|
@@ -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: '
|
|
151
|
+
build: 'bun build ./src/main.ts --outdir ./dist --target bun',
|
|
151
152
|
dev: 'fluo dev',
|
|
152
|
-
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: '
|
|
160
|
+
build: 'deno compile --allow-env --allow-net --output dist/app src/main.ts',
|
|
160
161
|
dev: 'fluo dev',
|
|
161
|
-
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: '
|
|
169
|
+
build: 'wrangler deploy --dry-run',
|
|
170
|
+
deploy: 'wrangler deploy',
|
|
169
171
|
dev: 'fluo dev',
|
|
170
|
-
|
|
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":
|
|
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 {
|
|
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/
|
|
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
|
-
|
|
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 \`
|
|
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
|
-
|
|
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 {
|
|
552
|
-
|
|
553
|
-
import { HealthModule } from './health/health.module';
|
|
554
|
+
import { HealthModule } from '@fluojs/runtime';
|
|
554
555
|
|
|
555
|
-
|
|
556
|
+
import { GreetingModule } from './greeting/greeting.module';
|
|
556
557
|
|
|
557
558
|
@Global()
|
|
558
559
|
@Module({
|
|
559
560
|
imports: [
|
|
560
|
-
|
|
561
|
-
|
|
561
|
+
GreetingModule,
|
|
562
|
+
HealthModule.forRoot(),
|
|
562
563
|
],
|
|
563
564
|
})
|
|
564
565
|
export class AppModule {}
|
|
565
566
|
`;
|
|
566
567
|
}
|
|
567
|
-
const processEnvValue = options.runtime === '
|
|
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 {
|
|
571
|
+
import { HealthModule } from '@fluojs/runtime';
|
|
571
572
|
|
|
572
|
-
import {
|
|
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
|
-
|
|
584
|
-
|
|
582
|
+
GreetingModule,
|
|
583
|
+
HealthModule.forRoot(),
|
|
585
584
|
],
|
|
586
585
|
})
|
|
587
586
|
export class AppModule {}
|
|
588
587
|
`;
|
|
589
588
|
}
|
|
590
|
-
function
|
|
591
|
-
return `export class
|
|
592
|
-
|
|
593
|
-
|
|
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
|
|
598
|
-
return `import type {
|
|
597
|
+
function createGreetingRepoFile(projectName, importSuffix = '') {
|
|
598
|
+
return `import type { GreetingResponseDto } from './greeting.response.dto${importSuffix}';
|
|
599
599
|
|
|
600
|
-
export class
|
|
601
|
-
|
|
600
|
+
export class GreetingRepo {
|
|
601
|
+
findGreeting(): GreetingResponseDto {
|
|
602
602
|
return {
|
|
603
|
-
|
|
604
|
-
|
|
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
|
|
611
|
+
function createGreetingRepoTestFile() {
|
|
611
612
|
return `import { describe, expect, it } from 'vitest';
|
|
612
613
|
|
|
613
|
-
import {
|
|
614
|
+
import { GreetingRepo } from './greeting.repo';
|
|
614
615
|
|
|
615
|
-
describe('
|
|
616
|
-
it('returns
|
|
617
|
-
const repo = new
|
|
618
|
-
expect(repo.
|
|
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
|
|
624
|
+
function createGreetingServiceFile(importSuffix = '') {
|
|
624
625
|
return `import { Inject } from '@fluojs/core';
|
|
625
|
-
import type {
|
|
626
|
+
import type { GreetingResponseDto } from './greeting.response.dto${importSuffix}';
|
|
626
627
|
|
|
627
|
-
import {
|
|
628
|
+
import { GreetingRepo } from './greeting.repo${importSuffix}';
|
|
628
629
|
|
|
629
|
-
@Inject(
|
|
630
|
-
export class
|
|
631
|
-
constructor(private readonly repo:
|
|
630
|
+
@Inject(GreetingRepo)
|
|
631
|
+
export class GreetingService {
|
|
632
|
+
constructor(private readonly repo: GreetingRepo) {}
|
|
632
633
|
|
|
633
|
-
|
|
634
|
-
return this.repo.
|
|
634
|
+
getGreeting(): GreetingResponseDto {
|
|
635
|
+
return this.repo.findGreeting();
|
|
635
636
|
}
|
|
636
637
|
}
|
|
637
638
|
`;
|
|
638
639
|
}
|
|
639
|
-
function
|
|
640
|
+
function createGreetingServiceTestFile() {
|
|
640
641
|
return `import { describe, expect, it } from 'vitest';
|
|
641
642
|
|
|
642
|
-
import {
|
|
643
|
-
import {
|
|
643
|
+
import { GreetingService } from './greeting.service';
|
|
644
|
+
import { GreetingRepo } from './greeting.repo';
|
|
644
645
|
|
|
645
|
-
class
|
|
646
|
-
|
|
647
|
-
return {
|
|
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('
|
|
652
|
+
describe('GreetingService', () => {
|
|
652
653
|
it('delegates to the repo', () => {
|
|
653
|
-
const service = new
|
|
654
|
-
expect(service.
|
|
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
|
|
660
|
-
return `import {
|
|
660
|
+
function createGreetingControllerFile(importSuffix = '') {
|
|
661
|
+
return `import { Inject } from '@fluojs/core';
|
|
661
662
|
import { Controller, Get } from '@fluojs/http';
|
|
662
663
|
|
|
663
|
-
import {
|
|
664
|
-
import {
|
|
665
|
-
|
|
666
|
-
ensureMetadataSymbol();
|
|
664
|
+
import { GreetingService } from './greeting.service${importSuffix}';
|
|
665
|
+
import { GreetingResponseDto } from './greeting.response.dto${importSuffix}';
|
|
667
666
|
|
|
668
|
-
@Inject(
|
|
669
|
-
@Controller('/
|
|
670
|
-
export class
|
|
671
|
-
constructor(private readonly service:
|
|
667
|
+
@Inject(GreetingService)
|
|
668
|
+
@Controller('/greeting')
|
|
669
|
+
export class GreetingController {
|
|
670
|
+
constructor(private readonly service: GreetingService) {}
|
|
672
671
|
|
|
673
672
|
@Get('/')
|
|
674
|
-
|
|
675
|
-
return this.service.
|
|
673
|
+
getGreeting(): GreetingResponseDto {
|
|
674
|
+
return this.service.getGreeting();
|
|
676
675
|
}
|
|
677
676
|
}
|
|
678
677
|
`;
|
|
679
678
|
}
|
|
680
|
-
function
|
|
679
|
+
function createGreetingControllerTestFile() {
|
|
681
680
|
return `import { describe, expect, it } from 'vitest';
|
|
682
681
|
|
|
683
|
-
import {
|
|
682
|
+
import { GreetingController } from './greeting.controller';
|
|
684
683
|
|
|
685
|
-
class
|
|
686
|
-
|
|
687
|
-
return {
|
|
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('
|
|
690
|
+
describe('GreetingController', () => {
|
|
692
691
|
it('delegates to the service', () => {
|
|
693
|
-
const controller = new
|
|
694
|
-
expect(controller.
|
|
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
|
|
698
|
+
function createGreetingModuleFile(importSuffix = '') {
|
|
700
699
|
return `import { Module } from '@fluojs/core';
|
|
701
700
|
|
|
702
|
-
import {
|
|
703
|
-
import {
|
|
704
|
-
import {
|
|
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: [
|
|
708
|
-
providers: [
|
|
706
|
+
controllers: [GreetingController],
|
|
707
|
+
providers: [GreetingRepo, GreetingService],
|
|
709
708
|
})
|
|
710
|
-
export class
|
|
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/
|
|
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/
|
|
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 {
|
|
1446
|
+
import { HealthModule } from '@fluojs/runtime';
|
|
1448
1447
|
|
|
1449
|
-
import {
|
|
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
|
-
|
|
1465
|
-
|
|
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,
|
|
1500
|
+
import { FluoFactory, HealthModule } from '@fluojs/runtime';
|
|
1503
1501
|
|
|
1504
|
-
import {
|
|
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
|
-
|
|
1589
|
-
|
|
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
|
|
1593
|
+
const greetingResponse = createResponse();
|
|
1598
1594
|
|
|
1599
1595
|
const microservice = await app.connectMicroservice();
|
|
1600
1596
|
|
|
1601
|
-
await Promise.all([app.startAllMicroservices(), app.dispatch(createRequest('/
|
|
1597
|
+
await Promise.all([app.startAllMicroservices(), app.dispatch(createRequest('/greeting/'), greetingResponse)]);
|
|
1602
1598
|
|
|
1603
|
-
expect(
|
|
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
|
|
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('/
|
|
1674
|
+
await app.dispatch(createRequest('/greeting/'), response);
|
|
1679
1675
|
|
|
1680
|
-
expect(response.body).toEqual({
|
|
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: '/
|
|
1707
|
-
body: {
|
|
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: '/
|
|
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 /
|
|
1773
|
+
throw new Error('Expected /greeting/ to return an object body.');
|
|
1778
1774
|
}
|
|
1779
1775
|
|
|
1780
|
-
const infoBody = infoResponse.body as {
|
|
1781
|
-
if (infoBody.
|
|
1782
|
-
throw new Error('Expected /
|
|
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:
|
|
1909
|
-
path: 'src/
|
|
1904
|
+
content: createGreetingResponseDtoFile(),
|
|
1905
|
+
path: 'src/greeting/greeting.response.dto.ts'
|
|
1910
1906
|
}, {
|
|
1911
|
-
content:
|
|
1912
|
-
path: 'src/
|
|
1907
|
+
content: createGreetingRepoFile(options.projectName, importSuffix),
|
|
1908
|
+
path: 'src/greeting/greeting.repo.ts'
|
|
1913
1909
|
}, {
|
|
1914
|
-
content:
|
|
1915
|
-
path: 'src/
|
|
1910
|
+
content: createGreetingServiceFile(importSuffix),
|
|
1911
|
+
path: 'src/greeting/greeting.service.ts'
|
|
1916
1912
|
}, {
|
|
1917
|
-
content:
|
|
1918
|
-
path: 'src/
|
|
1913
|
+
content: createGreetingControllerFile(importSuffix),
|
|
1914
|
+
path: 'src/greeting/greeting.controller.ts'
|
|
1919
1915
|
}, {
|
|
1920
|
-
content:
|
|
1921
|
-
path: 'src/
|
|
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:
|
|
1932
|
-
path: 'src/
|
|
1927
|
+
content: createGreetingRepoTestFile(),
|
|
1928
|
+
path: 'src/greeting/greeting.repo.test.ts'
|
|
1933
1929
|
}, {
|
|
1934
|
-
content:
|
|
1935
|
-
path: 'src/
|
|
1930
|
+
content: createGreetingServiceTestFile(),
|
|
1931
|
+
path: 'src/greeting/greeting.service.test.ts'
|
|
1936
1932
|
}, {
|
|
1937
|
-
content:
|
|
1938
|
-
path: 'src/
|
|
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:
|
|
1995
|
-
path: 'src/
|
|
1990
|
+
content: createGreetingResponseDtoFile(),
|
|
1991
|
+
path: 'src/greeting/greeting.response.dto.ts'
|
|
1996
1992
|
}, {
|
|
1997
|
-
content:
|
|
1998
|
-
path: 'src/
|
|
1993
|
+
content: createGreetingRepoFile(options.projectName),
|
|
1994
|
+
path: 'src/greeting/greeting.repo.ts'
|
|
1999
1995
|
}, {
|
|
2000
|
-
content:
|
|
2001
|
-
path: 'src/
|
|
1996
|
+
content: createGreetingRepoTestFile(),
|
|
1997
|
+
path: 'src/greeting/greeting.repo.test.ts'
|
|
2002
1998
|
}, {
|
|
2003
|
-
content:
|
|
2004
|
-
path: 'src/
|
|
1999
|
+
content: createGreetingServiceFile(),
|
|
2000
|
+
path: 'src/greeting/greeting.service.ts'
|
|
2005
2001
|
}, {
|
|
2006
|
-
content:
|
|
2007
|
-
path: 'src/
|
|
2002
|
+
content: createGreetingServiceTestFile(),
|
|
2003
|
+
path: 'src/greeting/greeting.service.test.ts'
|
|
2008
2004
|
}, {
|
|
2009
|
-
content:
|
|
2010
|
-
path: 'src/
|
|
2005
|
+
content: createGreetingControllerFile(),
|
|
2006
|
+
path: 'src/greeting/greeting.controller.ts'
|
|
2011
2007
|
}, {
|
|
2012
|
-
content:
|
|
2013
|
-
path: 'src/
|
|
2008
|
+
content: createGreetingControllerTestFile(),
|
|
2009
|
+
path: 'src/greeting/greeting.controller.test.ts'
|
|
2014
2010
|
}, {
|
|
2015
|
-
content:
|
|
2016
|
-
path: 'src/
|
|
2011
|
+
content: createGreetingModuleFile(),
|
|
2012
|
+
path: 'src/greeting/greeting.module.ts'
|
|
2017
2013
|
}, {
|
|
2018
2014
|
content: createMathHandlerFile({
|
|
2019
2015
|
transport: 'tcp'
|