@fluojs/cli 1.0.0-beta.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.
Files changed (108) hide show
  1. package/LICENSE +21 -0
  2. package/README.ko.md +155 -0
  3. package/README.md +155 -0
  4. package/bin/fluo.mjs +5 -0
  5. package/dist/cli.d.ts +37 -0
  6. package/dist/cli.d.ts.map +1 -0
  7. package/dist/cli.js +292 -0
  8. package/dist/commands/generate.d.ts +40 -0
  9. package/dist/commands/generate.d.ts.map +1 -0
  10. package/dist/commands/generate.js +134 -0
  11. package/dist/commands/inspect.d.ts +30 -0
  12. package/dist/commands/inspect.d.ts.map +1 -0
  13. package/dist/commands/inspect.js +221 -0
  14. package/dist/commands/migrate.d.ts +30 -0
  15. package/dist/commands/migrate.d.ts.map +1 -0
  16. package/dist/commands/migrate.js +173 -0
  17. package/dist/commands/new.d.ts +45 -0
  18. package/dist/commands/new.d.ts.map +1 -0
  19. package/dist/commands/new.js +353 -0
  20. package/dist/generator-types.d.ts +21 -0
  21. package/dist/generator-types.d.ts.map +1 -0
  22. package/dist/generator-types.js +1 -0
  23. package/dist/generators/controller.d.ts +3 -0
  24. package/dist/generators/controller.d.ts.map +1 -0
  25. package/dist/generators/controller.js +22 -0
  26. package/dist/generators/guard.d.ts +3 -0
  27. package/dist/generators/guard.d.ts.map +1 -0
  28. package/dist/generators/guard.js +15 -0
  29. package/dist/generators/interceptor.d.ts +3 -0
  30. package/dist/generators/interceptor.d.ts.map +1 -0
  31. package/dist/generators/interceptor.js +15 -0
  32. package/dist/generators/manifest.d.ts +121 -0
  33. package/dist/generators/manifest.d.ts.map +1 -0
  34. package/dist/generators/manifest.js +130 -0
  35. package/dist/generators/middleware.d.ts +3 -0
  36. package/dist/generators/middleware.d.ts.map +1 -0
  37. package/dist/generators/middleware.js +15 -0
  38. package/dist/generators/module.d.ts +6 -0
  39. package/dist/generators/module.d.ts.map +1 -0
  40. package/dist/generators/module.js +143 -0
  41. package/dist/generators/render.d.ts +2 -0
  42. package/dist/generators/render.d.ts.map +1 -0
  43. package/dist/generators/render.js +17 -0
  44. package/dist/generators/repository.d.ts +3 -0
  45. package/dist/generators/repository.d.ts.map +1 -0
  46. package/dist/generators/repository.js +29 -0
  47. package/dist/generators/request-dto.d.ts +3 -0
  48. package/dist/generators/request-dto.d.ts.map +1 -0
  49. package/dist/generators/request-dto.js +17 -0
  50. package/dist/generators/response-dto.d.ts +3 -0
  51. package/dist/generators/response-dto.d.ts.map +1 -0
  52. package/dist/generators/response-dto.js +17 -0
  53. package/dist/generators/service.d.ts +3 -0
  54. package/dist/generators/service.d.ts.map +1 -0
  55. package/dist/generators/service.js +22 -0
  56. package/dist/generators/templates/controller.test.ts.ejs +21 -0
  57. package/dist/generators/templates/controller.ts.ejs +29 -0
  58. package/dist/generators/templates/guard.ts.ejs +7 -0
  59. package/dist/generators/templates/interceptor.ts.ejs +7 -0
  60. package/dist/generators/templates/middleware.ts.ejs +11 -0
  61. package/dist/generators/templates/module.ts.ejs +9 -0
  62. package/dist/generators/templates/repository.slice.test.ts.ejs +15 -0
  63. package/dist/generators/templates/repository.test.ts.ejs +9 -0
  64. package/dist/generators/templates/repository.ts.ejs +10 -0
  65. package/dist/generators/templates/request-dto.ts.ejs +9 -0
  66. package/dist/generators/templates/response-dto.ts.ejs +3 -0
  67. package/dist/generators/templates/service.test.ts.ejs +21 -0
  68. package/dist/generators/templates/service.ts.ejs +24 -0
  69. package/dist/generators/utils.d.ts +4 -0
  70. package/dist/generators/utils.d.ts.map +1 -0
  71. package/dist/generators/utils.js +18 -0
  72. package/dist/help.d.ts +8 -0
  73. package/dist/help.d.ts.map +1 -0
  74. package/dist/help.js +16 -0
  75. package/dist/index.d.ts +4 -0
  76. package/dist/index.d.ts.map +1 -0
  77. package/dist/index.js +2 -0
  78. package/dist/new/install.d.ts +51 -0
  79. package/dist/new/install.d.ts.map +1 -0
  80. package/dist/new/install.js +140 -0
  81. package/dist/new/package-spec-resolver.d.ts +4 -0
  82. package/dist/new/package-spec-resolver.d.ts.map +1 -0
  83. package/dist/new/package-spec-resolver.js +397 -0
  84. package/dist/new/prompt.d.ts +56 -0
  85. package/dist/new/prompt.d.ts.map +1 -0
  86. package/dist/new/prompt.js +278 -0
  87. package/dist/new/resolver.d.ts +32 -0
  88. package/dist/new/resolver.d.ts.map +1 -0
  89. package/dist/new/resolver.js +93 -0
  90. package/dist/new/scaffold.d.ts +14 -0
  91. package/dist/new/scaffold.d.ts.map +1 -0
  92. package/dist/new/scaffold.js +2010 -0
  93. package/dist/new/starter-profiles.d.ts +91 -0
  94. package/dist/new/starter-profiles.d.ts.map +1 -0
  95. package/dist/new/starter-profiles.js +347 -0
  96. package/dist/new/types.d.ts +63 -0
  97. package/dist/new/types.d.ts.map +1 -0
  98. package/dist/new/types.js +1 -0
  99. package/dist/registry.d.ts +10 -0
  100. package/dist/registry.d.ts.map +1 -0
  101. package/dist/registry.js +30 -0
  102. package/dist/transforms/nestjs-migrate.d.ts +33 -0
  103. package/dist/transforms/nestjs-migrate.d.ts.map +1 -0
  104. package/dist/transforms/nestjs-migrate.js +891 -0
  105. package/dist/types.d.ts +12 -0
  106. package/dist/types.d.ts.map +1 -0
  107. package/dist/types.js +1 -0
  108. package/package.json +65 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 fluo contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.ko.md ADDED
