@fluojs/cli 1.0.0-beta.6 → 1.0.0-beta.8
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 +10 -3
- package/README.md +10 -3
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +26 -2
- package/dist/commands/generate.d.ts.map +1 -1
- package/dist/commands/generate.js +14 -3
- package/dist/generator-types.d.ts +9 -0
- package/dist/generator-types.d.ts.map +1 -1
- package/dist/generators/e2e.d.ts +10 -0
- package/dist/generators/e2e.d.ts.map +1 -0
- package/dist/generators/e2e.js +21 -0
- package/dist/generators/manifest.d.ts +43 -9
- package/dist/generators/manifest.d.ts.map +1 -1
- package/dist/generators/manifest.js +22 -3
- package/dist/generators/module.d.ts +2 -2
- package/dist/generators/module.d.ts.map +1 -1
- package/dist/generators/module.js +12 -2
- package/dist/generators/resource.d.ts.map +1 -1
- package/dist/generators/resource.js +30 -2
- package/dist/generators/templates/e2e.test.ts.ejs +19 -0
- package/dist/generators/templates/module.slice.test.ts.ejs +13 -0
- package/dist/generators/templates/resource.module.ts.ejs +13 -0
- package/dist/generators/templates/resource.slice.test.ts.ejs +21 -0
- package/dist/new/scaffold.d.ts.map +1 -1
- package/dist/new/scaffold.js +60 -16
- package/package.json +3 -3
package/README.ko.md
CHANGED
|
@@ -76,6 +76,8 @@ pnpm dev
|
|
|
76
76
|
|
|
77
77
|
생성된 non-Deno starter의 `vite.config.ts`는 `@fluojs/vite`에서 `fluoDecoratorsPlugin()`을 import합니다. 따라서 decorator transform 업데이트는 각 신규 프로젝트에 inline 복사되는 대신 유지보수되는 Vite 패키지를 통해 전달됩니다.
|
|
78
78
|
|
|
79
|
+
생성된 non-Deno HTTP starter는 TDD-first Vitest 레이아웃을 사용합니다. 빠른 greeting unit test와 `greeting.slice.test.ts`는 `src/greeting/` 아래에 colocate하고, 앱 dispatch test는 `src/app.test.ts`에 유지하며, 기본 e2e 스타일 request-pipeline test는 `createTestApp({ rootModule })`와 `app.request(...).send()`를 사용해 `test/app.e2e.test.ts`에 둡니다. 생성된 `vitest.config.ts`는 `src/**/*.test.ts`와 `test/**/*.test.ts`를 모두 포함하고, package script는 `test`, `test:watch`, `test:cov`, `test:e2e`를 노출합니다. 기존 `src/app.e2e.test.ts` 테스트는 request helper를 바꾸지 않고 `test/app.e2e.test.ts`로 이동할 수 있습니다.
|
|
80
|
+
|
|
79
81
|
생성된 Node.js 애플리케이션 프로젝트에서 `fluo dev`는 기본적으로 fluo가 소유한 restart boundary를 거칩니다. 이 runner는 source와 주요 config 입력을 watch하고, atomic-save event burst를 debounce하며, restart 전에 파일 content hash를 비교하고, spawn하는 각 Node 앱 child process마다 `.env`를 로드하며, `node_modules`, `dist`, `.git`, `.fluo`, coverage, cache 폴더, editor swap file 같은 noisy output/cache 경로를 무시합니다. 파일 내용이 바뀌지 않은 Ctrl+S 저장은 앱을 재시작하지 않아야 합니다. 계획된 restart가 아닌 terminal 앱 child exit 또는 crash가 발생하면 runner는 watcher를 닫고, pending restart timer와 path를 비우며, `SIGINT`/`SIGTERM` handler를 등록 해제하고, child의 terminal code로 종료합니다. 이 동작은 full-process restart-on-watch이며 module-level HMR이 아닙니다. Config watch reload는 별도의 in-process config 관심사이고, 향후 HMR 작업은 어떤 모듈을 안전하게 hot-swap할 수 있는지 따로 문서화해야 합니다. 디버깅에 runtime-native Node watcher가 필요하면 `fluo dev --raw-watch` 또는 `FLUO_DEV_RAW_WATCH=1`을 사용하세요. 생성된 Bun/Deno/Workers 프로젝트는 기본적으로 watch/reload를 `bun --watch`, `deno run --watch`, `wrangler dev`에 위임합니다. 해당 프로젝트에서 fluo 소유 restart runner로 되돌리려면 `fluo dev --runner fluo` 또는 `FLUO_DEV_RUNNER=fluo`를 사용하고, 그 runner에 추가 ignore 경로가 필요하면 `FLUO_DEV_WATCH_IGNORE=path,pattern`으로 지정하세요.
|
|
80
82
|
|
|
81
83
|
`fluo new`는 같은 Node 기반 설치/빌드 흐름 위에서 Node.js + Fastify, Express, raw Node.js HTTP 애플리케이션 스타터를 제공합니다.
|
|
@@ -135,18 +137,23 @@ feature slice를 생성합니다. 일부 schematic은 모듈에 자동 등록되
|
|
|
135
137
|
|
|
136
138
|
```bash
|
|
137
139
|
fluo generate module users
|
|
140
|
+
fluo generate module users --with-test
|
|
138
141
|
fluo generate resource users
|
|
142
|
+
fluo generate resource users --with-slice-test
|
|
143
|
+
fluo generate e2e users
|
|
139
144
|
fluo generate controller users
|
|
140
145
|
fluo generate service users
|
|
141
146
|
fluo generate request-dto users CreateUser
|
|
142
147
|
fluo generate service users --dry-run
|
|
143
148
|
```
|
|
144
149
|
|
|
145
|
-
지원되는 generator kind와 alias는 `controller`/`co`, `guard`/`gu`, `interceptor`/`in`, `middleware`/`mi`, `module`/`mo`, `repo`/`repository`, `request-dto`/`req`, `resource`/`resrc`, `response-dto`/`res`, `service`/`s`입니다.
|
|
150
|
+
지원되는 generator kind와 alias는 `controller`/`co`, `e2e`, `guard`/`gu`, `interceptor`/`in`, `middleware`/`mi`, `module`/`mo`, `repo`/`repository`, `request-dto`/`req`, `resource`/`resrc`, `response-dto`/`res`, `service`/`s`입니다.
|
|
151
|
+
|
|
152
|
+
자동 등록되는 generator는 `controller`, `service`, `repo`, `guard`, `interceptor`, `middleware`입니다. 파일만 생성하는 generator는 `e2e`, `module`, `request-dto`, `response-dto`, `resource`입니다.
|
|
146
153
|
|
|
147
|
-
|
|
154
|
+
`fluo generate module <name> --with-test`는 작성한 module을 `createTestingModule({ rootModule })`로 컴파일하는 `*.slice.test.ts`를 추가합니다. `fluo generate resource <name>`는 module, controller, service, repository, request DTO, response DTO, test를 포함하는 완전한 feature slice를 생성합니다. `--with-slice-test`를 추가하면 provider override와 service resolution을 보여 주는 resource-level slice test도 포함합니다. 생성된 resource module은 parent module에 자동으로 연결하지 않으므로, slice를 활성화할 준비가 되었을 때 직접 import하세요.
|
|
148
155
|
|
|
149
|
-
`fluo generate
|
|
156
|
+
`fluo generate e2e <name>`는 generated starter와 같은 app-level test 영역에 request-pipeline test를 두도록 `createTestApp({ rootModule: AppModule })`을 사용하는 `test/<name>.e2e.test.ts`를 작성합니다. 생성된 unit test는 직접 class 동작 검증에, slice test는 DI wiring과 override 검증에, e2e test는 virtual app을 통과하는 route, guard, interceptor, DTO validation, response writing 검증에 사용하세요.
|
|
150
157
|
|
|
151
158
|
Request DTO 생성은 feature 디렉터리와 DTO 클래스 이름을 분리해서 받습니다. 따라서 `CreateUser`, `UpdateUser` 같은 여러 입력 계약을 같은 `src/users/` 슬라이스 안에 둘 수 있습니다.
|
|
152
159
|
|
package/README.md
CHANGED
|
@@ -76,6 +76,8 @@ Generated Node.js `dev`, `build`, and `start` package scripts delegate to `fluo
|
|
|
76
76
|
|
|
77
77
|
Generated non-Deno starter `vite.config.ts` files import `fluoDecoratorsPlugin()` from `@fluojs/vite`, so decorator transform updates ship through the maintained Vite package instead of being copied inline into every new project.
|
|
78
78
|
|
|
79
|
+
Generated non-Deno HTTP starters use a TDD-first Vitest layout: fast greeting unit tests and `greeting.slice.test.ts` stay colocated under `src/greeting/`, app dispatch tests stay in `src/app.test.ts`, and the default e2e-style request-pipeline tests live in `test/app.e2e.test.ts` with `createTestApp({ rootModule })` plus `app.request(...).send()`. The generated `vitest.config.ts` includes both `src/**/*.test.ts` and `test/**/*.test.ts`, while generated package scripts expose `test`, `test:watch`, `test:cov`, and `test:e2e`; existing `src/app.e2e.test.ts` tests can move to `test/app.e2e.test.ts` without changing the request helper.
|
|
80
|
+
|
|
79
81
|
For generated Node.js application projects, `fluo dev` runs through a fluo-owned restart boundary by default. The runner watches source and common config inputs, debounces atomic-save bursts, hashes file content before restarting, loads `.env` for each Node app child process it spawns, and ignores noisy output/cache paths such as `node_modules`, `dist`, `.git`, `.fluo`, coverage, cache folders, and editor swap files. Pressing Ctrl+S without changing file content should not restart the app. On terminal app child exit or crash outside a planned restart, the runner closes watchers, clears the pending restart timer and paths, unregisters its `SIGINT`/`SIGTERM` handlers, and exits with the child terminal code. This is full-process restart-on-watch, not module-level HMR; config watch reloads are a separate in-process config concern, and future HMR work must document which modules can be safely hot-swapped. Use `fluo dev --raw-watch` or `FLUO_DEV_RAW_WATCH=1` when you need the runtime-native Node watcher for debugging. Generated Bun/Deno/Workers projects delegate watch/reload behavior to `bun --watch`, `deno run --watch`, or `wrangler dev` by default; use `fluo dev --runner fluo` or `FLUO_DEV_RUNNER=fluo` when those projects should return to the fluo-owned restart runner, and use `FLUO_DEV_WATCH_IGNORE=path,pattern` to add extra ignored paths for that runner.
|
|
80
82
|
|
|
81
83
|
`fluo new` supports Node.js + Fastify, Express, and raw Node.js HTTP application starters on the same Node-oriented install/build flow:
|
|
@@ -135,18 +137,23 @@ Generate a feature slice; some schematics auto-register in the module, while oth
|
|
|
135
137
|
|
|
136
138
|
```bash
|
|
137
139
|
fluo generate module users
|
|
140
|
+
fluo generate module users --with-test
|
|
138
141
|
fluo generate resource users
|
|
142
|
+
fluo generate resource users --with-slice-test
|
|
143
|
+
fluo generate e2e users
|
|
139
144
|
fluo generate controller users
|
|
140
145
|
fluo generate service users
|
|
141
146
|
fluo generate request-dto users CreateUser
|
|
142
147
|
fluo generate service users --dry-run
|
|
143
148
|
```
|
|
144
149
|
|
|
145
|
-
Supported generator kinds and aliases are `controller`/`co`, `guard`/`gu`, `interceptor`/`in`, `middleware`/`mi`, `module`/`mo`, `repo`/`repository`, `request-dto`/`req`, `resource`/`resrc`, `response-dto`/`res`, and `service`/`s`.
|
|
150
|
+
Supported generator kinds and aliases are `controller`/`co`, `e2e`, `guard`/`gu`, `interceptor`/`in`, `middleware`/`mi`, `module`/`mo`, `repo`/`repository`, `request-dto`/`req`, `resource`/`resrc`, `response-dto`/`res`, and `service`/`s`.
|
|
151
|
+
|
|
152
|
+
Auto-registered generators are `controller`, `service`, `repo`, `guard`, `interceptor`, and `middleware`. Files-only generators are `e2e`, `module`, `request-dto`, `response-dto`, and `resource`.
|
|
146
153
|
|
|
147
|
-
|
|
154
|
+
`fluo generate module <name> --with-test` adds a `*.slice.test.ts` that compiles the authored module with `createTestingModule({ rootModule })`. `fluo generate resource <name>` creates a complete feature slice with a module, controller, service, repository, request DTO, response DTO, and tests; add `--with-slice-test` to include a resource-level slice test that demonstrates provider override and service resolution. It does not wire the resource module into a parent module automatically; import the generated module when you are ready to activate the slice.
|
|
148
155
|
|
|
149
|
-
`fluo generate
|
|
156
|
+
`fluo generate e2e <name>` writes `test/<name>.e2e.test.ts` with `createTestApp({ rootModule: AppModule })` so request-pipeline tests live in the same app-level test area as generated starters. Use generated unit tests for direct class behavior, slice tests for DI wiring and overrides, and e2e tests for routes, guards, interceptors, DTO validation, and response writing through the virtual app.
|
|
150
157
|
|
|
151
158
|
Request DTO generation accepts the feature directory separately from the DTO class name, so multiple input contracts such as `CreateUser` and `UpdateUser` can live inside the same `src/users/` slice.
|
|
152
159
|
|
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,4BAA4B,EAAmC,MAAM,uBAAuB,CAAC;AAE3G,OAAO,EAAE,KAAK,wBAAwB,EAA2B,MAAM,mBAAmB,CAAC;AAO3F,OAAO,EAAE,KAAK,4BAA4B,EAA6C,MAAM,mBAAmB,CAAC;AAEjH,KAAK,SAAS,GAAG;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;CACjC,CAAC;AAEF,KAAK,iBAAiB,GAAG;IACvB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,EAAE,CAAC,EAAE,OAAO,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,aAAa,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC,CAAC;IACrF,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;QAAC,MAAM,CAAC,EAAE,SAAS,CAAC;QAAC,KAAK,EAAE,SAAS,GAAG,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,SAAS,CAAA;KAAE,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACzL,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,KAAK,CAAC,EAAE,iBAAiB,CAAC;IAC1B,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,WAAW,CAAC,EAAE,KAAK,GAAG,4BAA4B,CAAC;CACpD;
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,4BAA4B,EAAmC,MAAM,uBAAuB,CAAC;AAE3G,OAAO,EAAE,KAAK,wBAAwB,EAA2B,MAAM,mBAAmB,CAAC;AAO3F,OAAO,EAAE,KAAK,4BAA4B,EAA6C,MAAM,mBAAmB,CAAC;AAEjH,KAAK,SAAS,GAAG;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;CACjC,CAAC;AAEF,KAAK,iBAAiB,GAAG;IACvB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,EAAE,CAAC,EAAE,OAAO,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,aAAa,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC,CAAC;IACrF,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;QAAC,MAAM,CAAC,EAAE,SAAS,CAAC;QAAC,KAAK,EAAE,SAAS,GAAG,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,SAAS,CAAA;KAAE,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACzL,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,KAAK,CAAC,EAAE,iBAAiB,CAAC;IAC1B,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,WAAW,CAAC,EAAE,KAAK,GAAG,4BAA4B,CAAC;CACpD;AAwZD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,MAAM,CAC1B,IAAI,WAAwB,EAC5B,OAAO,GAAE,iBAAiB,GAAG,wBAAwB,GAAG,4BAAiC,GACxF,OAAO,CAAC,MAAM,CAAC,CAqNjB"}
|
package/dist/cli.js
CHANGED
|
@@ -134,7 +134,7 @@ function readCliVersion() {
|
|
|
134
134
|
return manifest.version;
|
|
135
135
|
}
|
|
136
136
|
function generateUsage() {
|
|
137
|
-
return ['Usage: fluo generate|g <kind> <name> [options]', ' fluo generate|g request-dto|req <feature> <name> [options]', '', 'Schematics', renderHelpTable(GENERATE_KIND_HELP, [{
|
|
137
|
+
return ['Usage: fluo generate|g <kind> <name> [options]', ' fluo generate|g request-dto|req <feature> <name> [options]', ' fluo generate|g e2e <name> [options]', '', 'Schematics', renderHelpTable(GENERATE_KIND_HELP, [{
|
|
138
138
|
header: 'Schematic',
|
|
139
139
|
render: entry => entry.schematic
|
|
140
140
|
}, {
|
|
@@ -206,6 +206,8 @@ function parseGenerateArgs(argv) {
|
|
|
206
206
|
let seenForce = false;
|
|
207
207
|
let seenDryRun = false;
|
|
208
208
|
let seenTargetDirectory = false;
|
|
209
|
+
let seenWithSliceTest = false;
|
|
210
|
+
let seenWithTest = false;
|
|
209
211
|
for (let index = 0; index < optionArgs.length; index += 1) {
|
|
210
212
|
const option = optionArgs[index];
|
|
211
213
|
const next = optionArgs[index + 1];
|
|
@@ -243,8 +245,30 @@ function parseGenerateArgs(argv) {
|
|
|
243
245
|
seenDryRun = true;
|
|
244
246
|
continue;
|
|
245
247
|
}
|
|
248
|
+
if (option === '--with-test') {
|
|
249
|
+
if (seenWithTest) {
|
|
250
|
+
throw new Error('Duplicate --with-test option.');
|
|
251
|
+
}
|
|
252
|
+
parsedOptions.withTest = true;
|
|
253
|
+
seenWithTest = true;
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
if (option === '--with-slice-test') {
|
|
257
|
+
if (seenWithSliceTest) {
|
|
258
|
+
throw new Error('Duplicate --with-slice-test option.');
|
|
259
|
+
}
|
|
260
|
+
parsedOptions.withSliceTest = true;
|
|
261
|
+
seenWithSliceTest = true;
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
246
264
|
throw new Error(`Unknown option: ${option}`);
|
|
247
265
|
}
|
|
266
|
+
if (parsedOptions.withTest && kind !== 'module') {
|
|
267
|
+
throw new Error('--with-test is only supported for module generation. Use --with-slice-test for resource generation.');
|
|
268
|
+
}
|
|
269
|
+
if (parsedOptions.withSliceTest && kind !== 'resource') {
|
|
270
|
+
throw new Error('--with-slice-test is only supported for resource generation.');
|
|
271
|
+
}
|
|
248
272
|
return {
|
|
249
273
|
kind,
|
|
250
274
|
name,
|
|
@@ -472,7 +496,7 @@ export async function runCli(argv = process.argv.slice(2), runtime = {}) {
|
|
|
472
496
|
return runInfoCommand(parsedCommand.argv, commandRuntime);
|
|
473
497
|
}
|
|
474
498
|
if (parsedCommand.command === 'build' || parsedCommand.command === 'dev' || parsedCommand.command === 'start') {
|
|
475
|
-
return runScriptCommand(parsedCommand.command, parsedCommand.argv, commandRuntime);
|
|
499
|
+
return await runScriptCommand(parsedCommand.command, parsedCommand.argv, commandRuntime);
|
|
476
500
|
}
|
|
477
501
|
if (parsedCommand.command === 'upgrade') {
|
|
478
502
|
return runUpgradeCommand(parsedCommand.argv, commandRuntime);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../src/commands/generate.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,KAAK,EAAE,sBAAsB,EAAkB,MAAM,2BAA2B,CAAC;AAexF,8EAA8E;AAC9E,MAAM,MAAM,kBAAkB,GAAG,QAAQ,GAAG,eAAe,GAAG,kBAAkB,GAAG,eAAe,GAAG,WAAW,GAAG,MAAM,GAAG,WAAW,CAAC;AAExI,0FAA0F;AAC1F,MAAM,MAAM,iBAAiB,GAAG;IAC9B,oCAAoC;IACpC,MAAM,EAAE,kBAAkB,CAAC;IAC3B,gDAAgD;IAChD,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;
|
|
1
|
+
{"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../src/commands/generate.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,KAAK,EAAE,sBAAsB,EAAkB,MAAM,2BAA2B,CAAC;AAexF,8EAA8E;AAC9E,MAAM,MAAM,kBAAkB,GAAG,QAAQ,GAAG,eAAe,GAAG,kBAAkB,GAAG,eAAe,GAAG,WAAW,GAAG,MAAM,GAAG,WAAW,CAAC;AAExI,0FAA0F;AAC1F,MAAM,MAAM,iBAAiB,GAAG;IAC9B,oCAAoC;IACpC,MAAM,EAAE,kBAAkB,CAAC;IAC3B,gDAAgD;IAChD,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAyIF;;;;;;GAMG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,iBAAiB,EAAE,CAAC;IAClC,cAAc,EAAE,sBAAsB,CAAC,gBAAgB,CAAC,CAAC;CAC1D,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,OAAO,GAAE,eAAoB,GAAG,cAAc,CAgE1I"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
-
import { join, normalize, resolve } from 'node:path';
|
|
2
|
+
import { basename, dirname, join, normalize, relative, resolve, sep } from 'node:path';
|
|
3
3
|
import { findGeneratorDefinition } from '../generators/manifest.js';
|
|
4
4
|
import { ensureModuleImport, generateModuleFiles, registerInModule } from '../generators/module.js';
|
|
5
5
|
import { toKebabCase, toPascalCase, toPlural } from '../generators/utils.js';
|
|
@@ -57,14 +57,25 @@ function planModuleWrite(modulePath, content) {
|
|
|
57
57
|
path: modulePath
|
|
58
58
|
};
|
|
59
59
|
}
|
|
60
|
-
function createGeneratorOptions(kind, domainDirectory, kebab, options) {
|
|
60
|
+
function createGeneratorOptions(kind, domainDirectory, kebab, options, resolvedBase) {
|
|
61
61
|
return {
|
|
62
62
|
...options,
|
|
63
|
+
e2eRootModuleImport: options.e2eRootModuleImport ?? (kind === 'e2e' ? resolveE2eRootModuleImport(domainDirectory, resolvedBase) : undefined),
|
|
63
64
|
hasRepo: options.hasRepo ?? (kind === 'service' ? existsSync(join(domainDirectory, `${kebab}.repo.ts`)) : undefined),
|
|
64
65
|
hasService: options.hasService ?? (kind === 'controller' ? existsSync(join(domainDirectory, `${kebab}.service.ts`)) : undefined)
|
|
65
66
|
};
|
|
66
67
|
}
|
|
68
|
+
function toImportSpecifier(path) {
|
|
69
|
+
const normalized = path.split(sep).join('/');
|
|
70
|
+
return normalized.startsWith('.') ? normalized : `./${normalized}`;
|
|
71
|
+
}
|
|
72
|
+
function resolveE2eRootModuleImport(domainDirectory, resolvedBase) {
|
|
73
|
+
return toImportSpecifier(relative(domainDirectory, join(resolvedBase, 'app.module')));
|
|
74
|
+
}
|
|
67
75
|
function resolveDomainDirectory(kind, resolvedBase, kebab, options) {
|
|
76
|
+
if (kind === 'e2e') {
|
|
77
|
+
return basename(resolvedBase) === 'src' ? join(dirname(resolvedBase), 'test') : join(resolvedBase, 'test');
|
|
78
|
+
}
|
|
68
79
|
if (kind === 'request-dto' && options.targetFeature !== undefined) {
|
|
69
80
|
const normalizedFeature = options.targetFeature.trim();
|
|
70
81
|
const featureKebab = assertValidResourceName(normalizedFeature);
|
|
@@ -154,7 +165,7 @@ export function runGenerateCommand(kind, name, baseDirectory, options = {}) {
|
|
|
154
165
|
const generator = findGeneratorDefinition(kind);
|
|
155
166
|
const resolvedBase = resolve(baseDirectory);
|
|
156
167
|
const domainDirectory = resolveDomainDirectory(kind, resolvedBase, kebab, options);
|
|
157
|
-
const generatorOptions = createGeneratorOptions(kind, domainDirectory, kebab, options);
|
|
168
|
+
const generatorOptions = createGeneratorOptions(kind, domainDirectory, kebab, options, resolvedBase);
|
|
158
169
|
const files = generator.factory(normalizedName, generatorOptions);
|
|
159
170
|
const moduleRegistration = 'moduleRegistration' in generator ? generator.moduleRegistration : undefined;
|
|
160
171
|
const moduleUpdate = moduleRegistration ? prepareModuleUpdate(domainDirectory, normalizedName, kind, moduleRegistration.classSuffix, moduleRegistration.arrayKey) : undefined;
|
|
@@ -7,13 +7,22 @@ export interface GeneratedFile {
|
|
|
7
7
|
export interface GenerateOptions {
|
|
8
8
|
/** Preview planned writes and module updates without mutating the workspace. */
|
|
9
9
|
dryRun?: boolean;
|
|
10
|
+
/** Import specifier used by generated e2e tests to load the application root module. */
|
|
11
|
+
e2eRootModuleImport?: string;
|
|
12
|
+
/** Overwrite existing generated files instead of skipping them. */
|
|
10
13
|
force?: boolean;
|
|
14
|
+
/** Indicates that a repository sibling exists and should be imported by service templates. */
|
|
11
15
|
hasRepo?: boolean;
|
|
16
|
+
/** Indicates that a service sibling exists and should be imported by controller templates. */
|
|
12
17
|
hasService?: boolean;
|
|
13
18
|
/**
|
|
14
19
|
* Feature or slice directory that should receive feature-local files such as request DTOs.
|
|
15
20
|
*/
|
|
16
21
|
targetFeature?: string;
|
|
22
|
+
/** Emit resource-level slice test coverage with provider override examples. */
|
|
23
|
+
withSliceTest?: boolean;
|
|
24
|
+
/** Emit module-level test coverage for schematics that support companion tests. */
|
|
25
|
+
withTest?: boolean;
|
|
17
26
|
}
|
|
18
27
|
/**
|
|
19
28
|
* Produces the in-memory files for one schematic/resource pair.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generator-types.d.ts","sourceRoot":"","sources":["../src/generator-types.ts"],"names":[],"mappings":"AAAA,sFAAsF;AACtF,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,iIAAiI;AACjI,MAAM,WAAW,eAAe;IAC9B,gFAAgF;IAChF,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"generator-types.d.ts","sourceRoot":"","sources":["../src/generator-types.ts"],"names":[],"mappings":"AAAA,sFAAsF;AACtF,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,iIAAiI;AACjI,MAAM,WAAW,eAAe;IAC9B,gFAAgF;IAChF,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,wFAAwF;IACxF,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,mEAAmE;IACnE,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,8FAA8F;IAC9F,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,8FAA8F;IAC9F,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,+EAA+E;IAC/E,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,mFAAmF;IACnF,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,KAAK,aAAa,EAAE,CAAC;AAE5F,oFAAoF;AACpF,MAAM,WAAW,qBAAqB;IACpC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,gBAAgB,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,kGAAkG;AAClG,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,SAAS,GAAG,MAAM,CAAC;CAC3B"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { GenerateOptions, GeneratedFile } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Generate an app-level e2e-style test file.
|
|
4
|
+
*
|
|
5
|
+
* @param name The e2e test name.
|
|
6
|
+
* @param options The generation options.
|
|
7
|
+
* @returns The generated e2e test files.
|
|
8
|
+
*/
|
|
9
|
+
export declare function generateE2eFiles(name: string, options?: GenerateOptions): GeneratedFile[];
|
|
10
|
+
//# sourceMappingURL=e2e.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"e2e.d.ts","sourceRoot":"","sources":["../../src/generators/e2e.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAKlE;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,eAAoB,GAAG,aAAa,EAAE,CAU7F"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { renderTemplate } from './render.js';
|
|
2
|
+
import { toKebabCase } from './utils.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Generate an app-level e2e-style test file.
|
|
6
|
+
*
|
|
7
|
+
* @param name The e2e test name.
|
|
8
|
+
* @param options The generation options.
|
|
9
|
+
* @returns The generated e2e test files.
|
|
10
|
+
*/
|
|
11
|
+
export function generateE2eFiles(name, options = {}) {
|
|
12
|
+
const kebab = toKebabCase(name);
|
|
13
|
+
const rootModuleImport = options.e2eRootModuleImport ?? '../src/app.module';
|
|
14
|
+
return [{
|
|
15
|
+
content: renderTemplate('e2e.test.ts.ejs', {
|
|
16
|
+
kebab,
|
|
17
|
+
rootModuleImport
|
|
18
|
+
}),
|
|
19
|
+
path: `${kebab}.e2e.test.ts`
|
|
20
|
+
}];
|
|
21
|
+
}
|
|
@@ -40,6 +40,16 @@ export declare const generatorOptionSchemas: readonly [{
|
|
|
40
40
|
readonly description: "Preview planned writes, skips, and module wiring without touching files.";
|
|
41
41
|
readonly name: "--dry-run";
|
|
42
42
|
readonly value: "boolean";
|
|
43
|
+
}, {
|
|
44
|
+
readonly aliases: readonly [];
|
|
45
|
+
readonly description: "Emit a module-level slice test when generating module schematics.";
|
|
46
|
+
readonly name: "--with-test";
|
|
47
|
+
readonly value: "boolean";
|
|
48
|
+
}, {
|
|
49
|
+
readonly aliases: readonly [];
|
|
50
|
+
readonly description: "Emit the resource slice test with createTestingModule provider override coverage.";
|
|
51
|
+
readonly name: "--with-slice-test";
|
|
52
|
+
readonly value: "boolean";
|
|
43
53
|
}, {
|
|
44
54
|
readonly aliases: readonly ["-h"];
|
|
45
55
|
readonly description: "Show help for the generate command.";
|
|
@@ -61,6 +71,14 @@ export declare const builtInGeneratorCollection: {
|
|
|
61
71
|
readonly nextStepHint: "Run 'pnpm typecheck' to verify module wiring, then add route handlers.";
|
|
62
72
|
readonly schematic: "controller";
|
|
63
73
|
readonly wiringBehavior: "auto-registered";
|
|
74
|
+
}, {
|
|
75
|
+
readonly aliases: readonly [];
|
|
76
|
+
readonly description: "Generate an app-level e2e-style test with createTestApp({ rootModule }).";
|
|
77
|
+
readonly factory: (name: string, options: import("../generator-types.js").GenerateOptions | undefined) => import("../generator-types.js").GeneratedFile[];
|
|
78
|
+
readonly kind: "e2e";
|
|
79
|
+
readonly nextStepHint: "Run 'pnpm test:e2e' after wiring the route into AppModule, or update the generated path expectation first.";
|
|
80
|
+
readonly schematic: "e2e";
|
|
81
|
+
readonly wiringBehavior: "files-only";
|
|
64
82
|
}, {
|
|
65
83
|
readonly aliases: readonly ["gu"];
|
|
66
84
|
readonly description: "Generate a guard (auto-registered as a provider in the module).";
|
|
@@ -99,8 +117,8 @@ export declare const builtInGeneratorCollection: {
|
|
|
99
117
|
readonly wiringBehavior: "auto-registered";
|
|
100
118
|
}, {
|
|
101
119
|
readonly aliases: readonly ["mo"];
|
|
102
|
-
readonly description: "Generate a standalone module (
|
|
103
|
-
readonly factory: (name: string) => import("../generator-types.js").GeneratedFile[];
|
|
120
|
+
readonly description: "Generate a standalone module (add --with-test for a module graph slice test).";
|
|
121
|
+
readonly factory: (name: string, options: import("../generator-types.js").GenerateOptions | undefined) => import("../generator-types.js").GeneratedFile[];
|
|
104
122
|
readonly kind: "module";
|
|
105
123
|
readonly nextStepHint: "Import the new module in a parent module's imports array, then run 'pnpm typecheck'.";
|
|
106
124
|
readonly schematic: "module";
|
|
@@ -128,7 +146,7 @@ export declare const builtInGeneratorCollection: {
|
|
|
128
146
|
readonly wiringBehavior: "files-only";
|
|
129
147
|
}, {
|
|
130
148
|
readonly aliases: readonly ["resrc"];
|
|
131
|
-
readonly description: "Generate a full resource slice with module, controller, service, repository, and
|
|
149
|
+
readonly description: "Generate a full resource slice with module, controller, service, repository, DTO stubs, and optional --with-slice-test.";
|
|
132
150
|
readonly factory: (name: string, options: import("../generator-types.js").GenerateOptions | undefined) => import("../generator-types.js").GeneratedFile[];
|
|
133
151
|
readonly kind: "resource";
|
|
134
152
|
readonly nextStepHint: "Run 'pnpm typecheck' and wire the resource module into a parent module when ready.";
|
|
@@ -173,6 +191,14 @@ export declare const generatorCollections: readonly [{
|
|
|
173
191
|
readonly nextStepHint: "Run 'pnpm typecheck' to verify module wiring, then add route handlers.";
|
|
174
192
|
readonly schematic: "controller";
|
|
175
193
|
readonly wiringBehavior: "auto-registered";
|
|
194
|
+
}, {
|
|
195
|
+
readonly aliases: readonly [];
|
|
196
|
+
readonly description: "Generate an app-level e2e-style test with createTestApp({ rootModule }).";
|
|
197
|
+
readonly factory: (name: string, options: import("../generator-types.js").GenerateOptions | undefined) => import("../generator-types.js").GeneratedFile[];
|
|
198
|
+
readonly kind: "e2e";
|
|
199
|
+
readonly nextStepHint: "Run 'pnpm test:e2e' after wiring the route into AppModule, or update the generated path expectation first.";
|
|
200
|
+
readonly schematic: "e2e";
|
|
201
|
+
readonly wiringBehavior: "files-only";
|
|
176
202
|
}, {
|
|
177
203
|
readonly aliases: readonly ["gu"];
|
|
178
204
|
readonly description: "Generate a guard (auto-registered as a provider in the module).";
|
|
@@ -211,8 +237,8 @@ export declare const generatorCollections: readonly [{
|
|
|
211
237
|
readonly wiringBehavior: "auto-registered";
|
|
212
238
|
}, {
|
|
213
239
|
readonly aliases: readonly ["mo"];
|
|
214
|
-
readonly description: "Generate a standalone module (
|
|
215
|
-
readonly factory: (name: string) => import("../generator-types.js").GeneratedFile[];
|
|
240
|
+
readonly description: "Generate a standalone module (add --with-test for a module graph slice test).";
|
|
241
|
+
readonly factory: (name: string, options: import("../generator-types.js").GenerateOptions | undefined) => import("../generator-types.js").GeneratedFile[];
|
|
216
242
|
readonly kind: "module";
|
|
217
243
|
readonly nextStepHint: "Import the new module in a parent module's imports array, then run 'pnpm typecheck'.";
|
|
218
244
|
readonly schematic: "module";
|
|
@@ -240,7 +266,7 @@ export declare const generatorCollections: readonly [{
|
|
|
240
266
|
readonly wiringBehavior: "files-only";
|
|
241
267
|
}, {
|
|
242
268
|
readonly aliases: readonly ["resrc"];
|
|
243
|
-
readonly description: "Generate a full resource slice with module, controller, service, repository, and
|
|
269
|
+
readonly description: "Generate a full resource slice with module, controller, service, repository, DTO stubs, and optional --with-slice-test.";
|
|
244
270
|
readonly factory: (name: string, options: import("../generator-types.js").GenerateOptions | undefined) => import("../generator-types.js").GeneratedFile[];
|
|
245
271
|
readonly kind: "resource";
|
|
246
272
|
readonly nextStepHint: "Run 'pnpm typecheck' and wire the resource module into a parent module when ready.";
|
|
@@ -283,6 +309,14 @@ export declare const generatorManifest: readonly [{
|
|
|
283
309
|
readonly nextStepHint: "Run 'pnpm typecheck' to verify module wiring, then add route handlers.";
|
|
284
310
|
readonly schematic: "controller";
|
|
285
311
|
readonly wiringBehavior: "auto-registered";
|
|
312
|
+
}, {
|
|
313
|
+
readonly aliases: readonly [];
|
|
314
|
+
readonly description: "Generate an app-level e2e-style test with createTestApp({ rootModule }).";
|
|
315
|
+
readonly factory: (name: string, options: import("../generator-types.js").GenerateOptions | undefined) => import("../generator-types.js").GeneratedFile[];
|
|
316
|
+
readonly kind: "e2e";
|
|
317
|
+
readonly nextStepHint: "Run 'pnpm test:e2e' after wiring the route into AppModule, or update the generated path expectation first.";
|
|
318
|
+
readonly schematic: "e2e";
|
|
319
|
+
readonly wiringBehavior: "files-only";
|
|
286
320
|
}, {
|
|
287
321
|
readonly aliases: readonly ["gu"];
|
|
288
322
|
readonly description: "Generate a guard (auto-registered as a provider in the module).";
|
|
@@ -321,8 +355,8 @@ export declare const generatorManifest: readonly [{
|
|
|
321
355
|
readonly wiringBehavior: "auto-registered";
|
|
322
356
|
}, {
|
|
323
357
|
readonly aliases: readonly ["mo"];
|
|
324
|
-
readonly description: "Generate a standalone module (
|
|
325
|
-
readonly factory: (name: string) => import("../generator-types.js").GeneratedFile[];
|
|
358
|
+
readonly description: "Generate a standalone module (add --with-test for a module graph slice test).";
|
|
359
|
+
readonly factory: (name: string, options: import("../generator-types.js").GenerateOptions | undefined) => import("../generator-types.js").GeneratedFile[];
|
|
326
360
|
readonly kind: "module";
|
|
327
361
|
readonly nextStepHint: "Import the new module in a parent module's imports array, then run 'pnpm typecheck'.";
|
|
328
362
|
readonly schematic: "module";
|
|
@@ -350,7 +384,7 @@ export declare const generatorManifest: readonly [{
|
|
|
350
384
|
readonly wiringBehavior: "files-only";
|
|
351
385
|
}, {
|
|
352
386
|
readonly aliases: readonly ["resrc"];
|
|
353
|
-
readonly description: "Generate a full resource slice with module, controller, service, repository, and
|
|
387
|
+
readonly description: "Generate a full resource slice with module, controller, service, repository, DTO stubs, and optional --with-slice-test.";
|
|
354
388
|
readonly factory: (name: string, options: import("../generator-types.js").GenerateOptions | undefined) => import("../generator-types.js").GeneratedFile[];
|
|
355
389
|
readonly kind: "resource";
|
|
356
390
|
readonly nextStepHint: "Run 'pnpm typecheck' and wire the resource module into a parent module when ready.";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/generators/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAyB,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/generators/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAyB,MAAM,uBAAuB,CAAC;AAcrF,yFAAyF;AACzF,MAAM,MAAM,cAAc,GAAG,aAAa,GAAG,WAAW,GAAG,YAAY,CAAC;AAExE,KAAK,4BAA4B,GAAG;IAClC,QAAQ,EAAE,cAAc,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,8EAA8E;AAC9E,MAAM,MAAM,sBAAsB,GAAG;IACnC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,gBAAgB,CAAC;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,kBAAkB,CAAC,EAAE,4BAA4B,CAAC;IAClD,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,iBAAiB,GAAG,YAAY,CAAC;CAClD,CAAC;AAEF,4FAA4F;AAC5F,MAAM,MAAM,mBAAmB,GAAG;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,SAAS,sBAAsB,EAAE,CAAC;IAC9C,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,UAAU,CAAC;CACpB,CAAC;AAEF,wFAAwF;AACxF,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAOkB,CAAC;AA+GtD,6EAA6E;AAC7E,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAKC,CAAC;AAEzC,2FAA2F;AAC3F,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAAiF,CAAC;AAEnH,2FAA2F;AAC3F,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAAwC,CAAC;AAEvE,kFAAkF;AAClF,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC;AAEvE,uEAAuE;AACvE,MAAM,MAAM,mBAAmB,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAC;AAcrE;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,aAAa,GAAG,mBAAmB,CAOhF;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,IAAI,SAAS,mBAAmB,EAAE,CAEzE;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,YAAY,GAAE,MAAsC,GAAG,SAAS,mBAAmB,EAAE,CAO7H;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,aAAa,GAAG,SAAS,CAMzF"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { generateControllerFiles } from './controller.js';
|
|
2
|
+
import { generateE2eFiles } from './e2e.js';
|
|
2
3
|
import { generateGuardFiles } from './guard.js';
|
|
3
4
|
import { generateInterceptorFiles } from './interceptor.js';
|
|
4
5
|
import { generateMiddlewareFiles } from './middleware.js';
|
|
@@ -31,6 +32,16 @@ export const generatorOptionSchemas = [{
|
|
|
31
32
|
description: 'Preview planned writes, skips, and module wiring without touching files.',
|
|
32
33
|
name: '--dry-run',
|
|
33
34
|
value: 'boolean'
|
|
35
|
+
}, {
|
|
36
|
+
aliases: [],
|
|
37
|
+
description: 'Emit a module-level slice test when generating module schematics.',
|
|
38
|
+
name: '--with-test',
|
|
39
|
+
value: 'boolean'
|
|
40
|
+
}, {
|
|
41
|
+
aliases: [],
|
|
42
|
+
description: 'Emit the resource slice test with createTestingModule provider override coverage.',
|
|
43
|
+
name: '--with-slice-test',
|
|
44
|
+
value: 'boolean'
|
|
34
45
|
}, {
|
|
35
46
|
aliases: ['-h'],
|
|
36
47
|
description: 'Show help for the generate command.',
|
|
@@ -49,6 +60,14 @@ const builtInGeneratorDefinitions = [{
|
|
|
49
60
|
nextStepHint: "Run 'pnpm typecheck' to verify module wiring, then add route handlers.",
|
|
50
61
|
schematic: 'controller',
|
|
51
62
|
wiringBehavior: 'auto-registered'
|
|
63
|
+
}, {
|
|
64
|
+
aliases: [],
|
|
65
|
+
description: 'Generate an app-level e2e-style test with createTestApp({ rootModule }).',
|
|
66
|
+
factory: (name, options) => generateE2eFiles(name, options),
|
|
67
|
+
kind: 'e2e',
|
|
68
|
+
nextStepHint: "Run 'pnpm test:e2e' after wiring the route into AppModule, or update the generated path expectation first.",
|
|
69
|
+
schematic: 'e2e',
|
|
70
|
+
wiringBehavior: 'files-only'
|
|
52
71
|
}, {
|
|
53
72
|
aliases: ['gu'],
|
|
54
73
|
description: 'Generate a guard (auto-registered as a provider in the module).',
|
|
@@ -87,8 +106,8 @@ const builtInGeneratorDefinitions = [{
|
|
|
87
106
|
wiringBehavior: 'auto-registered'
|
|
88
107
|
}, {
|
|
89
108
|
aliases: ['mo'],
|
|
90
|
-
description: 'Generate a standalone module (
|
|
91
|
-
factory: name => generateModuleFiles(name),
|
|
109
|
+
description: 'Generate a standalone module (add --with-test for a module graph slice test).',
|
|
110
|
+
factory: (name, options) => generateModuleFiles(name, options),
|
|
92
111
|
kind: 'module',
|
|
93
112
|
nextStepHint: "Import the new module in a parent module's imports array, then run 'pnpm typecheck'.",
|
|
94
113
|
schematic: 'module',
|
|
@@ -116,7 +135,7 @@ const builtInGeneratorDefinitions = [{
|
|
|
116
135
|
wiringBehavior: 'files-only'
|
|
117
136
|
}, {
|
|
118
137
|
aliases: ['resrc'],
|
|
119
|
-
description: 'Generate a full resource slice with module, controller, service, repository, and
|
|
138
|
+
description: 'Generate a full resource slice with module, controller, service, repository, DTO stubs, and optional --with-slice-test.',
|
|
120
139
|
factory: (name, options) => generateResourceFiles(name, options),
|
|
121
140
|
kind: 'resource',
|
|
122
141
|
nextStepHint: "Run 'pnpm typecheck' and wire the resource module into a parent module when ready.",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { GeneratedFile } from '../types.js';
|
|
1
|
+
import type { GenerateOptions, GeneratedFile } from '../types.js';
|
|
2
2
|
import type { ModuleArrayKey } from './manifest.js';
|
|
3
3
|
/**
|
|
4
4
|
* Ensure module import.
|
|
@@ -15,7 +15,7 @@ export declare function ensureModuleImport(source: string, className: string, im
|
|
|
15
15
|
* @param name The name.
|
|
16
16
|
* @returns The generate module files result.
|
|
17
17
|
*/
|
|
18
|
-
export declare function generateModuleFiles(name: string): GeneratedFile[];
|
|
18
|
+
export declare function generateModuleFiles(name: string, options?: GenerateOptions): GeneratedFile[];
|
|
19
19
|
/**
|
|
20
20
|
* Register in module.
|
|
21
21
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../../src/generators/module.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../../src/generators/module.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGlE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AA6FpD;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAyDhG;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,eAAoB,GAAG,aAAa,EAAE,CAmBhG;AAwDD;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAEpG"}
|
|
@@ -112,16 +112,26 @@ export function ensureModuleImport(source, className, importPath) {
|
|
|
112
112
|
* @param name The name.
|
|
113
113
|
* @returns The generate module files result.
|
|
114
114
|
*/
|
|
115
|
-
export function generateModuleFiles(name) {
|
|
115
|
+
export function generateModuleFiles(name, options = {}) {
|
|
116
116
|
const kebab = toKebabCase(name);
|
|
117
117
|
const pascal = `${toPascalCase(name)}Module`;
|
|
118
|
-
|
|
118
|
+
const files = [{
|
|
119
119
|
content: renderTemplate('module.ts.ejs', {
|
|
120
120
|
kebab,
|
|
121
121
|
pascal
|
|
122
122
|
}),
|
|
123
123
|
path: `${kebab}.module.ts`
|
|
124
124
|
}];
|
|
125
|
+
if (options.withTest) {
|
|
126
|
+
files.push({
|
|
127
|
+
content: renderTemplate('module.slice.test.ts.ejs', {
|
|
128
|
+
kebab,
|
|
129
|
+
pascal
|
|
130
|
+
}),
|
|
131
|
+
path: `${kebab}.slice.test.ts`
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
return files;
|
|
125
135
|
}
|
|
126
136
|
function insertIntoModuleArray(source, arrayKey, className) {
|
|
127
137
|
const sourceFile = parseSource(source);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resource.d.ts","sourceRoot":"","sources":["../../src/generators/resource.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"resource.d.ts","sourceRoot":"","sources":["../../src/generators/resource.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAUlE;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,eAAoB,GAAG,aAAa,EAAE,CA2BlG"}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { generateControllerFiles } from './controller.js';
|
|
2
|
-
import { generateModuleFiles } from './module.js';
|
|
3
2
|
import { generateRepoFiles } from './repository.js';
|
|
4
3
|
import { generateRequestDtoFiles } from './request-dto.js';
|
|
5
4
|
import { generateResponseDtoFiles } from './response-dto.js';
|
|
5
|
+
import { renderTemplate } from './render.js';
|
|
6
6
|
import { generateServiceFiles } from './service.js';
|
|
7
|
+
import { toKebabCase, toPascalCase } from './utils.js';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Generate a complete feature resource slice.
|
|
@@ -13,11 +14,38 @@ import { generateServiceFiles } from './service.js';
|
|
|
13
14
|
* @returns The generated resource files.
|
|
14
15
|
*/
|
|
15
16
|
export function generateResourceFiles(name, options = {}) {
|
|
16
|
-
|
|
17
|
+
const kebab = toKebabCase(name);
|
|
18
|
+
const resource = toPascalCase(name);
|
|
19
|
+
const module = `${resource}Module`;
|
|
20
|
+
const controller = `${resource}Controller`;
|
|
21
|
+
const repo = `${resource}Repo`;
|
|
22
|
+
const service = `${resource}Service`;
|
|
23
|
+
const files = [{
|
|
24
|
+
content: renderTemplate('resource.module.ts.ejs', {
|
|
25
|
+
controller,
|
|
26
|
+
kebab,
|
|
27
|
+
module,
|
|
28
|
+
repo,
|
|
29
|
+
service
|
|
30
|
+
}),
|
|
31
|
+
path: `${kebab}.module.ts`
|
|
32
|
+
}, ...generateRepoFiles(name, options), ...generateServiceFiles(name, {
|
|
17
33
|
...options,
|
|
18
34
|
hasRepo: true
|
|
19
35
|
}), ...generateControllerFiles(name, {
|
|
20
36
|
...options,
|
|
21
37
|
hasService: true
|
|
22
38
|
}), ...generateRequestDtoFiles(`Create ${name}`), ...generateResponseDtoFiles(name)];
|
|
39
|
+
if (options.withSliceTest) {
|
|
40
|
+
files.push({
|
|
41
|
+
content: renderTemplate('resource.slice.test.ts.ejs', {
|
|
42
|
+
kebab,
|
|
43
|
+
repo,
|
|
44
|
+
resource,
|
|
45
|
+
service
|
|
46
|
+
}),
|
|
47
|
+
path: `${kebab}.slice.test.ts`
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
return files;
|
|
23
51
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { createTestApp } from '@fluojs/testing';
|
|
4
|
+
|
|
5
|
+
import { AppModule } from '<%- rootModuleImport %>';
|
|
6
|
+
|
|
7
|
+
describe('<%- kebab %> e2e', () => {
|
|
8
|
+
it('dispatches through the app request pipeline', async () => {
|
|
9
|
+
const app = await createTestApp({ rootModule: AppModule });
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
const response = await app.request('GET', '/<%- kebab %>').send();
|
|
13
|
+
|
|
14
|
+
expect(response.status).toBe(200);
|
|
15
|
+
} finally {
|
|
16
|
+
await app.close();
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { createTestingModule } from '@fluojs/testing';
|
|
4
|
+
|
|
5
|
+
import { <%- pascal %> } from './<%- kebab %>.module';
|
|
6
|
+
|
|
7
|
+
describe('<%- pascal %> slice', () => {
|
|
8
|
+
it('compiles the authored module graph', async () => {
|
|
9
|
+
const testingModule = await createTestingModule({ rootModule: <%- pascal %> }).compile();
|
|
10
|
+
|
|
11
|
+
expect(testingModule.rootModule).toBe(<%- pascal %>);
|
|
12
|
+
});
|
|
13
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Module } from '@fluojs/core';
|
|
2
|
+
|
|
3
|
+
import { <%- controller %> } from './<%- kebab %>.controller';
|
|
4
|
+
import { <%- repo %> } from './<%- kebab %>.repo';
|
|
5
|
+
import { <%- service %> } from './<%- kebab %>.service';
|
|
6
|
+
|
|
7
|
+
@Module({
|
|
8
|
+
controllers: [<%- controller %>],
|
|
9
|
+
providers: [<%- repo %>, <%- service %>],
|
|
10
|
+
})
|
|
11
|
+
class <%- module %> {}
|
|
12
|
+
|
|
13
|
+
export { <%- module %> };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { createTestingModule } from '@fluojs/testing';
|
|
4
|
+
|
|
5
|
+
import { <%- resource %>Module } from './<%- kebab %>.module';
|
|
6
|
+
import { <%- repo %> } from './<%- kebab %>.repo';
|
|
7
|
+
import { <%- service %> } from './<%- kebab %>.service';
|
|
8
|
+
|
|
9
|
+
describe('<%- resource %>Module slice', () => {
|
|
10
|
+
it('compiles provider wiring with an explicit override', async () => {
|
|
11
|
+
const testingModule = await createTestingModule({ rootModule: <%- resource %>Module })
|
|
12
|
+
.overrideProvider(<%- repo %>, {
|
|
13
|
+
list<%- resource %>s: async () => [{ id: '<%- kebab %>-1' }],
|
|
14
|
+
})
|
|
15
|
+
.compile();
|
|
16
|
+
|
|
17
|
+
const service = await testingModule.resolve<<%- service %>>(<%- service %>);
|
|
18
|
+
|
|
19
|
+
await expect(service.list<%- resource %>s()).resolves.toEqual([{ id: '<%- kebab %>-1' }]);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../src/new/scaffold.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,gBAAgB,EAAkB,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../src/new/scaffold.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,gBAAgB,EAAkB,MAAM,YAAY,CAAC;AA+qEnE;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,gBAAgB,EACzB,aAAa,SAAkB,GAC9B,OAAO,CAAC,IAAI,CAAC,CA6Bf;AAED;;GAEG;AACH,eAAO,MAAM,eAAe,6BAAuB,CAAC"}
|
package/dist/new/scaffold.js
CHANGED
|
@@ -11,6 +11,7 @@ const PUBLISHED_DEV_DEPENDENCIES = {
|
|
|
11
11
|
'@babel/preset-typescript': '^7.27.1',
|
|
12
12
|
'@types/babel__core': '^7.20.5',
|
|
13
13
|
'@types/node': '^22.13.10',
|
|
14
|
+
'@vitest/coverage-v8': '^3.0.8',
|
|
14
15
|
tsx: '^4.20.4',
|
|
15
16
|
typescript: '^6.0.2',
|
|
16
17
|
vite: '^6.2.1',
|
|
@@ -27,22 +28,22 @@ const PUBLISHED_RUNTIME_DEPENDENCIES = {
|
|
|
27
28
|
nats: '^2.29.3'
|
|
28
29
|
};
|
|
29
30
|
const PUBLISHED_INTERNAL_DEPENDENCIES = {
|
|
30
|
-
'@fluojs/cli': '^1.0.0-beta.
|
|
31
|
-
'@fluojs/config': '^1.0.0-beta.
|
|
32
|
-
'@fluojs/core': '^1.0.0-beta.
|
|
31
|
+
'@fluojs/cli': '^1.0.0-beta.7',
|
|
32
|
+
'@fluojs/config': '^1.0.0-beta.7',
|
|
33
|
+
'@fluojs/core': '^1.0.0-beta.4',
|
|
33
34
|
'@fluojs/di': '^1.0.0-beta.6',
|
|
34
|
-
'@fluojs/http': '^1.0.0-beta.
|
|
35
|
-
'@fluojs/microservices': '^1.0.0-beta.
|
|
35
|
+
'@fluojs/http': '^1.0.0-beta.10',
|
|
36
|
+
'@fluojs/microservices': '^1.0.0-beta.5',
|
|
36
37
|
'@fluojs/platform-bun': '^1.0.0-beta.6',
|
|
37
38
|
'@fluojs/platform-cloudflare-workers': '^1.0.0-beta.2',
|
|
38
39
|
'@fluojs/platform-deno': '^1.0.0-beta.3',
|
|
39
40
|
'@fluojs/platform-express': '^1.0.0-beta.6',
|
|
40
41
|
'@fluojs/platform-fastify': '^1.0.0-beta.8',
|
|
41
42
|
'@fluojs/platform-nodejs': '^1.0.0-beta.4',
|
|
42
|
-
'@fluojs/runtime': '^1.0.0-beta.
|
|
43
|
+
'@fluojs/runtime': '^1.0.0-beta.11',
|
|
43
44
|
'@fluojs/testing': '^1.0.0-beta.2',
|
|
44
|
-
'@fluojs/validation': '^1.0.0-beta.
|
|
45
|
-
'@fluojs/vite': '^1.0.0-beta.
|
|
45
|
+
'@fluojs/validation': '^1.0.0-beta.3',
|
|
46
|
+
'@fluojs/vite': '^1.0.0-beta.2'
|
|
46
47
|
};
|
|
47
48
|
function describeApplicationStarter(options) {
|
|
48
49
|
if (options.runtime === 'bun') {
|
|
@@ -152,6 +153,8 @@ function createProjectScripts(bootstrapPlan) {
|
|
|
152
153
|
dev: 'fluo dev',
|
|
153
154
|
start: 'bun dist/main.js',
|
|
154
155
|
test: 'vitest run',
|
|
156
|
+
'test:cov': 'vitest run --coverage',
|
|
157
|
+
'test:e2e': 'vitest run test/**/*.e2e.test.ts',
|
|
155
158
|
'test:watch': 'vitest',
|
|
156
159
|
typecheck: 'tsc -p tsconfig.json --noEmit'
|
|
157
160
|
};
|
|
@@ -171,6 +174,8 @@ function createProjectScripts(bootstrapPlan) {
|
|
|
171
174
|
dev: 'fluo dev',
|
|
172
175
|
preview: 'wrangler dev --remote --show-interactive-dev-session=false',
|
|
173
176
|
test: 'vitest run',
|
|
177
|
+
'test:cov': 'vitest run --coverage',
|
|
178
|
+
'test:e2e': 'vitest run test/**/*.e2e.test.ts',
|
|
174
179
|
'test:watch': 'vitest',
|
|
175
180
|
typecheck: 'tsc -p tsconfig.json --noEmit'
|
|
176
181
|
};
|
|
@@ -180,6 +185,10 @@ function createProjectScripts(bootstrapPlan) {
|
|
|
180
185
|
dev: 'fluo dev',
|
|
181
186
|
start: 'fluo start',
|
|
182
187
|
test: 'vitest run',
|
|
188
|
+
...(bootstrapPlan.profile.emitter.type === 'http' ? {
|
|
189
|
+
'test:cov': 'vitest run --coverage',
|
|
190
|
+
'test:e2e': 'vitest run test/**/*.e2e.test.ts'
|
|
191
|
+
} : {}),
|
|
183
192
|
'test:watch': 'vitest',
|
|
184
193
|
typecheck: 'tsc -p tsconfig.json --noEmit'
|
|
185
194
|
};
|
|
@@ -325,7 +334,7 @@ export default defineConfig({
|
|
|
325
334
|
plugins: [fluoBabelDecoratorsPlugin()],
|
|
326
335
|
test: {
|
|
327
336
|
environment: 'node',
|
|
328
|
-
include: ['src/**/*.test.ts'],
|
|
337
|
+
include: ['src/**/*.test.ts', 'test/**/*.test.ts'],
|
|
329
338
|
},
|
|
330
339
|
});
|
|
331
340
|
`;
|
|
@@ -371,7 +380,7 @@ function createHttpProjectReadme(options) {
|
|
|
371
380
|
const entrypointLabel = starter.entrypoint;
|
|
372
381
|
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('(... )', '(...)');
|
|
373
382
|
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`;
|
|
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
|
|
383
|
+
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/greeting.repo.test.ts\`, \`src/greeting/greeting.service.test.ts\`, and \`src/greeting/greeting.controller.test.ts\` — unit templates for the starter-owned greeting slice.\n- \`src/greeting/greeting.slice.test.ts\` — module/slice template via \`createTestingModule\` for real DI graph confidence.\n- \`src/app.test.ts\` — integration-style dispatch template for runtime + starter routes.\n- \`test/app.e2e.test.ts\` — default HTTP/e2e-style template powered by \`createTestApp\` and \`app.request(...).send()\` from \`@fluojs/testing\`; older \`src/app.e2e.test.ts\` tests can be moved here without changing the request helper.\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, \`${createRunCommand(options.packageManager, 'test:e2e')}\` for the dedicated request-level e2e suite, and \`${createRunCommand(options.packageManager, 'test:cov')}\` when your Vitest runtime supports coverage.`;
|
|
375
384
|
return `# ${options.projectName}
|
|
376
385
|
|
|
377
386
|
Generated by @fluojs/cli.
|
|
@@ -695,6 +704,28 @@ describe('GreetingController', () => {
|
|
|
695
704
|
});
|
|
696
705
|
`;
|
|
697
706
|
}
|
|
707
|
+
function createGreetingSliceTestFile(importSuffix = '') {
|
|
708
|
+
return `import { describe, expect, it } from 'vitest';
|
|
709
|
+
|
|
710
|
+
import { createTestingModule } from '@fluojs/testing';
|
|
711
|
+
|
|
712
|
+
import { GreetingModule } from './greeting.module${importSuffix}';
|
|
713
|
+
import { GreetingRepo } from './greeting.repo${importSuffix}';
|
|
714
|
+
import { GreetingService } from './greeting.service${importSuffix}';
|
|
715
|
+
|
|
716
|
+
describe('Greeting slice', () => {
|
|
717
|
+
it('resolves starter providers from the module graph', async () => {
|
|
718
|
+
const testingModule = await createTestingModule({ rootModule: GreetingModule }).compile();
|
|
719
|
+
|
|
720
|
+
const repo = await testingModule.resolve(GreetingRepo);
|
|
721
|
+
const service = await testingModule.resolve(GreetingService);
|
|
722
|
+
|
|
723
|
+
expect(repo.findGreeting()).toEqual({ message: 'Hello from fluo', framework: 'fluo', project: expect.any(String) });
|
|
724
|
+
expect(service.getGreeting()).toEqual({ message: 'Hello from fluo', framework: 'fluo', project: expect.any(String) });
|
|
725
|
+
});
|
|
726
|
+
});
|
|
727
|
+
`;
|
|
728
|
+
}
|
|
698
729
|
function createGreetingModuleFile(importSuffix = '') {
|
|
699
730
|
return `import { Module } from '@fluojs/core';
|
|
700
731
|
|
|
@@ -741,6 +772,12 @@ export default {
|
|
|
741
772
|
`;
|
|
742
773
|
}
|
|
743
774
|
const portExpression = options.runtime === 'bun' ? "Bun.env.PORT ?? '3000'" : "process.env.PORT ?? '3000'";
|
|
775
|
+
const loggerGuidance = options.runtime === 'node' ? `
|
|
776
|
+
// Application logging defaults to the pretty console logger when logger is omitted.
|
|
777
|
+
// JSON logs are opt-in:
|
|
778
|
+
// import { createJsonApplicationLogger } from '@fluojs/runtime/node';
|
|
779
|
+
// Then pass \`logger: createJsonApplicationLogger()\` to FluoFactory.create(...).
|
|
780
|
+
` : '';
|
|
744
781
|
return `import { ${starter.adapterFactory} } from '${starter.packageName}';
|
|
745
782
|
import { FluoFactory } from '@fluojs/runtime';
|
|
746
783
|
|
|
@@ -748,6 +785,7 @@ import { AppModule } from './app';
|
|
|
748
785
|
|
|
749
786
|
// The generated starter wires the selected first-class fluo new application path:
|
|
750
787
|
// ${starter.runtimeLabel} + ${starter.platformLabel} via ${starter.adapterFactory}(...).
|
|
788
|
+
${loggerGuidance}
|
|
751
789
|
|
|
752
790
|
const parsedPort = Number.parseInt(${portExpression}, 10);
|
|
753
791
|
const port = Number.isFinite(parsedPort) ? parsedPort : 3000;
|
|
@@ -1685,21 +1723,21 @@ function createAppE2eTestFile(importSuffix = '') {
|
|
|
1685
1723
|
|
|
1686
1724
|
import { createTestApp } from '@fluojs/testing';
|
|
1687
1725
|
|
|
1688
|
-
import { AppModule } from '
|
|
1726
|
+
import { AppModule } from '../src/app${importSuffix}';
|
|
1689
1727
|
|
|
1690
1728
|
describe('AppModule e2e', () => {
|
|
1691
|
-
it('serves runtime and starter routes through createTestApp', async () => {
|
|
1729
|
+
it('serves runtime and starter routes through createTestApp request helpers', async () => {
|
|
1692
1730
|
const app = await createTestApp({ rootModule: AppModule });
|
|
1693
1731
|
|
|
1694
|
-
await expect(app.
|
|
1732
|
+
await expect(app.request('GET', '/health').send()).resolves.toMatchObject({
|
|
1695
1733
|
body: { status: 'ok' },
|
|
1696
1734
|
status: 200,
|
|
1697
1735
|
});
|
|
1698
|
-
await expect(app.
|
|
1736
|
+
await expect(app.request('GET', '/ready').send()).resolves.toMatchObject({
|
|
1699
1737
|
body: { status: 'ready' },
|
|
1700
1738
|
status: 200,
|
|
1701
1739
|
});
|
|
1702
|
-
await expect(app.
|
|
1740
|
+
await expect(app.request('GET', '/greeting/').send()).resolves.toMatchObject({
|
|
1703
1741
|
body: { message: 'Hello from fluo', framework: 'fluo', project: expect.any(String) },
|
|
1704
1742
|
status: 200,
|
|
1705
1743
|
});
|
|
@@ -1932,12 +1970,15 @@ function emitApplicationScaffoldFiles(options) {
|
|
|
1932
1970
|
}, {
|
|
1933
1971
|
content: createGreetingControllerTestFile(),
|
|
1934
1972
|
path: 'src/greeting/greeting.controller.test.ts'
|
|
1973
|
+
}, {
|
|
1974
|
+
content: createGreetingSliceTestFile(importSuffix),
|
|
1975
|
+
path: 'src/greeting/greeting.slice.test.ts'
|
|
1935
1976
|
}, {
|
|
1936
1977
|
content: createAppTestFile(importSuffix),
|
|
1937
1978
|
path: 'src/app.test.ts'
|
|
1938
1979
|
}, {
|
|
1939
1980
|
content: createAppE2eTestFile(importSuffix),
|
|
1940
|
-
path: '
|
|
1981
|
+
path: 'test/app.e2e.test.ts'
|
|
1941
1982
|
});
|
|
1942
1983
|
return files;
|
|
1943
1984
|
}
|
|
@@ -2010,6 +2051,9 @@ function emitScaffoldFilesForRecipe(options, recipeId) {
|
|
|
2010
2051
|
}, {
|
|
2011
2052
|
content: createGreetingModuleFile(),
|
|
2012
2053
|
path: 'src/greeting/greeting.module.ts'
|
|
2054
|
+
}, {
|
|
2055
|
+
content: createGreetingSliceTestFile(),
|
|
2056
|
+
path: 'src/greeting/greeting.slice.test.ts'
|
|
2013
2057
|
}, {
|
|
2014
2058
|
content: createMathHandlerFile({
|
|
2015
2059
|
transport: 'tcp'
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"migration",
|
|
10
10
|
"diagnostics"
|
|
11
11
|
],
|
|
12
|
-
"version": "1.0.0-beta.
|
|
12
|
+
"version": "1.0.0-beta.8",
|
|
13
13
|
"private": false,
|
|
14
14
|
"license": "MIT",
|
|
15
15
|
"repository": {
|
|
@@ -44,10 +44,10 @@
|
|
|
44
44
|
"ejs": "^3.1.10",
|
|
45
45
|
"tsx": "^4.20.4",
|
|
46
46
|
"typescript": "^6.0.2",
|
|
47
|
-
"@fluojs/runtime": "^1.0.0-beta.
|
|
47
|
+
"@fluojs/runtime": "^1.0.0-beta.12"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
|
-
"@fluojs/studio": "^1.0.0-beta.
|
|
50
|
+
"@fluojs/studio": "^1.0.0-beta.4"
|
|
51
51
|
},
|
|
52
52
|
"peerDependenciesMeta": {
|
|
53
53
|
"@fluojs/studio": {
|