@fluojs/cli 1.0.6 → 2.0.1
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 +31 -10
- package/README.md +31 -10
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +4 -3
- package/dist/commands/generate.d.ts +1 -49
- package/dist/commands/generate.d.ts.map +1 -1
- package/dist/commands/generate.js +1 -214
- package/dist/commands/inspect.d.ts +0 -6
- package/dist/commands/inspect.d.ts.map +1 -1
- package/dist/commands/inspect.js +1 -48
- package/dist/commands/new.d.ts +0 -6
- package/dist/commands/new.d.ts.map +1 -1
- package/dist/commands/new.js +14 -88
- package/dist/commands/scripts.d.ts +1 -1
- package/dist/commands/scripts.d.ts.map +1 -1
- package/dist/commands/scripts.js +16 -7
- package/dist/dev-runner/node-restart-runner.d.ts +6 -0
- package/dist/dev-runner/node-restart-runner.d.ts.map +1 -1
- package/dist/dev-runner/node-restart-runner.js +43 -8
- package/dist/generate-command.d.ts +50 -0
- package/dist/generate-command.d.ts.map +1 -0
- package/dist/generate-command.js +214 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/new/scaffold.d.ts.map +1 -1
- package/dist/new/scaffold.js +115 -23
- package/dist/new/types.d.ts +0 -2
- package/dist/new/types.d.ts.map +1 -1
- package/dist/public-generate.d.ts +2 -0
- package/dist/public-generate.d.ts.map +1 -0
- package/dist/public-generate.js +1 -0
- package/dist/public-inspect.d.ts +13 -0
- package/dist/public-inspect.d.ts.map +1 -0
- package/dist/public-inspect.js +16 -0
- package/dist/public-new.d.ts +13 -0
- package/dist/public-new.d.ts.map +1 -0
- package/dist/public-new.js +16 -0
- package/dist/run-cli.d.ts +16 -0
- package/dist/run-cli.d.ts.map +1 -0
- package/dist/run-cli.js +16 -0
- package/dist/studio/sidecar.d.ts.map +1 -1
- package/dist/studio/sidecar.js +113 -27
- package/dist/types.d.ts +6 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/update-check.d.ts.map +1 -1
- package/dist/update-check.js +21 -4
- package/dist/usage.d.ts +13 -0
- package/dist/usage.d.ts.map +1 -0
- package/dist/usage.js +131 -0
- package/package.json +3 -3
package/README.ko.md
CHANGED
|
@@ -31,8 +31,9 @@ pnpm dlx @fluojs/cli new my-app
|
|
|
31
31
|
## 릴리스 계약
|
|
32
32
|
|
|
33
33
|
- `@fluojs/cli`는 intended publish surface에 포함되는 공개 패키지입니다.
|
|
34
|
+
- `@fluojs/cli`는 Node.js `>=20.0.0`을 요구합니다. 생성된 Bun, Deno, Cloudflare Workers starter가 비 Node runtime을 대상으로 할 수는 있지만 CLI process 자체는 Node.js에서 실행됩니다.
|
|
34
35
|
- 지원되는 설치 경로는 전역 패키지(`npm install -g @fluojs/cli`, `pnpm add -g @fluojs/cli`, `bun add -g @fluojs/cli`, `yarn global add @fluojs/cli`)와 무설치 실행 경로(`pnpm dlx @fluojs/cli ...`)입니다.
|
|
35
|
-
- 배포되는 `fluo` bin은 `package.json`에 선언된 dist 빌드 CLI
|
|
36
|
+
- 배포되는 `fluo` bin은 `package.json`에 선언된 `./bin/fluo.mjs` wrapper이며, 이 wrapper가 dist 빌드 CLI 엔트리포인트인 `../dist/cli.js`를 로드합니다.
|
|
36
37
|
|
|
37
38
|
## 버전 확인
|
|
38
39
|
|
|
@@ -112,7 +113,7 @@ fluo new my-grpc-service --shape microservice --transport grpc --runtime node --
|
|
|
112
113
|
|
|
113
114
|
지원되는 `--shape microservice --transport` 스타터 값은 정확히 `tcp`, `redis-streams`, `nats`, `kafka`, `rabbitmq`, `mqtt`, `grpc`입니다. 유지보수되는 Redis 기반 스타터가 필요하면 `redis-streams`를 사용하고, 더 넓은 Redis 통합 패턴이 필요하면 스캐폴딩 후 `@fluojs/redis`를 수동으로 추가하세요.
|
|
114
115
|
|
|
115
|
-
NATS/Kafka/RabbitMQ 스타터 계약은 외부 broker와 caller-owned client library 의존성을 숨기지 않고 명시적으로 유지합니다. 생성된 프로젝트는 `src/app.ts`에서 `nats` + `JSONCodec()`, `kafkajs` producer/consumer collaborator, `amqplib` publisher/consumer collaborator를 직접 연결하므로, 기본 fluo 패키지가 그 의존성을 감춘 것처럼 가장하지 않는 runnable starter 계약이 됩니다. 해당 broker client는 생성된 transport wrapper가 Fluo lifecycle에서 listen/send/emit을 시작할 때 lazy하게 생성합니다. 따라서 `fluo inspect`, 테스트, static tooling이 `src/app.ts`를 import하는 것만으로는 broker에 연결하거나 lifecycle이 teardown을 소유하기 전에 외부 리소스를 열지 않습니다.
|
|
116
|
+
NATS/Kafka/RabbitMQ 스타터 계약은 외부 broker와 caller-owned client library 의존성을 숨기지 않고 명시적으로 유지합니다. 생성된 프로젝트는 `src/app.ts`에서 `nats` + `JSONCodec()`, `kafkajs` producer/consumer collaborator, `amqplib` publisher/consumer collaborator를 직접 연결하므로, 기본 fluo 패키지가 그 의존성을 감춘 것처럼 가장하지 않는 runnable starter 계약이 됩니다. 해당 broker client는 생성된 transport wrapper가 Fluo lifecycle에서 listen/send/emit을 시작할 때 lazy하게 생성합니다. 따라서 `fluo inspect`, 테스트, static tooling이 `src/app.ts`를 import하는 것만으로는 broker에 연결하거나 lifecycle이 teardown을 소유하기 전에 외부 리소스를 열지 않습니다. 종료 중 delegated transport shutdown이 실패해도 각 wrapper는 소유한 client 종료를 계속 시도합니다. Kafka 연결이 일부만 성공한 경우에는 획득한 client를 disconnect하고, RabbitMQ channel 생성이 실패한 경우에는 획득한 connection을 닫은 뒤 startup error를 다시 던집니다.
|
|
116
117
|
|
|
117
118
|
starter 매트릭스에는 mixed single-package starter도 포함됩니다. 하나의 Fastify HTTP 앱과 attached TCP microservice를 같은 생성 프로젝트 안에 함께 배치합니다.
|
|
118
119
|
|
|
@@ -122,6 +123,18 @@ fluo new my-mixed-app --shape mixed --transport tcp --runtime node --platform fa
|
|
|
122
123
|
|
|
123
124
|
`fluo new`가 interactive TTY에서 실행되면 wizard는 기존 flags/config 모델을 그대로 사용합니다. wizard는 프로젝트 이름, shape-first 분기(`application` -> runtime + HTTP platform, `microservice` -> transport), 유지보수 가능한 tooling preset, package manager, 즉시 dependency를 설치할지 여부, git 저장소를 초기화할지 여부를 묻습니다. non-interactive 플래그 경로와 프로그래밍 방식의 `runNewCommand(...)` 호출도 동일한 resolved defaults를 사용합니다.
|
|
124
125
|
|
|
126
|
+
기본적으로 `fluo new my-app`은 `./my-app`에 파일을 씁니다. 프로젝트 이름과 대상 경로가 달라야 하거나 자동화에서 명시적인 디렉터리에 써야 하면 `--target-directory <path>`를 사용하세요:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
fluo new my-app --target-directory ./apps/api
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Scaffold는 기본적으로 비어 있지 않은 대상에서 충돌하는 파일을 덮어쓰지 않습니다. 내용이 다른 생성 파일을 의도적으로 덮어쓰려는 경우에만 `--force`를 추가하세요:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
fluo new my-app --target-directory ./apps/api --force
|
|
136
|
+
```
|
|
137
|
+
|
|
125
138
|
side effect 없이 완전히 resolved starter를 미리 확인하려면 `--print-plan`을 사용하세요:
|
|
126
139
|
|
|
127
140
|
```bash
|
|
@@ -153,7 +166,7 @@ fluo generate service users --dry-run
|
|
|
153
166
|
|
|
154
167
|
자동 등록되는 generator는 `controller`, `service`, `repo`, `guard`, `interceptor`, `middleware`입니다. 파일만 생성하는 generator는 `e2e`, `module`, `request-dto`, `response-dto`, `resource`입니다.
|
|
155
168
|
|
|
156
|
-
`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에 자동으로 연결하지
|
|
169
|
+
`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도 포함합니다. 이 명령은 파일만 생성하고 수동 활성화를 요구하는 generator입니다. 생성된 resource module은 parent module에 자동으로 연결하지 않으며, route를 자동 활성화하는 `nest g resource` 동등 명령으로 보면 안 됩니다. Slice를 활성화할 준비가 되었을 때 직접 import하세요.
|
|
157
170
|
|
|
158
171
|
`fluo generate e2e <name>`는 generated starter와 같은 app-level test 영역에 request-pipeline test를 두도록 `createTestApp({ rootModule: AppModule })`을 사용하는 `test/<name>.e2e.test.ts`를 작성하고, 기본 starter root module인 `../src/app`에서 `AppModule`을 import합니다. 생성된 unit test는 직접 class 동작 검증에, slice test는 DI wiring과 override 검증에, e2e test는 virtual app을 통과하는 route, guard, interceptor, DTO validation, response writing 검증에 사용하세요.
|
|
159
172
|
|
|
@@ -161,7 +174,7 @@ Request DTO 생성은 feature 디렉터리와 DTO 클래스 이름을 분리해
|
|
|
161
174
|
|
|
162
175
|
`--dry-run`을 추가하면 실제 실행과 같은 타깃 해석, 기존 파일 건너뛰기 또는 덮어쓰기 판단, 모듈 자동 등록 계획, 파일만 생성하는 wiring 상태, 다음 단계 힌트를 미리 볼 수 있습니다. 이 모드는 디렉터리 생성, 파일 쓰기, 모듈 갱신을 수행하지 않습니다. `--force`는 내용이 달라질 기존 파일의 계획 항목을 `SKIP`에서 `OVERWRITE`로 바꾸며, `--target-directory`는 실제 실행과 동일하게 지정한 소스 디렉터리 기준으로 preview 범위를 제한합니다.
|
|
163
176
|
|
|
164
|
-
Generator discovery는 의도적으로 built-in `@fluojs/cli/builtin` collection으로 제한됩니다. 외부 package-owned 또는 app-local generator collection은 보류되어 있습니다. `fluo generate`는 config file을 스캔하거나, 임의 package를 로드하거나, workspace-owned collection code를 실행하지 않습니다. 이 경계는 shipped generator 계약을 보존하면서 generator metadata, option schema, help output, file-write boundary를 결정적이고 테스트 가능하게 유지합니다.
|
|
177
|
+
Generator discovery는 의도적으로 built-in `@fluojs/cli/builtin` collection으로 제한됩니다. 외부 package-owned 또는 app-local generator collection은 보류되어 있습니다. `fluo generate`는 config file을 스캔하거나, 임의 package를 로드하거나, package discovery convention을 따르거나, workspace-owned collection code를 실행하지 않습니다. 이 경계는 shipped generator 계약을 보존하면서 generator metadata, option schema, help output, file-write boundary를 결정적이고 테스트 가능하게 유지합니다.
|
|
165
178
|
|
|
166
179
|
## 주요 패턴
|
|
167
180
|
|
|
@@ -194,9 +207,9 @@ fluo dev --studio --studio-port 51234
|
|
|
194
207
|
fluo dev --studio --dry-run
|
|
195
208
|
```
|
|
196
209
|
|
|
197
|
-
CLI는 local Studio sidecar를 시작하고, tokenized URL을 출력하며, restart lifecycle event를 sidecar로 계속 전달하고, 앱이 `@fluojs/runtime`을 import하기 전에 명시적인 Studio config를 Node 앱 child에 주입합니다. Optional package인 `@fluojs/studio`가 설치되어 있으면 sidecar는 패키징된 `@fluojs/studio/viewer` React app을 제공합니다. Runtime package source는 `process.env`를 직접 읽지 않으며, CLI가 주입한 Studio config가 있을 때만 live graph/routes/request/timing/diagnostic event를 전송합니다.
|
|
210
|
+
CLI는 local Studio sidecar를 시작하고, tokenized URL을 출력하며, restart lifecycle event를 sidecar로 계속 전달하고, 앱이 `@fluojs/runtime`을 import하기 전에 명시적인 Studio config를 Node 앱 child에 주입합니다. Studio live mode는 fluo가 소유한 Node restart runner를 요구합니다. 따라서 lifecycle event가 CLI restart boundary와 분리되지 않도록 `fluo dev --studio`는 `--raw-watch`, `--runner native`, `FLUO_DEV_RUNNER=native`를 거부합니다. Optional package인 `@fluojs/studio`가 설치되어 있으면 sidecar는 패키징된 `@fluojs/studio/viewer` React app을 제공합니다. Runtime package source는 `process.env`를 직접 읽지 않으며, CLI가 주입한 Studio config가 있을 때만 live graph/routes/request/timing/diagnostic event를 전송합니다.
|
|
198
211
|
|
|
199
|
-
보안 기본값은 local-only입니다. Sidecar는 `127.0.0.1`에 bind되고, runtime ingestion 및 browser state/SSE API는 generated token을 요구하며, CORS는 기본적으로 활성화하지 않고, request body는 기본적으로 수집하지 않습니다.
|
|
212
|
+
보안 기본값은 local-only입니다. Sidecar는 `127.0.0.1`에 bind되고, runtime ingestion 및 browser state/SSE API는 generated token을 요구하며, CORS는 기본적으로 활성화하지 않고, request body는 기본적으로 수집하지 않습니다. Local client가 partial request body만 보내고 socket을 닫으면 sidecar는 bounded error completion으로 ingestion 요청을 settle하므로, malformed local client가 sidecar 작업을 무기한 대기시킬 수 없습니다.
|
|
200
213
|
|
|
201
214
|
MVP runtime support는 명시적으로 제한됩니다.
|
|
202
215
|
|
|
@@ -251,6 +264,8 @@ fluo migrate ./src --skip tests
|
|
|
251
264
|
|
|
252
265
|
CI 작업, 대시보드, migration report에서 안정적인 machine-readable 결과가 필요하면 `--json`을 사용하세요. 사람을 위한 출력은 기본값으로 유지됩니다. JSON 모드는 성공 시 stdout에 structured report만 기록하고, parser 오류나 잘못된 flag 조합은 기존처럼 stderr에 메시지를 기록한 뒤 exit code `1`을 반환하며 partial JSON을 출력하지 않습니다. Report에는 `mode`(`dry-run` 또는 `apply`), `dryRun`, `apply`, 활성화된 `transforms`, `scannedFiles`, `changedFiles`, 전체 `warningCount`, 그리고 `filePath`, `changed`, `appliedTransforms`, `warningCount`, category label과 source line number가 포함된 warnings per-file metadata가 포함됩니다.
|
|
253
266
|
|
|
267
|
+
`--apply`로 다시 실행하기 전에는 모든 warning을 검토하세요. Warning은 자동 rewrite를 그대로 수락해도 된다는 뜻이 아니라 수동 follow-up 항목입니다. Warning category별 post-codemod checklist는 [NestJS migration guide](../../docs/getting-started/migrate-from-nestjs.ko.md)를 기준으로 확인하세요.
|
|
268
|
+
|
|
254
269
|
**주요 변환 사항:**
|
|
255
270
|
- `@nestjs/common` 임포트를 `@fluojs/core` 또는 `@fluojs/http`로 재작성합니다.
|
|
256
271
|
- bootstrap 패턴을 재작성하고 지원되는 `listen(port)` 호출을 fluo runtime startup 규칙으로 접습니다.
|
|
@@ -300,14 +315,20 @@ Studio가 없으면 CI와 non-interactive 실행은 prompt나 package manager
|
|
|
300
315
|
| `CliRuntimeOptions` | stream, cwd, environment, registry metadata, update-check hook 같은 `runCli(...)` runtime override 타입입니다. |
|
|
301
316
|
| `newUsage()` | help surface와 test에서 사용하는 현재 `fluo new` usage text를 반환합니다. |
|
|
302
317
|
| `runNewCommand(argv, options?)` | 프로젝트 스캐폴딩 로직에 대한 프로그래밍적 접근을 제공합니다. |
|
|
303
|
-
| `NewCommandRuntimeOptions` | prompt, filesystem write, dependency install, git initialization 같은 `runNewCommand(...)` runtime override 타입입니다. |
|
|
318
|
+
| `NewCommandRuntimeOptions` | prompt, filesystem write, dependency install, git initialization 같은 `runNewCommand(...)` runtime override 타입입니다. `runCli(...)`도 `new` 또는 `create`로 dispatch할 때 이 override를 받습니다. Monorepo-local starter dependency override는 내부 sandbox harness 세부사항이며 이 공개 타입의 일부가 아닙니다. |
|
|
304
319
|
| `CliPromptCancelledError` | 호출자가 제공한 prompt hook이 정상 취소를 알리기 위해 throw할 수 있는 안정적인 sentinel입니다. |
|
|
320
|
+
| `runGenerateCommand(kind, name, baseDirectory, options?)` | built-in schematic generator와 module auto-registration planner에 대한 프로그래밍적 접근을 제공합니다. |
|
|
305
321
|
| `GenerateOptions` | 프로그래밍 방식 generator 옵션 타입입니다. |
|
|
306
|
-
| `
|
|
322
|
+
| `GenerateResult` | 변경된 파일, dry-run plan entry, module wiring metadata, next-step hint를 포함하는 generator 결과 타입입니다. |
|
|
323
|
+
| `GeneratePlanEntry` / `GeneratePlanAction` | `runGenerateCommand(...)`가 반환하는 dry-run 및 write-plan path action 타입입니다. |
|
|
324
|
+
| `GeneratedFile` | write 전 생성된 파일 경로와 in-memory content를 설명하는 타입입니다. |
|
|
307
325
|
| `GeneratorKind` | 지원되는 모든 생성기 유형(예: `'controller'`, `'service'`)의 유니온 타입입니다. |
|
|
308
|
-
| `ModuleRegistration` | generator 실행의 module wiring
|
|
326
|
+
| `ModuleRegistration` | generator 실행의 controller, provider, middleware module wiring metadata를 설명하는 타입입니다. |
|
|
327
|
+
| `inspectUsage()` | help surface와 test에서 사용하는 현재 `fluo inspect` usage text를 반환합니다. |
|
|
328
|
+
| `runInspectCommand(argv, options?)` | inspect orchestration, JSON/report emission, Studio Mermaid delegation에 대한 프로그래밍적 접근을 제공합니다. |
|
|
329
|
+
| `InspectCommandRuntimeOptions` | cwd, stream, prompt, Studio renderer loading 같은 `runInspectCommand(...)`와 `runCli(...)` inspect runtime override 타입입니다. |
|
|
309
330
|
|
|
310
|
-
프로그래밍 방식 진입점은 호출자 프로세스의 소유권을 보존합니다. `runCli(...)
|
|
331
|
+
프로그래밍 방식 진입점은 호출자 프로세스의 소유권을 보존합니다. `runCli(...)`, `runNewCommand(...)`, `runInspectCommand(...)`는 `process.exit(...)`를 호출하지 않고 숫자 exit code를 반환하며, prompt 취소는 command runner를 통해 exit code `0`으로 해석됩니다. dependency 설치나 git 초기화 같은 setup 작업은 해석된 `fluo new` 옵션이 요청한 경우에만 실행됩니다. `runGenerateCommand(...)`는 구조화된 `GenerateResult`를 반환합니다. 파일 쓰기 없이 생성 파일과 module-wiring action을 미리 보려면 `dryRun: true`를 전달하세요. 호출자가 제공한 prompt hook은 공개 패키지 엔트리포인트의 `CliPromptCancelledError`를 throw해 CLI 내부 파일에 의존하지 않고 정상 취소를 표현할 수 있습니다.
|
|
311
332
|
|
|
312
333
|
## 관련 패키지
|
|
313
334
|
|
package/README.md
CHANGED
|
@@ -31,8 +31,9 @@ pnpm dlx @fluojs/cli new my-app
|
|
|
31
31
|
## Release Contract
|
|
32
32
|
|
|
33
33
|
- `@fluojs/cli` is a public package in the intended publish surface.
|
|
34
|
+
- `@fluojs/cli` requires Node.js `>=20.0.0`; generated Bun, Deno, and Cloudflare Workers starters may target non-Node runtimes, but the CLI process itself runs on Node.js.
|
|
34
35
|
- The supported install paths are the global package (`npm install -g @fluojs/cli`, `pnpm add -g @fluojs/cli`, `bun add -g @fluojs/cli`, or `yarn global add @fluojs/cli`) and the no-install runner (`pnpm dlx @fluojs/cli ...`).
|
|
35
|
-
- The published `fluo` bin is
|
|
36
|
+
- The published `fluo` bin is the `./bin/fluo.mjs` wrapper declared in `package.json`; that wrapper loads the dist-built CLI entrypoint at `../dist/cli.js`.
|
|
36
37
|
|
|
37
38
|
## Version Inspection
|
|
38
39
|
|
|
@@ -112,7 +113,7 @@ fluo new my-grpc-service --shape microservice --transport grpc --runtime node --
|
|
|
112
113
|
|
|
113
114
|
Supported `--shape microservice --transport` starter values are exactly `tcp`, `redis-streams`, `nats`, `kafka`, `rabbitmq`, `mqtt`, and `grpc`. Use `redis-streams` for the maintained Redis-backed starter, or add `@fluojs/redis` manually after scaffolding when you need broader Redis integration patterns.
|
|
114
115
|
|
|
115
|
-
The NATS/Kafka/RabbitMQ starter contracts stay explicit about external brokers and caller-owned client libraries. Generated projects wire `nats` + `JSONCodec()`, `kafkajs` producer/consumer collaborators, and `amqplib` publisher/consumer collaborators directly in `src/app.ts` so the starter contract is runnable without pretending the base fluo packages hide those dependencies. Those broker clients are created lazily by the generated transport wrapper when the Fluo lifecycle starts listening, sends, or emits; importing `src/app.ts` for `fluo inspect`, tests, or static tooling does not connect to a broker or open external resources before the application lifecycle owns teardown.
|
|
116
|
+
The NATS/Kafka/RabbitMQ starter contracts stay explicit about external brokers and caller-owned client libraries. Generated projects wire `nats` + `JSONCodec()`, `kafkajs` producer/consumer collaborators, and `amqplib` publisher/consumer collaborators directly in `src/app.ts` so the starter contract is runnable without pretending the base fluo packages hide those dependencies. Those broker clients are created lazily by the generated transport wrapper when the Fluo lifecycle starts listening, sends, or emits; importing `src/app.ts` for `fluo inspect`, tests, or static tooling does not connect to a broker or open external resources before the application lifecycle owns teardown. During shutdown, each wrapper still attempts to close its owned clients when delegated transport shutdown fails. Kafka partial connection failures disconnect acquired clients, and RabbitMQ channel creation failures close the acquired connection before the startup error is rethrown.
|
|
116
117
|
|
|
117
118
|
The starter matrix also includes a mixed single-package starter: one Fastify HTTP app with an attached TCP microservice in the same generated project.
|
|
118
119
|
|
|
@@ -122,6 +123,18 @@ fluo new my-mixed-app --shape mixed --transport tcp --runtime node --platform fa
|
|
|
122
123
|
|
|
123
124
|
When `fluo new` runs in an interactive TTY, the wizard uses the same flags/config model. It asks for the project name, shape-first branch (`application` -> runtime + HTTP platform, `microservice` -> transport), the maintained tooling preset, package-manager choice, whether to install dependencies immediately, and whether to initialize a git repository. Non-interactive flags and programmatic `runNewCommand(...)` calls use the same resolved defaults.
|
|
124
125
|
|
|
126
|
+
By default, `fluo new my-app` writes to `./my-app`. Use `--target-directory <path>` when the project name and destination path should differ, or when automation should write to an explicit directory:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
fluo new my-app --target-directory ./apps/api
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Scaffolding refuses to overwrite conflicting files in a non-empty target by default. Add `--force` only when you intentionally want generated files with different contents to be overwritten:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
fluo new my-app --target-directory ./apps/api --force
|
|
136
|
+
```
|
|
137
|
+
|
|
125
138
|
Use `--print-plan` when you want to preview the fully resolved starter without side effects:
|
|
126
139
|
|
|
127
140
|
```bash
|
|
@@ -153,7 +166,7 @@ Supported generator kinds and aliases are `controller`/`co`, `e2e`, `guard`/`gu`
|
|
|
153
166
|
|
|
154
167
|
Auto-registered generators are `controller`, `service`, `repo`, `guard`, `interceptor`, and `middleware`. Files-only generators are `e2e`, `module`, `request-dto`, `response-dto`, and `resource`.
|
|
155
168
|
|
|
156
|
-
`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
|
|
169
|
+
`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 is a files-only/manual-activation generator: it does not wire the resource module into a parent module automatically, and it should not be treated as a `nest g resource` equivalent that activates routes for you. Import the generated module when you are ready to activate the slice.
|
|
157
170
|
|
|
158
171
|
`fluo generate e2e <name>` writes `test/<name>.e2e.test.ts` with `createTestApp({ rootModule: AppModule })` and imports `AppModule` from the default starter root module at `../src/app`, 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.
|
|
159
172
|
|
|
@@ -161,7 +174,7 @@ Request DTO generation accepts the feature directory separately from the DTO cla
|
|
|
161
174
|
|
|
162
175
|
Add `--dry-run` to preview the same target resolution, skipped or overwritten file decisions, module auto-registration plan, files-only wiring status, and next-step hint without creating directories, writing files, or updating modules. `--force` still changes existing-file plan entries from `SKIP` to `OVERWRITE` when content would change, and `--target-directory` scopes the preview to that source directory exactly as it does for a real run.
|
|
163
176
|
|
|
164
|
-
Generator discovery is intentionally limited to the built-in `@fluojs/cli/builtin` collection. External package-owned or app-local generator collections are deferred: `fluo generate` does not scan config files, load arbitrary packages, or execute workspace-owned collection code. This keeps generator metadata, option schemas, help output, and file-write boundaries deterministic and testable while preserving the shipped generator contract.
|
|
177
|
+
Generator discovery is intentionally limited to the built-in `@fluojs/cli/builtin` collection. External package-owned or app-local generator collections are deferred: `fluo generate` does not scan config files, load arbitrary packages, follow package discovery conventions, or execute workspace-owned collection code. This keeps generator metadata, option schemas, help output, and file-write boundaries deterministic and testable while preserving the shipped generator contract.
|
|
165
178
|
|
|
166
179
|
## Common Patterns
|
|
167
180
|
|
|
@@ -194,9 +207,9 @@ fluo dev --studio --studio-port 51234
|
|
|
194
207
|
fluo dev --studio --dry-run
|
|
195
208
|
```
|
|
196
209
|
|
|
197
|
-
The CLI starts a local Studio sidecar, prints a tokenized URL, keeps restart lifecycle events flowing through the sidecar, and injects an explicit Studio config into the Node app child before the app imports `@fluojs/runtime`. The sidecar serves the packaged `@fluojs/studio/viewer` React app when that optional package is installed. Runtime package source never reads `process.env` directly; it publishes live graph/routes/request/timing/diagnostic events only when CLI-injected Studio config is present.
|
|
210
|
+
The CLI starts a local Studio sidecar, prints a tokenized URL, keeps restart lifecycle events flowing through the sidecar, and injects an explicit Studio config into the Node app child before the app imports `@fluojs/runtime`. Studio live mode requires the fluo-owned Node restart runner; `fluo dev --studio` rejects `--raw-watch`, `--runner native`, and `FLUO_DEV_RUNNER=native` so lifecycle events cannot be split from the CLI restart boundary. The sidecar serves the packaged `@fluojs/studio/viewer` React app when that optional package is installed. Runtime package source never reads `process.env` directly; it publishes live graph/routes/request/timing/diagnostic events only when CLI-injected Studio config is present.
|
|
198
211
|
|
|
199
|
-
Security defaults are local-only: the sidecar binds `127.0.0.1`, runtime ingestion and browser state/SSE APIs require generated tokens, CORS is not enabled by default, and request bodies are not captured by default.
|
|
212
|
+
Security defaults are local-only: the sidecar binds `127.0.0.1`, runtime ingestion and browser state/SSE APIs require generated tokens, CORS is not enabled by default, and request bodies are not captured by default. The sidecar settles ingestion requests with a bounded error completion when a local client closes the socket after sending only a partial request body, so a malformed local client cannot hang sidecar work indefinitely.
|
|
200
213
|
|
|
201
214
|
Runtime support for the MVP is explicit:
|
|
202
215
|
|
|
@@ -251,6 +264,8 @@ fluo migrate ./src --skip tests
|
|
|
251
264
|
|
|
252
265
|
Use `--json` when CI jobs, dashboards, or migration reports need a stable machine-readable result. Human output remains the default. JSON mode writes only the structured report to stdout on success, while parser errors and invalid flag combinations still write their message to stderr and return exit code `1` without partial JSON output. The report includes `mode` (`dry-run` or `apply`), `dryRun`, `apply`, enabled `transforms`, `scannedFiles`, `changedFiles`, aggregate `warningCount`, and per-file metadata with `filePath`, `changed`, `appliedTransforms`, `warningCount`, and warnings including category labels and source line numbers.
|
|
253
266
|
|
|
267
|
+
Review every warning before rerunning with `--apply`. Warnings are manual follow-up items rather than permission for an automatic rewrite to be accepted blindly; use the [NestJS migration guide](../../docs/getting-started/migrate-from-nestjs.md) as the post-codemod checklist for each warning category.
|
|
268
|
+
|
|
254
269
|
**Key Transformations:**
|
|
255
270
|
- Rewrites imports from `@nestjs/common` to `@fluojs/core` or `@fluojs/http`.
|
|
256
271
|
- Rewrites bootstrap patterns and folds supported `listen(port)` calls into fluo runtime startup conventions.
|
|
@@ -300,14 +315,20 @@ The package can be used programmatically to trigger CLI actions from within othe
|
|
|
300
315
|
| `CliRuntimeOptions` | Type for `runCli(...)` runtime overrides such as streams, cwd, environment, registry metadata, and update-check hooks. |
|
|
301
316
|
| `newUsage()` | Returns the current `fluo new` usage text for help surfaces and tests. |
|
|
302
317
|
| `runNewCommand(argv, options?)` | Programmatic access to the project scaffolding logic. |
|
|
303
|
-
| `NewCommandRuntimeOptions` | Type for `runNewCommand(...)` runtime overrides such as prompts, filesystem writes, dependency installation, and git initialization. |
|
|
318
|
+
| `NewCommandRuntimeOptions` | Type for `runNewCommand(...)` runtime overrides such as prompts, filesystem writes, dependency installation, and git initialization; `runCli(...)` also accepts these overrides when it dispatches `new` or `create`. Monorepo-local starter dependency overrides are internal sandbox harness details, not part of this public type. |
|
|
304
319
|
| `CliPromptCancelledError` | Stable sentinel that caller-supplied prompt hooks can throw to report normal cancellation. |
|
|
320
|
+
| `runGenerateCommand(kind, name, baseDirectory, options?)` | Programmatic access to the built-in schematic generator and module auto-registration planner. |
|
|
305
321
|
| `GenerateOptions` | Type for programmatic generator options. |
|
|
306
|
-
| `
|
|
322
|
+
| `GenerateResult` | Type for generator results, including changed files, dry-run plan entries, module wiring metadata, and next-step hints. |
|
|
323
|
+
| `GeneratePlanEntry` / `GeneratePlanAction` | Types for dry-run and write-plan path actions returned by `runGenerateCommand(...)`. |
|
|
324
|
+
| `GeneratedFile` | Type describing generated file paths and in-memory content before writes. |
|
|
307
325
|
| `GeneratorKind` | Union type of all supported generator types (e.g., `'controller'`, `'service'`). |
|
|
308
|
-
| `ModuleRegistration` | Type describing module wiring
|
|
326
|
+
| `ModuleRegistration` | Type describing controller, provider, or middleware module wiring metadata from generator runs. |
|
|
327
|
+
| `inspectUsage()` | Returns the current `fluo inspect` usage text for help surfaces and tests. |
|
|
328
|
+
| `runInspectCommand(argv, options?)` | Programmatic access to inspect orchestration, JSON/report emission, and Studio Mermaid delegation. |
|
|
329
|
+
| `InspectCommandRuntimeOptions` | Type for `runInspectCommand(...)` and `runCli(...)` inspect runtime overrides such as cwd, streams, prompts, and Studio renderer loading. |
|
|
309
330
|
|
|
310
|
-
Programmatic entry points preserve caller process ownership. `runCli(...)` and `
|
|
331
|
+
Programmatic entry points preserve caller process ownership. `runCli(...)`, `runNewCommand(...)`, and `runInspectCommand(...)` return numeric exit codes instead of calling `process.exit(...)`; prompt cancellation resolves as exit code `0` through the command runner, and setup actions such as dependency installation or git initialization only run when the resolved `fluo new` options request them. `runGenerateCommand(...)` returns a structured `GenerateResult`; pass `dryRun: true` to preview generated file and module-wiring actions without writing files. Caller-supplied prompt hooks can throw `CliPromptCancelledError` from the public package entrypoint to express normal cancellation without depending on CLI-internal files.
|
|
311
332
|
|
|
312
333
|
## Related Packages
|
|
313
334
|
|
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,
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,4BAA4B,EAAqB,MAAM,uBAAuB,CAAC;AAE7F,OAAO,EAAE,KAAK,wBAAwB,EAAiB,MAAM,mBAAmB,CAAC;AAMjF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAE9D,OAAO,EAAE,KAAK,4BAA4B,EAA6C,MAAM,mBAAmB,CAAC;AAGjH,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,kBAAkB,CAAC,EAAE,OAAO,kBAAkB,CAAC;IAC/C,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;AA4ZD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,MAAM,CAC1B,IAAI,WAAwB,EAC5B,OAAO,GAAE,iBAAiB,GAAG,wBAAwB,GAAG,4BAAiC,GACxF,OAAO,CAAC,MAAM,CAAC,CAuNjB"}
|
package/dist/cli.js
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
|
-
import { existsSync,
|
|
1
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
2
2
|
import { join, resolve } from 'node:path';
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
4
|
import { diagnosticsUsage, runAnalyzeCommand, runDoctorCommand, runInfoCommand } from './commands/diagnostics.js';
|
|
5
5
|
import { runGenerateCommand } from './commands/generate.js';
|
|
6
|
-
import {
|
|
6
|
+
import { runInspectCommand } from './commands/inspect.js';
|
|
7
7
|
import { migrateUsage, runMigrateCommand } from './commands/migrate.js';
|
|
8
|
-
import {
|
|
8
|
+
import { runNewCommand } from './commands/new.js';
|
|
9
9
|
import { addUsage, runAddCommand, runUpgradeCommand, upgradeUsage } from './commands/package-workflow.js';
|
|
10
10
|
import { runScriptCommand, scriptUsage } from './commands/scripts.js';
|
|
11
11
|
import { runNodeRestartRunner } from './dev-runner/node-restart-runner.js';
|
|
12
12
|
import { builtInGeneratorCollection, generatorManifest, generatorOptionSchemas, resolveGeneratorKind } from './generators/manifest.js';
|
|
13
13
|
import { renderAliasList, renderHelpTable } from './help.js';
|
|
14
14
|
import { removeUpdateCheckFlags, runCliUpdateCheck } from './update-check.js';
|
|
15
|
+
import { inspectUsage, newUsage } from './usage.js';
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* Runtime dependency overrides for embedding the CLI in tests or higher-level tooling.
|
|
@@ -1,50 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
import type { GeneratorManifestEntry } from '../generators/manifest.js';
|
|
3
|
-
/** Describes how one generated artifact would interact with the workspace. */
|
|
4
|
-
export type GeneratePlanAction = 'create' | 'module-create' | 'module-unchanged' | 'module-update' | 'overwrite' | 'skip' | 'unchanged';
|
|
5
|
-
/** One path-level action reported by generate dry-run previews and structured results. */
|
|
6
|
-
export type GeneratePlanEntry = {
|
|
7
|
-
/** Planned action for this path. */
|
|
8
|
-
action: GeneratePlanAction;
|
|
9
|
-
/** Absolute path affected by the plan entry. */
|
|
10
|
-
path: string;
|
|
11
|
-
};
|
|
12
|
-
/**
|
|
13
|
-
* Structured result returned by {@link runGenerateCommand} for tooling-friendly automation.
|
|
14
|
-
*
|
|
15
|
-
* `generatedFiles` only includes files whose on-disk content changed during the command.
|
|
16
|
-
* `moduleRegistered` reports whether the target schematic participates in automatic module wiring,
|
|
17
|
-
* even when the target module file was already up to date.
|
|
18
|
-
*/
|
|
19
|
-
export type GenerateResult = {
|
|
20
|
-
generatedFiles: string[];
|
|
21
|
-
moduleRegistered: boolean;
|
|
22
|
-
modulePath: string | undefined;
|
|
23
|
-
nextStepHint: string;
|
|
24
|
-
plannedFiles: GeneratePlanEntry[];
|
|
25
|
-
wiringBehavior: GeneratorManifestEntry['wiringBehavior'];
|
|
26
|
-
};
|
|
27
|
-
/**
|
|
28
|
-
* Generates one CLI schematic into a source directory and returns structured wiring metadata.
|
|
29
|
-
*
|
|
30
|
-
* The command keeps generation idempotent where possible: unchanged files are not rewritten, and
|
|
31
|
-
* auto-registered schematics reuse an existing module file when it already contains the required import
|
|
32
|
-
* and registration entry.
|
|
33
|
-
*
|
|
34
|
-
* @example
|
|
35
|
-
* ```ts
|
|
36
|
-
* const result = runGenerateCommand('service', 'Post', './src');
|
|
37
|
-
*
|
|
38
|
-
* console.log(result.wiringBehavior);
|
|
39
|
-
* console.log(result.nextStepHint);
|
|
40
|
-
* ```
|
|
41
|
-
*
|
|
42
|
-
* @param kind Generator kind to execute.
|
|
43
|
-
* @param name Resource name supplied by the caller before normalization.
|
|
44
|
-
* @param baseDirectory Source directory that should receive the generated domain folder.
|
|
45
|
-
* @param options Optional generation flags that control overwrites, request DTO feature placement, and sibling-aware templates.
|
|
46
|
-
* @returns Structured file and wiring metadata for the completed generation run.
|
|
47
|
-
* @throws {Error} When the resource name is invalid, the generator kind is unknown, or the target module source cannot be updated safely.
|
|
48
|
-
*/
|
|
49
|
-
export declare function runGenerateCommand(kind: GeneratorKind, name: string, baseDirectory: string, options?: GenerateOptions): GenerateResult;
|
|
1
|
+
export { type GeneratePlanAction, type GeneratePlanEntry, type GenerateResult, runGenerateCommand } from '../generate-command.js';
|
|
50
2
|
//# sourceMappingURL=generate.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../src/commands/generate.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../src/commands/generate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,kBAAkB,EAAE,KAAK,iBAAiB,EAAE,KAAK,cAAc,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC"}
|
|
@@ -1,214 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { basename, dirname, join, normalize, relative, resolve, sep } from 'node:path';
|
|
3
|
-
import { findGeneratorDefinition } from '../generators/manifest.js';
|
|
4
|
-
import { ensureModuleImport, generateModuleFiles, registerInModule } from '../generators/module.js';
|
|
5
|
-
import { toKebabCase, toPascalCase, toPlural } from '../generators/utils.js';
|
|
6
|
-
function writeFileIfChanged(filePath, content) {
|
|
7
|
-
if (existsSync(filePath) && readFileSync(filePath, 'utf8') === content) {
|
|
8
|
-
return false;
|
|
9
|
-
}
|
|
10
|
-
writeFileSync(filePath, content, 'utf8');
|
|
11
|
-
return true;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/** Describes how one generated artifact would interact with the workspace. */
|
|
15
|
-
|
|
16
|
-
/** One path-level action reported by generate dry-run previews and structured results. */
|
|
17
|
-
|
|
18
|
-
function planFileWrite(filePath, content, options) {
|
|
19
|
-
if (!existsSync(filePath)) {
|
|
20
|
-
return {
|
|
21
|
-
action: 'create',
|
|
22
|
-
path: filePath
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
if (!options.force) {
|
|
26
|
-
return {
|
|
27
|
-
action: 'skip',
|
|
28
|
-
path: filePath
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
if (readFileSync(filePath, 'utf8') === content) {
|
|
32
|
-
return {
|
|
33
|
-
action: 'unchanged',
|
|
34
|
-
path: filePath
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
return {
|
|
38
|
-
action: 'overwrite',
|
|
39
|
-
path: filePath
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
function planModuleWrite(modulePath, content) {
|
|
43
|
-
if (!existsSync(modulePath)) {
|
|
44
|
-
return {
|
|
45
|
-
action: 'module-create',
|
|
46
|
-
path: modulePath
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
if (readFileSync(modulePath, 'utf8') === content) {
|
|
50
|
-
return {
|
|
51
|
-
action: 'module-unchanged',
|
|
52
|
-
path: modulePath
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
return {
|
|
56
|
-
action: 'module-update',
|
|
57
|
-
path: modulePath
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
function createGeneratorOptions(kind, domainDirectory, kebab, options, resolvedBase) {
|
|
61
|
-
return {
|
|
62
|
-
...options,
|
|
63
|
-
e2eRootModuleImport: options.e2eRootModuleImport ?? (kind === 'e2e' ? resolveE2eRootModuleImport(domainDirectory, resolvedBase) : undefined),
|
|
64
|
-
hasRepo: options.hasRepo ?? (kind === 'service' ? existsSync(join(domainDirectory, `${kebab}.repo.ts`)) : undefined),
|
|
65
|
-
hasService: options.hasService ?? (kind === 'controller' ? existsSync(join(domainDirectory, `${kebab}.service.ts`)) : undefined)
|
|
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')));
|
|
74
|
-
}
|
|
75
|
-
function resolveDomainDirectory(kind, resolvedBase, kebab, options) {
|
|
76
|
-
if (kind === 'e2e') {
|
|
77
|
-
return basename(resolvedBase) === 'src' ? join(dirname(resolvedBase), 'test') : join(resolvedBase, 'test');
|
|
78
|
-
}
|
|
79
|
-
if (kind === 'request-dto' && options.targetFeature !== undefined) {
|
|
80
|
-
const normalizedFeature = options.targetFeature.trim();
|
|
81
|
-
const featureKebab = assertValidResourceName(normalizedFeature);
|
|
82
|
-
const featureDirectory = /^[A-Z]/u.test(normalizedFeature) ? toPlural(featureKebab) : featureKebab;
|
|
83
|
-
return join(resolvedBase, featureDirectory);
|
|
84
|
-
}
|
|
85
|
-
return join(resolvedBase, toPlural(kebab));
|
|
86
|
-
}
|
|
87
|
-
function assertValidResourceName(name) {
|
|
88
|
-
const kebab = toKebabCase(name);
|
|
89
|
-
if (name.trim().length === 0) {
|
|
90
|
-
throw new Error('Invalid resource name: name must not be empty.');
|
|
91
|
-
}
|
|
92
|
-
if (kebab !== normalize(kebab) || kebab.includes('/') || kebab.includes('\\') || kebab.includes('..')) {
|
|
93
|
-
throw new Error(`Invalid resource name "${name}": must not contain path separators or traversal sequences.`);
|
|
94
|
-
}
|
|
95
|
-
if (!/^[a-z0-9][a-z0-9-]*$/.test(kebab)) {
|
|
96
|
-
throw new Error(`Invalid resource name "${name}": use letters, numbers, spaces, underscores, or hyphens only.`);
|
|
97
|
-
}
|
|
98
|
-
return kebab;
|
|
99
|
-
}
|
|
100
|
-
function resolveModulePath(domainDirectory, name) {
|
|
101
|
-
const kebab = toKebabCase(name);
|
|
102
|
-
return join(domainDirectory, `${kebab}.module.ts`);
|
|
103
|
-
}
|
|
104
|
-
function readOrCreateModuleSource(modulePath, name) {
|
|
105
|
-
if (existsSync(modulePath)) {
|
|
106
|
-
return readFileSync(modulePath, 'utf8');
|
|
107
|
-
}
|
|
108
|
-
const [moduleFile] = generateModuleFiles(name);
|
|
109
|
-
if (!moduleFile) {
|
|
110
|
-
throw new Error(`Unable to generate module file for resource "${name}".`);
|
|
111
|
-
}
|
|
112
|
-
return moduleFile.content;
|
|
113
|
-
}
|
|
114
|
-
function buildUpdatedModuleSource(moduleSource, arrayKey, className, importPath) {
|
|
115
|
-
let source = moduleSource;
|
|
116
|
-
source = ensureModuleImport(source, className, importPath);
|
|
117
|
-
source = registerInModule(source, arrayKey, className);
|
|
118
|
-
return source;
|
|
119
|
-
}
|
|
120
|
-
function prepareModuleUpdate(domainDirectory, normalizedName, kind, classSuffix, arrayKey) {
|
|
121
|
-
const kebab = toKebabCase(normalizedName);
|
|
122
|
-
const modulePath = resolveModulePath(domainDirectory, normalizedName);
|
|
123
|
-
const className = `${toPascalCase(normalizedName)}${classSuffix}`;
|
|
124
|
-
const importPath = `${kebab}.${kind}`;
|
|
125
|
-
const moduleSource = readOrCreateModuleSource(modulePath, normalizedName);
|
|
126
|
-
return {
|
|
127
|
-
modulePath,
|
|
128
|
-
source: buildUpdatedModuleSource(moduleSource, arrayKey, className, importPath)
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Structured result returned by {@link runGenerateCommand} for tooling-friendly automation.
|
|
134
|
-
*
|
|
135
|
-
* `generatedFiles` only includes files whose on-disk content changed during the command.
|
|
136
|
-
* `moduleRegistered` reports whether the target schematic participates in automatic module wiring,
|
|
137
|
-
* even when the target module file was already up to date.
|
|
138
|
-
*/
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Generates one CLI schematic into a source directory and returns structured wiring metadata.
|
|
142
|
-
*
|
|
143
|
-
* The command keeps generation idempotent where possible: unchanged files are not rewritten, and
|
|
144
|
-
* auto-registered schematics reuse an existing module file when it already contains the required import
|
|
145
|
-
* and registration entry.
|
|
146
|
-
*
|
|
147
|
-
* @example
|
|
148
|
-
* ```ts
|
|
149
|
-
* const result = runGenerateCommand('service', 'Post', './src');
|
|
150
|
-
*
|
|
151
|
-
* console.log(result.wiringBehavior);
|
|
152
|
-
* console.log(result.nextStepHint);
|
|
153
|
-
* ```
|
|
154
|
-
*
|
|
155
|
-
* @param kind Generator kind to execute.
|
|
156
|
-
* @param name Resource name supplied by the caller before normalization.
|
|
157
|
-
* @param baseDirectory Source directory that should receive the generated domain folder.
|
|
158
|
-
* @param options Optional generation flags that control overwrites, request DTO feature placement, and sibling-aware templates.
|
|
159
|
-
* @returns Structured file and wiring metadata for the completed generation run.
|
|
160
|
-
* @throws {Error} When the resource name is invalid, the generator kind is unknown, or the target module source cannot be updated safely.
|
|
161
|
-
*/
|
|
162
|
-
export function runGenerateCommand(kind, name, baseDirectory, options = {}) {
|
|
163
|
-
const normalizedName = name.trim();
|
|
164
|
-
const kebab = assertValidResourceName(normalizedName);
|
|
165
|
-
const generator = findGeneratorDefinition(kind);
|
|
166
|
-
const resolvedBase = resolve(baseDirectory);
|
|
167
|
-
const domainDirectory = resolveDomainDirectory(kind, resolvedBase, kebab, options);
|
|
168
|
-
const generatorOptions = createGeneratorOptions(kind, domainDirectory, kebab, options, resolvedBase);
|
|
169
|
-
const files = generator.factory(normalizedName, generatorOptions);
|
|
170
|
-
const moduleRegistration = 'moduleRegistration' in generator ? generator.moduleRegistration : undefined;
|
|
171
|
-
const moduleUpdate = moduleRegistration ? prepareModuleUpdate(domainDirectory, normalizedName, kind, moduleRegistration.classSuffix, moduleRegistration.arrayKey) : undefined;
|
|
172
|
-
const plannedFiles = files.map(file => planFileWrite(join(domainDirectory, file.path), file.content, options));
|
|
173
|
-
const modulePlan = moduleUpdate ? planModuleWrite(moduleUpdate.modulePath, moduleUpdate.source) : undefined;
|
|
174
|
-
if (options.dryRun) {
|
|
175
|
-
return {
|
|
176
|
-
generatedFiles: [],
|
|
177
|
-
moduleRegistered: moduleUpdate !== undefined,
|
|
178
|
-
modulePath: moduleUpdate?.modulePath,
|
|
179
|
-
nextStepHint: generator.nextStepHint,
|
|
180
|
-
plannedFiles: modulePlan ? [...plannedFiles, modulePlan] : plannedFiles,
|
|
181
|
-
wiringBehavior: generator.wiringBehavior
|
|
182
|
-
};
|
|
183
|
-
}
|
|
184
|
-
mkdirSync(domainDirectory, {
|
|
185
|
-
recursive: true
|
|
186
|
-
});
|
|
187
|
-
const writtenPaths = files.map(file => {
|
|
188
|
-
const filePath = join(domainDirectory, file.path);
|
|
189
|
-
if (!options.force && existsSync(filePath)) {
|
|
190
|
-
return null;
|
|
191
|
-
}
|
|
192
|
-
return writeFileIfChanged(filePath, file.content) ? filePath : null;
|
|
193
|
-
}).filter(filePath => filePath !== null);
|
|
194
|
-
let moduleRegistered = false;
|
|
195
|
-
let resolvedModulePath;
|
|
196
|
-
if (moduleUpdate && writeFileIfChanged(moduleUpdate.modulePath, moduleUpdate.source)) {
|
|
197
|
-
moduleRegistered = true;
|
|
198
|
-
resolvedModulePath = moduleUpdate.modulePath;
|
|
199
|
-
if (!writtenPaths.includes(moduleUpdate.modulePath)) {
|
|
200
|
-
writtenPaths.push(moduleUpdate.modulePath);
|
|
201
|
-
}
|
|
202
|
-
} else if (moduleUpdate) {
|
|
203
|
-
moduleRegistered = true;
|
|
204
|
-
resolvedModulePath = moduleUpdate.modulePath;
|
|
205
|
-
}
|
|
206
|
-
return {
|
|
207
|
-
generatedFiles: writtenPaths,
|
|
208
|
-
moduleRegistered: moduleRegistered,
|
|
209
|
-
modulePath: resolvedModulePath,
|
|
210
|
-
nextStepHint: generator.nextStepHint,
|
|
211
|
-
plannedFiles: modulePlan ? [...plannedFiles, modulePlan] : plannedFiles,
|
|
212
|
-
wiringBehavior: generator.wiringBehavior
|
|
213
|
-
};
|
|
214
|
-
}
|
|
1
|
+
export { runGenerateCommand } from '../generate-command.js';
|
|
@@ -32,12 +32,6 @@ export interface InspectCommandRuntimeOptions {
|
|
|
32
32
|
/** Custom stream for standard output. */
|
|
33
33
|
stdout?: CliStream;
|
|
34
34
|
}
|
|
35
|
-
/**
|
|
36
|
-
* Returns the usage information string for the inspect command.
|
|
37
|
-
*
|
|
38
|
-
* @returns Formatted help text including usage and options.
|
|
39
|
-
*/
|
|
40
|
-
export declare function inspectUsage(): string;
|
|
41
35
|
/**
|
|
42
36
|
* Executes the inspect command to visualize the application module graph.
|
|
43
37
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inspect.d.ts","sourceRoot":"","sources":["../../src/commands/inspect.ts"],"names":[],"mappings":"AAMA,OAAO,EAOL,KAAK,qBAAqB,EAC3B,MAAM,iBAAiB,CAAC;AAMzB,KAAK,SAAS,GAAG;IACf,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;CACjC,CAAC;AAEF,KAAK,eAAe,GAAG;IACrB,KAAK,CAAC,IAAI,IAAI,CAAC;IACf,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACnE,CAAC;AAEF,KAAK,cAAc,GAAG;IACpB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF,KAAK,qBAAqB,GAAG,CAAC,QAAQ,EAAE,qBAAqB,KAAK,MAAM,CAAC;AAEzE,KAAK,2BAA2B,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,qBAAqB,GAAG,SAAS,CAAC,CAAC;AAE/F;;GAEG;AACH,MAAM,WAAW,4BAA4B;IAC3C,yEAAyE;IACzE,EAAE,CAAC,EAAE,OAAO,CAAC;IACb,uDAAuD;IACvD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,yEAAyE;IACzE,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,yEAAyE;IACzE,yBAAyB,CAAC,EAAE,2BAA2B,CAAC;IACxD,wFAAwF;IACxF,MAAM,CAAC,EAAE,eAAe,CAAC;IACzB,sCAAsC;IACtC,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,4CAA4C;IAC5C,KAAK,CAAC,EAAE,cAAc,CAAC;IACvB,yCAAyC;IACzC,MAAM,CAAC,EAAE,SAAS,CAAC;CACpB;
|
|
1
|
+
{"version":3,"file":"inspect.d.ts","sourceRoot":"","sources":["../../src/commands/inspect.ts"],"names":[],"mappings":"AAMA,OAAO,EAOL,KAAK,qBAAqB,EAC3B,MAAM,iBAAiB,CAAC;AAMzB,KAAK,SAAS,GAAG;IACf,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;CACjC,CAAC;AAEF,KAAK,eAAe,GAAG;IACrB,KAAK,CAAC,IAAI,IAAI,CAAC;IACf,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACnE,CAAC;AAEF,KAAK,cAAc,GAAG;IACpB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF,KAAK,qBAAqB,GAAG,CAAC,QAAQ,EAAE,qBAAqB,KAAK,MAAM,CAAC;AAEzE,KAAK,2BAA2B,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,qBAAqB,GAAG,SAAS,CAAC,CAAC;AAE/F;;GAEG;AACH,MAAM,WAAW,4BAA4B;IAC3C,yEAAyE;IACzE,EAAE,CAAC,EAAE,OAAO,CAAC;IACb,uDAAuD;IACvD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,yEAAyE;IACzE,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,yEAAyE;IACzE,yBAAyB,CAAC,EAAE,2BAA2B,CAAC;IACxD,wFAAwF;IACxF,MAAM,CAAC,EAAE,eAAe,CAAC;IACzB,sCAAsC;IACtC,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,4CAA4C;IAC5C,KAAK,CAAC,EAAE,cAAc,CAAC;IACvB,yCAAyC;IACzC,MAAM,CAAC,EAAE,SAAS,CAAC;CACpB;AAoSD;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,GAAE,4BAAiC,GAAG,OAAO,CAAC,MAAM,CAAC,CAkDnH"}
|
package/dist/commands/inspect.js
CHANGED
|
@@ -5,66 +5,19 @@ import { pathToFileURL } from 'node:url';
|
|
|
5
5
|
import * as clack from '@clack/prompts';
|
|
6
6
|
import { FluoFactory, PLATFORM_SHELL } from '@fluojs/runtime';
|
|
7
7
|
import { tsImport } from 'tsx/esm/api';
|
|
8
|
-
import { renderAliasList, renderHelpTable } from '../help.js';
|
|
9
8
|
import { CliPromptCancelledError, isCliPromptCancelledError } from '../prompt-cancel.js';
|
|
9
|
+
import { inspectUsage } from '../usage.js';
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Runtime options for the inspect command when used programmatically.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
const INSPECT_OPTION_HELP = [{
|
|
16
|
-
aliases: [],
|
|
17
|
-
description: 'Emit the runtime platform snapshot/diagnostics payload as JSON (default when no output mode is selected).',
|
|
18
|
-
option: '--json'
|
|
19
|
-
}, {
|
|
20
|
-
aliases: [],
|
|
21
|
-
description: 'Emit a Mermaid graph through the optional @fluojs/studio rendering contract.',
|
|
22
|
-
option: '--mermaid'
|
|
23
|
-
}, {
|
|
24
|
-
aliases: [],
|
|
25
|
-
description: 'Include bootstrap timing diagnostics next to JSON inspect output.',
|
|
26
|
-
option: '--timing'
|
|
27
|
-
}, {
|
|
28
|
-
aliases: [],
|
|
29
|
-
description: 'Emit a CI-friendly JSON report with summary, snapshot, diagnostics, and timing.',
|
|
30
|
-
option: '--report'
|
|
31
|
-
}, {
|
|
32
|
-
aliases: [],
|
|
33
|
-
description: 'Write the selected inspect payload to a file instead of stdout.',
|
|
34
|
-
option: '--output <path>'
|
|
35
|
-
}, {
|
|
36
|
-
aliases: [],
|
|
37
|
-
description: 'Select the exported module symbol name (default: AppModule).',
|
|
38
|
-
option: '--export <name>'
|
|
39
|
-
}, {
|
|
40
|
-
aliases: ['-h'],
|
|
41
|
-
description: 'Show help for the inspect command.',
|
|
42
|
-
option: '--help'
|
|
43
|
-
}];
|
|
44
15
|
const STUDIO_CONTRACT_ENTRYPOINT = '@fluojs/studio/contracts';
|
|
45
16
|
const TYPESCRIPT_MODULE_EXTENSIONS = new Set(['.ts', '.tsx', '.mts', '.cts']);
|
|
46
17
|
const STUDIO_MISSING_MESSAGE = ['Mermaid graph rendering is owned by @fluojs/studio, but @fluojs/studio is not resolvable from this project.', 'Install @fluojs/studio explicitly (for example: pnpm add -D @fluojs/studio) and rerun fluo inspect --mermaid.'].join('\n');
|
|
47
18
|
function isHelpFlag(value) {
|
|
48
19
|
return value === '--help' || value === '-h';
|
|
49
20
|
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Returns the usage information string for the inspect command.
|
|
53
|
-
*
|
|
54
|
-
* @returns Formatted help text including usage and options.
|
|
55
|
-
*/
|
|
56
|
-
export function inspectUsage() {
|
|
57
|
-
return ['Usage: fluo inspect <module-path> [options]', '', 'Options', renderHelpTable(INSPECT_OPTION_HELP, [{
|
|
58
|
-
header: 'Option',
|
|
59
|
-
render: entry => entry.option
|
|
60
|
-
}, {
|
|
61
|
-
header: 'Aliases',
|
|
62
|
-
render: entry => renderAliasList(entry.aliases)
|
|
63
|
-
}, {
|
|
64
|
-
header: 'Description',
|
|
65
|
-
render: entry => entry.description
|
|
66
|
-
}]), '', 'Docs: https://github.com/fluojs/fluo/tree/main/docs/getting-started/quick-start.md'].join('\n');
|
|
67
|
-
}
|
|
68
21
|
function parseInspectArgs(argv) {
|
|
69
22
|
let modulePath;
|
|
70
23
|
let exportName = 'AppModule';
|