@@ -0,0 +1,155 @@
1
+ # @fluojs/cli
2
+
3
+ <p><a href="./README.md"><kbd>English</kbd></a> <strong><kbd>한국어</kbd></strong></p>
4
+
5
+ fluo 공식 CLI — 새 애플리케이션 부트스트랩, 컴포넌트 생성, 런타임 그래프 검사, 코드 변환을 지원합니다.
6
+
7
+ ## 목차
8
+
9
+ - [설치](#설치)
10
+ - [사용 시점](#사용-시점)
11
+ - [빠른 시작](#빠른-시작)
12
+ - [주요 패턴](#주요-패턴)
13
+ - [공개 API](#공개-api)
14
+ - [관련 패키지](#관련-패키지)
15
+ - [예제 소스](#예제-소스)
16
+
17
+ ## 설치
18
+
19
+ ```bash
20
+ pnpm add -g @fluojs/cli
21
+ ```
22
+
23
+ 설치 없이 직접 실행하려면:
24
+
25
+ ```bash
26
+ pnpm dlx @fluojs/cli new my-app
27
+ ```
28
+
29
+ ## 릴리스 계약
30
+
31
+ - `@fluojs/cli`는 intended publish surface에 포함되는 공개 패키지입니다.
32
+ - 지원되는 설치 경로는 전역 패키지(`pnpm add -g @fluojs/cli`)와 무설치 실행 경로(`pnpm dlx @fluojs/cli ...`)입니다.
33
+ - 배포되는 `fluo` bin은 `package.json`에 선언된 dist 빌드 CLI 엔트리포인트를 기준으로 동작합니다.
34
+
35
+ ## 사용 시점
36
+
37
+ - **부트스트랩**: 표준적이고 검증 가능한 구조로 새 프로젝트를 시작할 때.
38
+ - **코드 생성**: 일관된 네이밍 규칙과 자동 연결 기능을 갖춘 모듈, 컨트롤러, 서비스, 레포지토리를 생성할 때.
39
+ - **코드 변환**: 기존 코드베이스를 fluo의 표준 데코레이터 모델에 맞출 때.
40
+ - **검사(Inspection)**: 런타임 의존성 그래프를 시각화하고 플랫폼 수준의 문제를 진단할 때.
41
+
42
+ ## 빠른 시작
43
+
44
+ ### 1. 새 프로젝트 생성
45
+ 몇 초 만에 완전한 스타터 애플리케이션을 스캐폴딩합니다.
46
+
47
+ ```bash
48
+ fluo new my-app
49
+ cd my-app
50
+ pnpm dev
51
+ ```
52
+
53
+ `fluo new`는 같은 Node 기반 설치/빌드 흐름 위에서 Node.js + Fastify, Express, raw Node.js HTTP 애플리케이션 스타터를 제공합니다.
54
+
55
+ ```bash
56
+ fluo new my-app --shape application --transport http --runtime node --platform fastify
57
+ fluo new my-express-app --shape application --transport http --runtime node --platform express
58
+ fluo new my-node-app --shape application --transport http --runtime node --platform nodejs
59
+ ```
60
+
61
+ 애플리케이션 매트릭스에는 런타임별 entrypoint, scripts, dependency 세트를 갖춘 Bun, Deno, Cloudflare Workers 네이티브 스타터도 포함됩니다.
62
+
63
+ ```bash
64
+ fluo new my-bun-app --shape application --transport http --runtime bun --platform bun
65
+ fluo new my-deno-app --shape application --transport http --runtime deno --platform deno
66
+ fluo new my-worker-app --shape application --transport http --runtime cloudflare-workers --platform cloudflare-workers
67
+ ```
68
+
69
+ `fluo new`는 microservice starter path도 제공합니다. `--transport`를 생략하면 TCP가 기본 경로로 사용되며, starter 매트릭스에는 transport별 dependency, env 템플릿, entrypoint를 갖춘 Redis Streams, NATS, Kafka, RabbitMQ, MQTT, gRPC 변형도 포함됩니다.
70
+
71
+ ```bash
72
+ fluo new my-microservice --shape microservice --transport tcp --runtime node --platform none
73
+ fluo new my-redis-streams-service --shape microservice --transport redis-streams --runtime node --platform none
74
+ fluo new my-nats-service --shape microservice --transport nats --runtime node --platform none
75
+ fluo new my-kafka-service --shape microservice --transport kafka --runtime node --platform none
76
+ fluo new my-rabbitmq-service --shape microservice --transport rabbitmq --runtime node --platform none
77
+ fluo new my-mqtt-service --shape microservice --transport mqtt --runtime node --platform none
78
+ fluo new my-grpc-service --shape microservice --transport grpc --runtime node --platform none
79
+ ```
80
+
81
+ 지원되는 `--shape microservice --transport` 스타터 값은 정확히 `tcp`, `redis-streams`, `nats`, `kafka`, `rabbitmq`, `mqtt`, `grpc`입니다. 이전 문서에 있던 `redis` 값은 더 이상 제공되는 스타터 계약에 포함되지 않으며, 유지보수되는 Redis 기반 스타터가 필요하면 `redis-streams`를 사용하고, 더 넓은 Redis 통합 패턴이 필요하면 스캐폴딩 후 `@fluojs/redis`를 수동으로 추가하세요.
82
+
83
+ NATS/Kafka/RabbitMQ 스타터 계약은 외부 broker와 caller-owned client library 의존성을 숨기지 않고 명시적으로 유지합니다. 생성된 프로젝트는 `src/app.ts`에서 `nats` + `JSONCodec()`, `kafkajs` producer/consumer collaborator, `amqplib` publisher/consumer collaborator를 직접 연결하므로, 기본 fluo 패키지가 그 의존성을 감춘 것처럼 가장하지 않는 runnable starter 계약이 됩니다.
84
+
85
+ starter 매트릭스에는 mixed single-package starter도 포함됩니다. 하나의 Fastify HTTP 앱과 attached TCP microservice를 같은 생성 프로젝트 안에 함께 배치합니다.
86
+
87
+ ```bash
88
+ fluo new my-mixed-app --shape mixed --transport tcp --runtime node --platform fastify
89
+ ```
90
+
91
+ `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를 사용합니다.
92
+
93
+ 현재 제공되는 스타터 매트릭스(Node.js Fastify/Express/raw Node.js HTTP, Bun, Deno, Cloudflare Workers, TCP/Redis Streams/NATS/Kafka/RabbitMQ/MQTT/gRPC microservice, 그리고 mixed)와 남아 있는 더 넓은 어댑터 생태계를 문서 수준에서 구분한 표는 [fluo new 지원 매트릭스](../../docs/reference/fluo-new-support-matrix.ko.md)를 확인하세요. `@fluojs/redis` 같은 패키지 수준 통합은 더 넓은 생태계에 남아 있지만, 추가 `fluo new --transport` 스타터 플래그는 아닙니다.
94
+
95
+ ### 2. 기능 추가
96
+ 컨트롤러와 서비스가 포함된 새 리소스를 추가하고, 모듈에 자동으로 연결합니다.
97
+
98
+ ```bash
99
+ fluo generate module users
100
+ fluo generate controller users
101
+ fluo generate service users
102
+ ```
103
+
104
+ ## 주요 패턴
105
+
106
+ ### 데코레이터 코드 변환
107
+ 코드베이스를 TC39 표준 데코레이터에 맞게 조정하는 codemod를 실행합니다.
108
+
109
+ ```bash
110
+ # 변경 사항 미리보기 (dry-run)
111
+ fluo migrate ./src
112
+
113
+ # 변환 적용
114
+ fluo migrate ./src --apply
115
+ ```
116
+
117
+ **주요 변환 사항:**
118
+ - `@nestjs/common` 임포트를 `@fluojs/core` 또는 `@fluojs/http`로 재작성합니다.
119
+ - `@Injectable()`을 제거하고 스코프를 `@Scope()`로 매핑합니다.
120
+ - `tsconfig.json`을 업데이트하여 `experimentalDecorators`를 비활성화하고 `baseUrl` 기반 경로 별칭을 TS6-safe `paths` 엔트리로 재작성합니다.
121
+
122
+ ### 런타임 검사 (Inspection)
123
+ 애플리케이션 구조를 시각화하고 초기화 문제를 해결합니다.
124
+
125
+ ```bash
126
+ # 의존성 그래프를 Mermaid 형식으로 내보내기
127
+ fluo inspect ./src/app.module.ts --mermaid
128
+
129
+ # @fluojs/studio용 snapshot 내보내기
130
+ fluo inspect ./src/app.module.ts --json > snapshot.json
131
+ ```
132
+
133
+ ## 공개 API
134
+
135
+ 다른 도구 내에서 CLI 동작을 트리거하기 위해 패키지를 프로그래밍 방식으로 사용할 수 있습니다.
136
+
137
+ | 익스포트 | 설명 |
138
+ |---|---|
139
+ | `runCli(argv?, options?)` | 모든 CLI 명령을 실행하는 메인 진입점입니다. |
140
+ | `runNewCommand(argv, options?)` | 프로젝트 스캐폴딩 로직에 대한 프로그래밍적 접근을 제공합니다. |
141
+ | `GeneratorKind` | 지원되는 모든 생성기 유형(예: `'controller'`, `'service'`)의 유니온 타입입니다. |
142
+
143
+ ## 관련 패키지
144
+
145
+ - **[@fluojs/runtime](../runtime/README.ko.md)**: 검사 및 부트스트랩에 사용되는 기본 엔진입니다.
146
+ - **[@fluojs/studio](../studio/README.ko.md)**: `inspect --json` 출력을 시각화하기 위한 웹 기반 UI입니다.
147
+ - **[@fluojs/testing](../testing/README.ko.md)**: 통합 및 E2E 테스트를 위해 생성된 테스트 템플릿에서 사용됩니다.
148
+ - **[Canonical Runtime Package Matrix](../../docs/reference/package-surface.ko.md)**: 공식 런타임/패키지 조합을 보여주는 기준 문서입니다.
149
+
150
+ ## 예제 소스
151
+
152
+ - [cli.ts](./src/cli.ts) - 명령 디스패처 및 인자 파싱.
153
+ - [commands/new.ts](./src/commands/new.ts) - 프로젝트 스캐폴딩 구현.
154
+ - [generators/](./src/generators/) - 템플릿 기반 파일 생성 로직.
155
+ - [transforms/](./src/transforms/) - 코드 변환 구현.
package/README.md ADDED
@@ -0,0 +1,155 @@
1
+ # @fluojs/cli
2
+
3
+ <p><strong><kbd>English</kbd></strong> <a href="./README.ko.md"><kbd>한국어</kbd></a></p>
4
+
5
+ The canonical CLI for fluo — bootstrap new applications, generate components, inspect runtime graphs, and run code transforms.
6
+
7
+ ## Table of Contents
8
+
9
+ - [Installation](#installation)
10
+ - [When to Use](#when-to-use)
11
+ - [Quick Start](#quick-start)
12
+ - [Common Patterns](#common-patterns)
13
+ - [Public API](#public-api)
14
+ - [Related Packages](#related-packages)
15
+ - [Example Sources](#example-sources)
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ pnpm add -g @fluojs/cli
21
+ ```
22
+
23
+ Or run directly without installation:
24
+
25
+ ```bash
26
+ pnpm dlx @fluojs/cli new my-app
27
+ ```
28
+
29
+ ## Release Contract
30
+
31
+ - `@fluojs/cli` is a public package in the intended publish surface.
32
+ - The supported install paths are the global package (`pnpm add -g @fluojs/cli`) and the no-install runner (`pnpm dlx @fluojs/cli ...`).
33
+ - The published `fluo` bin is backed by the dist-built CLI entrypoint declared in `package.json`.
34
+
35
+ ## When to Use
36
+
37
+ - **Bootstrapping**: When starting a new project with a standard, verifiable structure.
38
+ - **Generation**: To create modules, controllers, services, and repositories with consistent naming and automatic wiring.
39
+ - **Code transforms**: When aligning an existing codebase with fluo's standard decorator model.
40
+ - **Inspection**: To visualize the runtime dependency graph and diagnose platform-level issues.
41
+
42
+ ## Quick Start
43
+
44
+ ### 1. Create a new project
45
+ Scaffold a complete starter application in seconds.
46
+
47
+ ```bash
48
+ fluo new my-app
49
+ cd my-app
50
+ pnpm dev
51
+ ```
52
+
53
+ `fluo new` supports Node.js + Fastify, Express, and raw Node.js HTTP application starters on the same Node-oriented install/build flow:
54
+
55
+ ```bash
56
+ fluo new my-app --shape application --transport http --runtime node --platform fastify
57
+ fluo new my-express-app --shape application --transport http --runtime node --platform express
58
+ fluo new my-node-app --shape application --transport http --runtime node --platform nodejs
59
+ ```
60
+
61
+ The application matrix also includes runtime-native Bun, Deno, and Cloudflare Workers starters with runtime-specific entrypoints, scripts, and dependency sets:
62
+
63
+ ```bash
64
+ fluo new my-bun-app --shape application --transport http --runtime bun --platform bun
65
+ fluo new my-deno-app --shape application --transport http --runtime deno --platform deno
66
+ fluo new my-worker-app --shape application --transport http --runtime cloudflare-workers --platform cloudflare-workers
67
+ ```
68
+
69
+ `fluo new` also exposes microservice starter paths. TCP is the default when you omit `--transport`, and the starter matrix includes runnable Redis Streams, NATS, Kafka, RabbitMQ, MQTT, and gRPC variants with transport-specific dependencies, env templates, and entrypoints:
70
+
71
+ ```bash
72
+ fluo new my-microservice --shape microservice --transport tcp --runtime node --platform none
73
+ fluo new my-redis-streams-service --shape microservice --transport redis-streams --runtime node --platform none
74
+ fluo new my-nats-service --shape microservice --transport nats --runtime node --platform none
75
+ fluo new my-kafka-service --shape microservice --transport kafka --runtime node --platform none
76
+ fluo new my-rabbitmq-service --shape microservice --transport rabbitmq --runtime node --platform none
77
+ fluo new my-mqtt-service --shape microservice --transport mqtt --runtime node --platform none
78
+ fluo new my-grpc-service --shape microservice --transport grpc --runtime node --platform none
79
+ ```
80
+
81
+ Supported `--shape microservice --transport` starter values are exactly `tcp`, `redis-streams`, `nats`, `kafka`, `rabbitmq`, `mqtt`, and `grpc`. Earlier docs mentioned `redis`, but that value is no longer part of the shipped starter contract; use `redis-streams` for the maintained Redis-backed starter, or add `@fluojs/redis` manually after scaffolding when you need broader Redis integration patterns.
82
+
83
+ 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.
84
+
85
+ The starter matrix also includes a mixed single-package starter: one Fastify HTTP app with an attached TCP microservice in the same generated project.
86
+
87
+ ```bash
88
+ fluo new my-mixed-app --shape mixed --transport tcp --runtime node --platform fastify
89
+ ```
90
+
91
+ 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.
92
+
93
+ For a docs-level table that separates the shipped starter matrix (Node.js Fastify/Express/raw Node.js HTTP, Bun, Deno, Cloudflare Workers, TCP/Redis Streams/NATS/Kafka/RabbitMQ/MQTT/gRPC microservices, plus mixed) from the remaining broader adapter ecosystem, see the [fluo new support matrix](../../docs/reference/fluo-new-support-matrix.md). Package-level integrations such as `@fluojs/redis` remain part of the broader ecosystem, but they are not extra `fluo new --transport` starter flags.
94
+
95
+ ### 2. Generate a feature
96
+ Add a new resource with a controller and service, automatically wired into the module.
97
+
98
+ ```bash
99
+ fluo generate module users
100
+ fluo generate controller users
101
+ fluo generate service users
102
+ ```
103
+
104
+ ## Common Patterns
105
+
106
+ ### Decorator Codemods
107
+ Run codemods to align your codebase with TC39 standard decorators.
108
+
109
+ ```bash
110
+ # Preview changes (dry-run)
111
+ fluo migrate ./src
112
+
113
+ # Apply transformations
114
+ fluo migrate ./src --apply
115
+ ```
116
+
117
+ **Key Transformations:**
118
+ - Rewrites imports from `@nestjs/common` to `@fluojs/core` or `@fluojs/http`.
119
+ - Removes `@Injectable()` and maps scopes to `@Scope()`.
120
+ - Updates `tsconfig.json` to disable `experimentalDecorators` and rewrites `baseUrl`-backed path aliases to TS6-safe `paths` entries.
121
+
122
+ ### Runtime Inspection
123
+ Visualize your application structure and troubleshoot initialization issues.
124
+
125
+ ```bash
126
+ # Export dependency graph as Mermaid
127
+ fluo inspect ./src/app.module.ts --mermaid
128
+
129
+ # Export snapshot for @fluojs/studio
130
+ fluo inspect ./src/app.module.ts --json > snapshot.json
131
+ ```
132
+
133
+ ## Public API
134
+
135
+ The package can be used programmatically to trigger CLI actions from within other tools.
136
+
137
+ | Export | Description |
138
+ |---|---|
139
+ | `runCli(argv?, options?)` | Main entry point to execute any CLI command. |
140
+ | `runNewCommand(argv, options?)` | Programmatic access to the project scaffolding logic. |
141
+ | `GeneratorKind` | Union type of all supported generator types (e.g., `'controller'`, `'service'`). |
142
+
143
+ ## Related Packages
144
+
145
+ - **[@fluojs/runtime](../runtime/README.md)**: The underlying engine used for inspection and bootstrap.
146
+ - **[@fluojs/studio](../studio/README.md)**: The web-based UI for visualizing `inspect --json` exports.
147
+ - **[@fluojs/testing](../testing/README.md)**: Used by generated test templates for integration and E2E testing.
148
+ - **[Canonical Runtime Package Matrix](../../docs/reference/package-surface.md)**: The source of truth for official runtime/package combinations.
149
+
150
+ ## Example Sources
151
+
152
+ - [cli.ts](./src/cli.ts) - Command dispatcher and argument parsing.
153
+ - [commands/new.ts](./src/commands/new.ts) - Project scaffolding implementation.
154
+ - [generators/](./src/generators/) - Template-based file generation logic.
155
+ - [transforms/](./src/transforms/) - Code transformation implementations.
package/bin/fluo.mjs ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { runCli } = await import('../dist/cli.js');
4
+
5
+ process.exitCode = await runCli(process.argv.slice(2));
package/dist/cli.d.ts ADDED
@@ -0,0 +1,37 @@
1
+ import { type NewCommandRuntimeOptions } from './commands/new.js';
2
+ type CliStream = {
3
+ write(message: string): unknown;
4
+ };
5
+ /**
6
+ * Runtime dependency overrides for embedding the CLI in tests or higher-level tooling.
7
+ */
8
+ export interface CliRuntimeOptions {
9
+ cwd?: string;
10
+ stderr?: CliStream;
11
+ stdout?: CliStream;
12
+ }
13
+ /**
14
+ * Runs the top-level CLI command dispatcher and returns a process-style exit code.
15
+ *
16
+ * This programmatic entry point mirrors the published `fluo` binary while allowing callers to swap
17
+ * standard streams or the working directory for tests, sandboxes, and editor integrations.
18
+ *
19
+ * @example
20
+ * ```ts
21
+ * import { runCli } from '@fluojs/cli';
22
+ *
23
+ * const output: string[] = [];
24
+ * const exitCode = await runCli(['generate', 'service', 'Post'], {
25
+ * cwd: '/workspace/app',
26
+ * stdout: { write: (chunk) => output.push(String(chunk)) },
27
+ * stderr: { write: (chunk) => output.push(String(chunk)) },
28
+ * });
29
+ * ```
30
+ *
31
+ * @param argv Argument vector to execute. Defaults to the current process arguments without the node/bin prefix.
32
+ * @param runtime Optional runtime overrides shared by the top-level dispatcher and the `new` command.
33
+ * @returns `0` when the command completes successfully, otherwise `1` after writing the error message to `stderr`.
34
+ */
35
+ export declare function runCli(argv?: string[], runtime?: CliRuntimeOptions & NewCommandRuntimeOptions): Promise<number>;
36
+ export {};
37
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,KAAK,wBAAwB,EAA2B,MAAM,mBAAmB,CAAC;AAK3F,KAAK,SAAS,GAAG;IACf,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;CACjC,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,MAAM,CAAC,EAAE,SAAS,CAAC;CACpB;AAmPD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,MAAM,CAC1B,IAAI,WAAwB,EAC5B,OAAO,GAAE,iBAAiB,GAAG,wBAA6B,GACzD,OAAO,CAAC,MAAM,CAAC,CAgGjB"}
package/dist/cli.js ADDED
@@ -0,0 +1,292 @@
1
+ import { existsSync, readdirSync } from 'node:fs';
2
+ import { join, resolve } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { runGenerateCommand } from './commands/generate.js';
5
+ import { inspectUsage, runInspectCommand } from './commands/inspect.js';
6
+ import { migrateUsage, runMigrateCommand } from './commands/migrate.js';
7
+ import { newUsage, runNewCommand } from './commands/new.js';
8
+ import { generatorManifest, resolveGeneratorKind } from './generators/manifest.js';
9
+ import { renderAliasList, renderHelpTable } from './help.js';
10
+
11
+ /**
12
+ * Runtime dependency overrides for embedding the CLI in tests or higher-level tooling.
13
+ */
14
+
15
+ const GENERATE_KIND_HELP = [...generatorManifest.map(entry => ({
16
+ aliases: [...entry.aliases],
17
+ description: entry.description,
18
+ kind: entry.kind,
19
+ schematic: entry.schematic,
20
+ wiring: entry.wiringBehavior === 'auto-registered' ? 'auto' : 'manual'
21
+ }))];
22
+ const GENERATE_OPTION_HELP = [{
23
+ aliases: ['-o'],
24
+ description: 'Write generated files under a specific source directory.',
25
+ option: '--target-directory <path>'
26
+ }, {
27
+ aliases: ['-f'],
28
+ description: 'Overwrite files that already exist.',
29
+ option: '--force'
30
+ }, {
31
+ aliases: ['-h'],
32
+ description: 'Show help for the generate command.',
33
+ option: '--help'
34
+ }];
35
+ const TOP_LEVEL_COMMAND_HELP = [{
36
+ aliases: ['create'],
37
+ command: 'new',
38
+ description: 'Scaffold a new fluo application and install dependencies.'
39
+ }, {
40
+ aliases: ['g'],
41
+ command: 'generate',
42
+ description: 'Generate a schematic inside an existing fluo application.'
43
+ }, {
44
+ aliases: [],
45
+ command: 'inspect',
46
+ description: 'Inspect runtime platform snapshot/diagnostics and emit timing optionally.'
47
+ }, {
48
+ aliases: [],
49
+ command: 'migrate',
50
+ description: 'Run NestJS-to-fluo codemods (dry-run by default).'
51
+ }, {
52
+ aliases: [],
53
+ command: 'help',
54
+ description: 'Show top-level or command-specific help.'
55
+ }];
56
+ function normalizeGeneratorKind(value) {
57
+ return resolveGeneratorKind(value);
58
+ }
59
+ function isHelpFlag(value) {
60
+ return value === '--help' || value === '-h';
61
+ }
62
+ function generateUsage() {
63
+ return ['Usage: fluo generate|g <kind> <name> [options]', '', 'Schematics', renderHelpTable(GENERATE_KIND_HELP, [{
64
+ header: 'Schematic',
65
+ render: entry => entry.schematic
66
+ }, {
67
+ header: 'Aliases',
68
+ render: entry => renderAliasList(entry.aliases)
69
+ }, {
70
+ header: 'Wiring',
71
+ render: entry => entry.wiring
72
+ }, {
73
+ header: 'Description',
74
+ render: entry => entry.description
75
+ }]), '', ' auto = class is auto-registered in the domain module (created if absent)', ' manual = files only; you must wire the generated class into a module yourself', '', 'Options', renderHelpTable(GENERATE_OPTION_HELP, [{
76
+ header: 'Option',
77
+ render: entry => entry.option
78
+ }, {
79
+ header: 'Aliases',
80
+ render: entry => renderAliasList(entry.aliases)
81
+ }, {
82
+ header: 'Description',
83
+ render: entry => entry.description
84
+ }]), '', 'Next steps:', ' Run \'pnpm typecheck\' to verify the generated module wiring.', ' Run \'pnpm test\' to execute the generated test templates.', '', 'Docs: https://github.com/fluojs/fluo/tree/main/docs/getting-started/generator-workflow.md'].join('\n');
85
+ }
86
+ function usage() {
87
+ return ['Usage: fluo <command> [options]', '', 'Commands', renderHelpTable(TOP_LEVEL_COMMAND_HELP, [{
88
+ header: 'Command',
89
+ render: entry => entry.command
90
+ }, {
91
+ header: 'Aliases',
92
+ render: entry => renderAliasList(entry.aliases)
93
+ }, {
94
+ header: 'Description',
95
+ render: entry => entry.description
96
+ }]), '', "Run 'fluo help <command>' for more information on a command.", 'Docs: https://github.com/fluojs/fluo/tree/main/docs/getting-started/quick-start.md'].join('\n');
97
+ }
98
+ function resolveDefaultTargetDirectory(startDirectory) {
99
+ const resolvedStartDirectory = resolve(startDirectory);
100
+ if (existsSync(join(resolvedStartDirectory, 'package.json')) && existsSync(join(resolvedStartDirectory, 'src'))) {
101
+ return join(resolvedStartDirectory, 'src');
102
+ }
103
+ if (existsSync(join(resolvedStartDirectory, 'apps'))) {
104
+ const appDirectories = readdirSync(join(resolvedStartDirectory, 'apps'), {
105
+ withFileTypes: true
106
+ }).filter(entry => entry.isDirectory()).map(entry => join(resolvedStartDirectory, 'apps', entry.name)).filter(directory => existsSync(join(directory, 'package.json')) && existsSync(join(directory, 'src')));
107
+ if (appDirectories.length === 1) {
108
+ return join(appDirectories[0], 'src');
109
+ }
110
+ if (appDirectories.length > 1) {
111
+ throw new Error('Multiple app targets were found under apps/. Use --target-directory to choose the app src directory explicitly.');
112
+ }
113
+ }
114
+ return resolvedStartDirectory;
115
+ }
116
+ function parseGenerateArgs(argv) {
117
+ const [command, rawKind, name, ...optionArgs] = argv;
118
+ const kind = normalizeGeneratorKind(rawKind);
119
+ if (!(command === 'g' || command === 'generate')) {
120
+ throw new Error(usage());
121
+ }
122
+ if (!kind || !name) {
123
+ throw new Error(generateUsage());
124
+ }
125
+ if (name.startsWith('-')) {
126
+ throw new Error(`Invalid resource name "${name}": names cannot start with "-".`);
127
+ }
128
+ const parsedOptions = {};
129
+ let targetDirectory;
130
+ let seenForce = false;
131
+ let seenTargetDirectory = false;
132
+ for (let index = 0; index < optionArgs.length; index += 1) {
133
+ const option = optionArgs[index];
134
+ const next = optionArgs[index + 1];
135
+ if (option === '--target-directory' || option === '-o') {
136
+ if (seenTargetDirectory) {
137
+ throw new Error('Duplicate --target-directory option.');
138
+ }
139
+ if (!next || next.startsWith('-')) {
140
+ throw new Error('Expected --target-directory to have a path value.');
141
+ }
142
+ targetDirectory = next;
143
+ seenTargetDirectory = true;
144
+ index += 1;
145
+ continue;
146
+ }
147
+ if (option === '--force' || option === '-f') {
148
+ if (seenForce) {
149
+ throw new Error('Duplicate --force option.');
150
+ }
151
+ parsedOptions.force = true;
152
+ seenForce = true;
153
+ continue;
154
+ }
155
+ throw new Error(`Unknown option: ${option}`);
156
+ }
157
+ return {
158
+ kind,
159
+ name,
160
+ options: parsedOptions,
161
+ targetDirectory
162
+ };
163
+ }
164
+ function parseCommand(argv) {
165
+ const [command] = argv;
166
+ if (command === 'new' || command === 'create') {
167
+ return {
168
+ argv: argv.slice(1),
169
+ command: 'new'
170
+ };
171
+ }
172
+ if (command === 'migrate') {
173
+ return {
174
+ argv: argv.slice(1),
175
+ command: 'migrate'
176
+ };
177
+ }
178
+ if (command === 'inspect') {
179
+ return {
180
+ argv: argv.slice(1),
181
+ command: 'inspect'
182
+ };
183
+ }
184
+ return {
185
+ argv,
186
+ command: 'generate',
187
+ parsed: parseGenerateArgs(argv)
188
+ };
189
+ }
190
+
191
+ /**
192
+ * Runs the top-level CLI command dispatcher and returns a process-style exit code.
193
+ *
194
+ * This programmatic entry point mirrors the published `fluo` binary while allowing callers to swap
195
+ * standard streams or the working directory for tests, sandboxes, and editor integrations.
196
+ *
197
+ * @example
198
+ * ```ts
199
+ * import { runCli } from '@fluojs/cli';
200
+ *
201
+ * const output: string[] = [];
202
+ * const exitCode = await runCli(['generate', 'service', 'Post'], {
203
+ * cwd: '/workspace/app',
204
+ * stdout: { write: (chunk) => output.push(String(chunk)) },
205
+ * stderr: { write: (chunk) => output.push(String(chunk)) },
206
+ * });
207
+ * ```
208
+ *
209
+ * @param argv Argument vector to execute. Defaults to the current process arguments without the node/bin prefix.
210
+ * @param runtime Optional runtime overrides shared by the top-level dispatcher and the `new` command.
211
+ * @returns `0` when the command completes successfully, otherwise `1` after writing the error message to `stderr`.
212
+ */
213
+ export async function runCli(argv = process.argv.slice(2), runtime = {}) {
214
+ const cwd = runtime.cwd ? resolve(runtime.cwd) : process.cwd();
215
+ const stdout = runtime.stdout ?? process.stdout;
216
+ const stderr = runtime.stderr ?? process.stderr;
217
+ try {
218
+ if (argv.length === 0) {
219
+ throw new Error(usage());
220
+ }
221
+ if (argv[0] === 'help') {
222
+ const topic = argv[1];
223
+ if (topic === 'new' || topic === 'create') {
224
+ stdout.write(`${newUsage()}\n`);
225
+ return 0;
226
+ }
227
+ if (topic === 'g' || topic === 'generate') {
228
+ stdout.write(`${generateUsage()}\n`);
229
+ return 0;
230
+ }
231
+ if (topic === 'migrate') {
232
+ stdout.write(`${migrateUsage()}\n`);
233
+ return 0;
234
+ }
235
+ if (topic === 'inspect') {
236
+ stdout.write(`${inspectUsage()}\n`);
237
+ return 0;
238
+ }
239
+ stdout.write(`${usage()}\n`);
240
+ return 0;
241
+ }
242
+ if (isHelpFlag(argv[0])) {
243
+ stdout.write(`${usage()}\n`);
244
+ return 0;
245
+ }
246
+ if ((argv[0] === 'g' || argv[0] === 'generate') && argv.slice(1).some(isHelpFlag)) {
247
+ stdout.write(`${generateUsage()}\n`);
248
+ return 0;
249
+ }
250
+ if (argv[0] === 'migrate' && argv.slice(1).some(isHelpFlag)) {
251
+ stdout.write(`${migrateUsage()}\n`);
252
+ return 0;
253
+ }
254
+ if (argv[0] === 'inspect' && argv.slice(1).some(isHelpFlag)) {
255
+ stdout.write(`${inspectUsage()}\n`);
256
+ return 0;
257
+ }
258
+ const parsedCommand = parseCommand(argv);
259
+ if (parsedCommand.command === 'new') {
260
+ return runNewCommand(parsedCommand.argv, runtime);
261
+ }
262
+ if (parsedCommand.command === 'migrate') {
263
+ return runMigrateCommand(parsedCommand.argv, runtime);
264
+ }
265
+ if (parsedCommand.command === 'inspect') {
266
+ return runInspectCommand(parsedCommand.argv, runtime);
267
+ }
268
+ const targetDirectory = resolve(cwd, parsedCommand.parsed.targetDirectory ?? resolveDefaultTargetDirectory(cwd));
269
+ const result = runGenerateCommand(parsedCommand.parsed.kind, parsedCommand.parsed.name, targetDirectory, parsedCommand.parsed.options);
270
+ stdout.write(`Generated ${result.generatedFiles.length} file(s):\n`);
271
+ for (const file of result.generatedFiles) {
272
+ stdout.write(` CREATE ${file}\n`);
273
+ }
274
+ stdout.write('\n');
275
+ if (result.wiringBehavior === 'auto-registered' && result.moduleRegistered) {
276
+ stdout.write(`Wiring: auto-registered in ${result.modulePath ?? 'module'}\n`);
277
+ } else if (result.wiringBehavior === 'files-only') {
278
+ stdout.write('Wiring: files only — manual registration required (see next steps)\n');
279
+ }
280
+ stdout.write(`\nNext steps:\n ${result.nextStepHint}\n`);
281
+ return 0;
282
+ } catch (error) {
283
+ const message = error instanceof Error ? error.message : String(error);
284
+ stderr.write(`${message}\n`);
285
+ return 1;
286
+ }
287
+ }
288
+ if (process.argv[1] && fileURLToPath(import.meta.url) === resolve(process.argv[1])) {
289
+ process.exitCode = await runCli(undefined, {
290
+ userAgent: process.env.npm_config_user_agent
291
+ });
292
+ }