@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.
- package/README.ko.md +97 -3
- package/README.md +97 -3
- package/dist/cli.d.ts +8 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +201 -4
- package/dist/commands/diagnostics.d.ts +15 -0
- package/dist/commands/diagnostics.d.ts.map +1 -0
- package/dist/commands/diagnostics.js +163 -0
- package/dist/commands/new.js +2 -2
- package/dist/commands/package-manager.d.ts +9 -0
- package/dist/commands/package-manager.d.ts.map +1 -0
- package/dist/commands/package-manager.js +63 -0
- package/dist/commands/package-workflow.d.ts +20 -0
- package/dist/commands/package-workflow.d.ts.map +1 -0
- package/dist/commands/package-workflow.js +137 -0
- package/dist/commands/scripts.d.ts +38 -0
- package/dist/commands/scripts.d.ts.map +1 -0
- package/dist/commands/scripts.js +570 -0
- package/dist/dev-runner/node-restart-runner.d.ts +55 -0
- package/dist/dev-runner/node-restart-runner.d.ts.map +1 -0
- package/dist/dev-runner/node-restart-runner.js +317 -0
- 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/generators/manifest.d.ts +24 -0
- package/dist/generators/manifest.d.ts.map +1 -1
- package/dist/generators/manifest.js +9 -0
- package/dist/generators/resource.d.ts +10 -0
- package/dist/generators/resource.d.ts.map +1 -0
- package/dist/generators/resource.js +23 -0
- package/dist/generators/templates/controller.ts.ejs +5 -1
- package/dist/generators/templates/request-dto.ts.ejs +3 -0
- package/dist/new/scaffold.d.ts.map +1 -1
- package/dist/new/scaffold.js +193 -148
- package/dist/new/starter-profiles.d.ts.map +1 -1
- package/dist/new/starter-profiles.js +13 -13
- package/dist/update-check.d.ts +1 -0
- package/dist/update-check.d.ts.map +1 -1
- package/dist/update-check.js +7 -5
- 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') {
|
|
@@ -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: '
|
|
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: '
|
|
159
|
-
dev: '
|
|
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
|
-
|
|
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: '
|
|
175
|
-
dev: '
|
|
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":
|
|
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 {
|
|
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/
|
|
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
|
-
|
|
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 \`
|
|
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
|
-
|
|
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 {
|
|
506
|
-
|
|
507
|
-
import { HealthModule } from './health/health.module';
|
|
554
|
+
import { HealthModule } from '@fluojs/runtime';
|
|
508
555
|
|
|
509
|
-
|
|
556
|
+
import { GreetingModule } from './greeting/greeting.module';
|
|
510
557
|
|
|
511
558
|
@Global()
|
|
512
559
|
@Module({
|
|
513
560
|
imports: [
|
|
514
|
-
|
|
515
|
-
|
|
561
|
+
GreetingModule,
|
|
562
|
+
HealthModule.forRoot(),
|
|
516
563
|
],
|
|
517
564
|
})
|
|
518
565
|
export class AppModule {}
|
|
519
566
|
`;
|
|
520
567
|
}
|
|
521
|
-
const processEnvValue = options.runtime === '
|
|
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 {
|
|
525
|
-
|
|
526
|
-
import { HealthModule } from './health/health.module${importSuffix}';
|
|
571
|
+
import { HealthModule } from '@fluojs/runtime';
|
|
527
572
|
|
|
528
|
-
|
|
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
|
-
|
|
538
|
-
|
|
582
|
+
GreetingModule,
|
|
583
|
+
HealthModule.forRoot(),
|
|
539
584
|
],
|
|
540
585
|
})
|
|
541
586
|
export class AppModule {}
|
|
542
587
|
`;
|
|
543
588
|
}
|
|
544
|
-
function
|
|
545
|
-
return `export class
|
|
546
|
-
|
|
547
|
-
|
|
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
|
|
552
|
-
return `import type {
|
|
597
|
+
function createGreetingRepoFile(projectName, importSuffix = '') {
|
|
598
|
+
return `import type { GreetingResponseDto } from './greeting.response.dto${importSuffix}';
|
|
553
599
|
|
|
554
|
-
export class
|
|
555
|
-
|
|
600
|
+
export class GreetingRepo {
|
|
601
|
+
findGreeting(): GreetingResponseDto {
|
|
556
602
|
return {
|
|
557
|
-
|
|
558
|
-
|
|
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
|
|
611
|
+
function createGreetingRepoTestFile() {
|
|
565
612
|
return `import { describe, expect, it } from 'vitest';
|
|
566
613
|
|
|
567
|
-
import {
|
|
614
|
+
import { GreetingRepo } from './greeting.repo';
|
|
568
615
|
|
|
569
|
-
describe('
|
|
570
|
-
it('returns
|
|
571
|
-
const repo = new
|
|
572
|
-
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) });
|
|
573
620
|
});
|
|
574
621
|
});
|
|
575
622
|
`;
|
|
576
623
|
}
|
|
577
|
-
function
|
|
624
|
+
function createGreetingServiceFile(importSuffix = '') {
|
|
578
625
|
return `import { Inject } from '@fluojs/core';
|
|
579
|
-
import type {
|
|
626
|
+
import type { GreetingResponseDto } from './greeting.response.dto${importSuffix}';
|
|
580
627
|
|
|
581
|
-
import {
|
|
628
|
+
import { GreetingRepo } from './greeting.repo${importSuffix}';
|
|
582
629
|
|
|
583
|
-
@Inject(
|
|
584
|
-
export class
|
|
585
|
-
constructor(private readonly repo:
|
|
630
|
+
@Inject(GreetingRepo)
|
|
631
|
+
export class GreetingService {
|
|
632
|
+
constructor(private readonly repo: GreetingRepo) {}
|
|
586
633
|
|
|
587
|
-
|
|
588
|
-
return this.repo.
|
|
634
|
+
getGreeting(): GreetingResponseDto {
|
|
635
|
+
return this.repo.findGreeting();
|
|
589
636
|
}
|
|
590
637
|
}
|
|
591
638
|
`;
|
|
592
639
|
}
|
|
593
|
-
function
|
|
640
|
+
function createGreetingServiceTestFile() {
|
|
594
641
|
return `import { describe, expect, it } from 'vitest';
|
|
595
642
|
|
|
596
|
-
import {
|
|
597
|
-
import {
|
|
643
|
+
import { GreetingService } from './greeting.service';
|
|
644
|
+
import { GreetingRepo } from './greeting.repo';
|
|
598
645
|
|
|
599
|
-
class
|
|
600
|
-
|
|
601
|
-
return {
|
|
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('
|
|
652
|
+
describe('GreetingService', () => {
|
|
606
653
|
it('delegates to the repo', () => {
|
|
607
|
-
const service = new
|
|
608
|
-
expect(service.
|
|
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
|
|
660
|
+
function createGreetingControllerFile(importSuffix = '') {
|
|
614
661
|
return `import { Inject } from '@fluojs/core';
|
|
615
662
|
import { Controller, Get } from '@fluojs/http';
|
|
616
663
|
|
|
617
|
-
import {
|
|
618
|
-
import {
|
|
664
|
+
import { GreetingService } from './greeting.service${importSuffix}';
|
|
665
|
+
import { GreetingResponseDto } from './greeting.response.dto${importSuffix}';
|
|
619
666
|
|
|
620
|
-
@Inject(
|
|
621
|
-
@Controller('/
|
|
622
|
-
export class
|
|
623
|
-
constructor(private readonly service:
|
|
667
|
+
@Inject(GreetingService)
|
|
668
|
+
@Controller('/greeting')
|
|
669
|
+
export class GreetingController {
|
|
670
|
+
constructor(private readonly service: GreetingService) {}
|
|
624
671
|
|
|
625
672
|
@Get('/')
|
|
626
|
-
|
|
627
|
-
return this.service.
|
|
673
|
+
getGreeting(): GreetingResponseDto {
|
|
674
|
+
return this.service.getGreeting();
|
|
628
675
|
}
|
|
629
676
|
}
|
|
630
677
|
`;
|
|
631
678
|
}
|
|
632
|
-
function
|
|
679
|
+
function createGreetingControllerTestFile() {
|
|
633
680
|
return `import { describe, expect, it } from 'vitest';
|
|
634
681
|
|
|
635
|
-
import {
|
|
682
|
+
import { GreetingController } from './greeting.controller';
|
|
636
683
|
|
|
637
|
-
class
|
|
638
|
-
|
|
639
|
-
return {
|
|
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('
|
|
690
|
+
describe('GreetingController', () => {
|
|
644
691
|
it('delegates to the service', () => {
|
|
645
|
-
const controller = new
|
|
646
|
-
expect(controller.
|
|
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
|
|
698
|
+
function createGreetingModuleFile(importSuffix = '') {
|
|
652
699
|
return `import { Module } from '@fluojs/core';
|
|
653
700
|
|
|
654
|
-
import {
|
|
655
|
-
import {
|
|
656
|
-
import {
|
|
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: [
|
|
660
|
-
providers: [
|
|
706
|
+
controllers: [GreetingController],
|
|
707
|
+
providers: [GreetingRepo, GreetingService],
|
|
661
708
|
})
|
|
662
|
-
export class
|
|
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/
|
|
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/
|
|
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 {
|
|
1446
|
+
import { HealthModule } from '@fluojs/runtime';
|
|
1399
1447
|
|
|
1400
|
-
import {
|
|
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
|
-
|
|
1416
|
-
|
|
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,
|
|
1500
|
+
import { FluoFactory, HealthModule } from '@fluojs/runtime';
|
|
1454
1501
|
|
|
1455
|
-
import {
|
|
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
|
-
|
|
1540
|
-
|
|
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
|
|
1593
|
+
const greetingResponse = createResponse();
|
|
1549
1594
|
|
|
1550
1595
|
const microservice = await app.connectMicroservice();
|
|
1551
1596
|
|
|
1552
|
-
await Promise.all([app.startAllMicroservices(), app.dispatch(createRequest('/
|
|
1597
|
+
await Promise.all([app.startAllMicroservices(), app.dispatch(createRequest('/greeting/'), greetingResponse)]);
|
|
1553
1598
|
|
|
1554
|
-
expect(
|
|
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
|
|
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('/
|
|
1674
|
+
await app.dispatch(createRequest('/greeting/'), response);
|
|
1630
1675
|
|
|
1631
|
-
expect(response.body).toEqual({
|
|
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: '/
|
|
1658
|
-
body: {
|
|
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: '/
|
|
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 /
|
|
1773
|
+
throw new Error('Expected /greeting/ to return an object body.');
|
|
1729
1774
|
}
|
|
1730
1775
|
|
|
1731
|
-
const infoBody = infoResponse.body as {
|
|
1732
|
-
if (infoBody.
|
|
1733
|
-
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.');
|
|
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:
|
|
1860
|
-
path: 'src/
|
|
1904
|
+
content: createGreetingResponseDtoFile(),
|
|
1905
|
+
path: 'src/greeting/greeting.response.dto.ts'
|
|
1861
1906
|
}, {
|
|
1862
|
-
content:
|
|
1863
|
-
path: 'src/
|
|
1907
|
+
content: createGreetingRepoFile(options.projectName, importSuffix),
|
|
1908
|
+
path: 'src/greeting/greeting.repo.ts'
|
|
1864
1909
|
}, {
|
|
1865
|
-
content:
|
|
1866
|
-
path: 'src/
|
|
1910
|
+
content: createGreetingServiceFile(importSuffix),
|
|
1911
|
+
path: 'src/greeting/greeting.service.ts'
|
|
1867
1912
|
}, {
|
|
1868
|
-
content:
|
|
1869
|
-
path: 'src/
|
|
1913
|
+
content: createGreetingControllerFile(importSuffix),
|
|
1914
|
+
path: 'src/greeting/greeting.controller.ts'
|
|
1870
1915
|
}, {
|
|
1871
|
-
content:
|
|
1872
|
-
path: 'src/
|
|
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:
|
|
1883
|
-
path: 'src/
|
|
1927
|
+
content: createGreetingRepoTestFile(),
|
|
1928
|
+
path: 'src/greeting/greeting.repo.test.ts'
|
|
1884
1929
|
}, {
|
|
1885
|
-
content:
|
|
1886
|
-
path: 'src/
|
|
1930
|
+
content: createGreetingServiceTestFile(),
|
|
1931
|
+
path: 'src/greeting/greeting.service.test.ts'
|
|
1887
1932
|
}, {
|
|
1888
|
-
content:
|
|
1889
|
-
path: 'src/
|
|
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:
|
|
1946
|
-
path: 'src/
|
|
1990
|
+
content: createGreetingResponseDtoFile(),
|
|
1991
|
+
path: 'src/greeting/greeting.response.dto.ts'
|
|
1947
1992
|
}, {
|
|
1948
|
-
content:
|
|
1949
|
-
path: 'src/
|
|
1993
|
+
content: createGreetingRepoFile(options.projectName),
|
|
1994
|
+
path: 'src/greeting/greeting.repo.ts'
|
|
1950
1995
|
}, {
|
|
1951
|
-
content:
|
|
1952
|
-
path: 'src/
|
|
1996
|
+
content: createGreetingRepoTestFile(),
|
|
1997
|
+
path: 'src/greeting/greeting.repo.test.ts'
|
|
1953
1998
|
}, {
|
|
1954
|
-
content:
|
|
1955
|
-
path: 'src/
|
|
1999
|
+
content: createGreetingServiceFile(),
|
|
2000
|
+
path: 'src/greeting/greeting.service.ts'
|
|
1956
2001
|
}, {
|
|
1957
|
-
content:
|
|
1958
|
-
path: 'src/
|
|
2002
|
+
content: createGreetingServiceTestFile(),
|
|
2003
|
+
path: 'src/greeting/greeting.service.test.ts'
|
|
1959
2004
|
}, {
|
|
1960
|
-
content:
|
|
1961
|
-
path: 'src/
|
|
2005
|
+
content: createGreetingControllerFile(),
|
|
2006
|
+
path: 'src/greeting/greeting.controller.ts'
|
|
1962
2007
|
}, {
|
|
1963
|
-
content:
|
|
1964
|
-
path: 'src/
|
|
2008
|
+
content: createGreetingControllerTestFile(),
|
|
2009
|
+
path: 'src/greeting/greeting.controller.test.ts'
|
|
1965
2010
|
}, {
|
|
1966
|
-
content:
|
|
1967
|
-
path: 'src/
|
|
2011
|
+
content: createGreetingModuleFile(),
|
|
2012
|
+
path: 'src/greeting/greeting.module.ts'
|
|
1968
2013
|
}, {
|
|
1969
2014
|
content: createMathHandlerFile({
|
|
1970
2015
|
transport: 'tcp'
|