fastnode-cli 0.1.2 β†’ 0.2.0

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.md CHANGED
@@ -1,121 +1,113 @@
1
- # πŸ§ͺ fastnode-cli
2
-
3
- **The official CLI tool for scaffolding and managing FastNode projects** β€” bringing the simplicity of FastAPI to the Node.js ecosystem with NestJS under the hood.
4
-
5
- ---
6
-
7
- ## πŸš€ Features
8
-
9
- - πŸ“¦ Instantly scaffold a FastNode-based project
10
- - 🧱 Generate modules, services, controllers, and more
11
- - βš™οΈ Built-in support for TypeScript
12
- - πŸ“‚ Clean project structure inspired by FastAPI
13
- - πŸ§ͺ Easy to extend and integrate with `fastnode-core`
14
-
15
- ---
16
-
17
- ## πŸ“¦ Installation
18
-
19
- ```bash
20
- npm install -g fastnode-cli
21
- ```
22
-
23
- ---
24
-
25
- ## πŸ› οΈ Usage
26
-
27
- ### Create a new project
28
-
29
- ```bash
30
- fastnode create my-app
31
- ```
32
- ### Install Packages
33
- ```bash
34
- cd my-app
35
- npm install
36
- ```
37
-
38
- ### Run the Project
39
- ```bash
40
- fastnode serve my-app
41
-
42
- ```
43
-
44
-
45
-
46
- This creates a new FastNode project in the `my-app` folder.
47
-
48
- ### Generate a module
49
-
50
- ```bash
51
- fastnode generate module user
52
- ```
53
-
54
- Creates a `user` module with its own folder, service, and controller.
55
-
56
- ### Generate a service
57
-
58
- ```bash
59
- fastnode generate service auth
60
- ```
61
-
62
- ### Generate a controller
63
-
64
- ```bash
65
- fastnode generate controller auth
66
- ```
67
-
68
- ---
69
-
70
- ## πŸ“ Folder Structure
71
-
72
- Here’s what a typical FastNode project looks like:
73
-
74
- ```
75
- src/
76
- β”œβ”€β”€ app.module.ts
77
- β”œβ”€β”€ main.ts
78
- └── modules/
79
- β”œβ”€β”€ user/
80
- β”‚ β”œβ”€β”€ user.controller.ts
81
- β”‚ β”œβ”€β”€ user.module.ts
82
- β”‚ └── user.service.ts
83
- ```
84
-
85
- ---
86
-
87
- ## πŸ”§ Configuration
88
-
89
- You can customize the CLI's behavior with a `.fastnoderc` config file (coming soon).
90
-
91
- ---
92
-
93
- ## πŸ“š Documentation
94
-
95
- See the core framework:
96
- πŸ‘‰ [`fastnode-core`](https://www.npmjs.com/package/fastnode-core)
97
-
98
- ---
99
-
100
- ## 🀝 Contributing
101
-
102
- Pull requests are welcome! Please open an issue first to discuss what you’d like to change.
103
-
104
- ---
105
-
106
- ## πŸ“„ License
107
-
108
- MIT License
109
-
110
- ---
111
-
112
- ## πŸ’‘ Inspiration
113
-
114
- Inspired by how fast and intuitive **FastAPI** is for Python developers, `fastnode` aims to bring that same DX (developer experience) to the **Node.js/NestJS** world.
115
-
116
- ---
117
-
118
- ## πŸ”— Links
119
-
120
- - CLI: [fastnode-cli on npm](https://www.npmjs.com/package/fastnode-cli)
121
- - Core: [fastnode-core on npm](https://www.npmjs.com/package/fastnode-core)
1
+ # fastnode-cli
2
+
3
+ `fastnode-cli` scaffolds FastNode applications and works with `fastnode-core`.
4
+
5
+ FastNode is built on Fastify and is not NestJS internally.
6
+
7
+ ## Features
8
+
9
+ - Create a new FastNode app quickly
10
+ - Generate a starter TypeScript project
11
+ - Scaffold an app that already shows execution policy usage
12
+ - Works with `fastnode-core`
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ npm install -g fastnode-cli
18
+ ```
19
+
20
+ ## Usage
21
+
22
+ ### Create a project
23
+
24
+ ```bash
25
+ fastnode create my-app
26
+ cd my-app
27
+ npm install
28
+ ```
29
+
30
+ ### Start the generated app
31
+
32
+ ```bash
33
+ fastnode serve my-app
34
+ ```
35
+
36
+ ### Generate a controller
37
+
38
+ ```bash
39
+ fastnode generate controller items
40
+ ```
41
+
42
+ This creates:
43
+
44
+ ```txt
45
+ src/items/items.controller.ts
46
+ ```
47
+
48
+ ## What the Scaffold Includes
49
+
50
+ The generated app now includes a simple execution policy example:
51
+
52
+ - app-level default execution policy in `createApp(...)`
53
+ - controller-level policy with `@ExecutionPolicy(...)`
54
+ - FastAPI-style path placeholders like `/{item_id}`
55
+ - parameter decorators like `@Param()` and `@Query()`
56
+
57
+ Example generated shape:
58
+
59
+ ```ts
60
+ import "reflect-metadata";
61
+ import {
62
+ Controller,
63
+ ExecutionPolicy,
64
+ Get,
65
+ Module,
66
+ Param,
67
+ Query,
68
+ createApp,
69
+ } from "fastnode-core";
70
+
71
+ @Controller("/hello")
72
+ @ExecutionPolicy({ timeout: 5000 })
73
+ class HelloController {
74
+ @Get("/")
75
+ hello(@Query("name") name?: string) {
76
+ return {
77
+ message: `Hello from hello${name ? `, ${name}` : ""}`,
78
+ };
79
+ }
80
+
81
+ @Get("/{item_id}")
82
+ getOne(@Param("item_id") itemId: number) {
83
+ return { item_id: itemId };
84
+ }
85
+ }
86
+
87
+ @Module({ controllers: [HelloController] })
88
+ class HelloModule {}
89
+
90
+ createApp([HelloModule], {
91
+ executionPolicy: {
92
+ timeout: 30000,
93
+ mode: "inline",
94
+ },
95
+ }).listen(3000);
96
+ ```
97
+
98
+ Generated controller files also include request-data examples for:
99
+
100
+ - `@Param("id")`
101
+ - `@Query("page")`
102
+ - `@Body()`
103
+ - `@Body("name")`
104
+
105
+ ## Related Docs
106
+
107
+ See `packages/core/README.md` for the full execution policy guide, including:
108
+
109
+ - `@ExecutionPolicy()`
110
+ - `@UseExecutionPolicy()`
111
+ - `@IsolatedHandler()`
112
+ - inline vs isolated execution
113
+ - timeout response behavior
package/dist/index.js CHANGED
@@ -7,76 +7,200 @@ Object.defineProperty(exports, "__esModule", { value: true });
7
7
  const child_process_1 = require("child_process");
8
8
  const fs_1 = __importDefault(require("fs"));
9
9
  const path_1 = __importDefault(require("path"));
10
- const [, , command, arg] = process.argv;
11
- if (command === "create" && arg) {
12
- const projectDir = path_1.default.resolve(arg);
13
- const moduleDir = path_1.default.join(projectDir, "src", arg);
10
+ const [, , command, resource, name] = process.argv;
11
+ if (command === "create" && resource) {
12
+ createProject(resource);
13
+ }
14
+ else if (command === "serve" && resource) {
15
+ serveProject(resource);
16
+ }
17
+ else if (command === "generate" && resource === "controller" && name) {
18
+ generateController(name);
19
+ }
20
+ else {
21
+ printUsage();
22
+ }
23
+ function createProject(projectName) {
24
+ const projectDir = path_1.default.resolve(projectName);
25
+ const moduleDir = path_1.default.join(projectDir, "src", projectName);
14
26
  fs_1.default.mkdirSync(moduleDir, { recursive: true });
15
- // 1️⃣ main.ts
16
- const mainFile = path_1.default.join(moduleDir, "main.ts");
17
- fs_1.default.writeFileSync(mainFile, `import "reflect-metadata";
18
- import { Controller, Get, Module, createApp } from "fastnode-core";
19
-
20
- @Controller("/${arg}")
21
- class ${capitalize(arg)}Controller {
22
- @Get("/")
23
- hello() {
24
- return { message: "Hello from ${arg}" };
25
- }
26
- }
27
-
28
- @Module({ controllers: [${capitalize(arg)}Controller] })
29
- class ${capitalize(arg)}Module {}
30
-
31
- createApp([${capitalize(arg)}Module]).listen(3000);
32
- `);
33
- // 2️⃣ tsconfig.json
34
- fs_1.default.writeFileSync(path_1.default.join(projectDir, "tsconfig.json"), `{
35
- "compilerOptions": {
36
- "target": "ES2020",
37
- "module": "CommonJS",
38
- "rootDir": "./src",
39
- "outDir": "./dist",
40
- "strict": true,
41
- "esModuleInterop": true,
42
- "experimentalDecorators": true,
43
- "emitDecoratorMetadata": true,
44
- "useDefineForClassFields": false
45
- },
46
- "include": ["src"]
47
- }
27
+ fs_1.default.writeFileSync(path_1.default.join(moduleDir, "main.ts"), buildMainTemplate(projectName));
28
+ fs_1.default.writeFileSync(path_1.default.join(projectDir, "tsconfig.json"), `{
29
+ "compilerOptions": {
30
+ "target": "ES2020",
31
+ "module": "CommonJS",
32
+ "rootDir": "./src",
33
+ "outDir": "./dist",
34
+ "strict": true,
35
+ "esModuleInterop": true,
36
+ "experimentalDecorators": true,
37
+ "emitDecoratorMetadata": true,
38
+ "useDefineForClassFields": false
39
+ },
40
+ "include": ["src"]
41
+ }
48
42
  `);
49
- // 3️⃣ package.json
50
- fs_1.default.writeFileSync(path_1.default.join(projectDir, "package.json"), `{
51
- "name": "${arg}",
52
- "version": "1.0.0",
53
- "type": "commonjs",
54
- "main": "dist/index.js",
55
- "scripts": {
56
- "dev": "tsx src/${arg}/main.ts",
57
- "build": "tsc",
58
- "start": "node dist/${arg}/main.js"
59
- },
60
- "dependencies": {
61
- "fastnode-core": "latest",
62
- "reflect-metadata": "^0.1.13"
63
- },
64
- "devDependencies": {
65
- "tsx": "^4.7.0",
66
- "typescript": "^5.0.0"
67
- }
68
- }
43
+ fs_1.default.writeFileSync(path_1.default.join(projectDir, "package.json"), `{
44
+ "name": "${projectName}",
45
+ "version": "1.0.0",
46
+ "type": "commonjs",
47
+ "main": "dist/index.js",
48
+ "scripts": {
49
+ "dev": "tsx src/${projectName}/main.ts",
50
+ "build": "tsc",
51
+ "start": "node dist/${projectName}/main.js"
52
+ },
53
+ "dependencies": {
54
+ "fastnode-core": "latest",
55
+ "reflect-metadata": "^0.1.13"
56
+ },
57
+ "devDependencies": {
58
+ "tsx": "^4.7.0",
59
+ "typescript": "^5.0.0"
60
+ }
61
+ }
69
62
  `);
70
- console.log(`βœ” Project '${arg}' created at ${projectDir}`);
63
+ console.log(`βœ” Project '${projectName}' created at ${projectDir}`);
71
64
  }
72
- if (command === "serve" && arg) {
73
- const entry = path_1.default.resolve(process.cwd(), `src/${arg}/main.ts`);
65
+ function serveProject(projectName) {
66
+ const entry = path_1.default.resolve(process.cwd(), `src/${projectName}/main.ts`);
74
67
  if (!fs_1.default.existsSync(entry)) {
75
68
  console.error(`❌ Cannot find module at ${entry}`);
76
69
  process.exit(1);
77
70
  }
78
71
  (0, child_process_1.execSync)(`npx tsx ${entry}`, { stdio: "inherit" });
79
72
  }
80
- function capitalize(str) {
81
- return str.charAt(0).toUpperCase() + str.slice(1);
73
+ function generateController(controllerName) {
74
+ const normalizedName = toKebabCase(controllerName);
75
+ const className = `${toPascalCase(controllerName)}Controller`;
76
+ const controllerDir = path_1.default.resolve(process.cwd(), "src", normalizedName);
77
+ const controllerFile = path_1.default.join(controllerDir, `${normalizedName}.controller.ts`);
78
+ fs_1.default.mkdirSync(controllerDir, { recursive: true });
79
+ if (fs_1.default.existsSync(controllerFile)) {
80
+ console.error(`❌ Controller already exists at ${controllerFile}`);
81
+ process.exit(1);
82
+ }
83
+ fs_1.default.writeFileSync(controllerFile, buildControllerTemplate(normalizedName));
84
+ console.log(`βœ” Controller '${normalizedName}' created at ${controllerFile}`);
85
+ console.log(`β„Ή Add ${className} to a module so FastNode can register it.`);
86
+ }
87
+ function buildMainTemplate(projectName) {
88
+ const className = `${toPascalCase(projectName)}Controller`;
89
+ const moduleName = `${toPascalCase(projectName)}Module`;
90
+ return `import "reflect-metadata";
91
+ import {
92
+ Controller,
93
+ ExecutionPolicy,
94
+ Get,
95
+ Module,
96
+ Param,
97
+ Query,
98
+ createApp,
99
+ } from "fastnode-core";
100
+
101
+ @Controller("/${projectName}")
102
+ @ExecutionPolicy({ timeout: 5000 })
103
+ class ${className} {
104
+ @Get("/")
105
+ hello(@Query("name") name?: string) {
106
+ return {
107
+ message: \`Hello from ${projectName}\${name ? \`, \${name}\` : ""}\`,
108
+ };
109
+ }
110
+
111
+ @Get("/{item_id}")
112
+ getOne(
113
+ @Param("item_id") itemId: number,
114
+ @Query("include") include?: string
115
+ ) {
116
+ return {
117
+ item_id: itemId,
118
+ include,
119
+ };
120
+ }
121
+ }
122
+
123
+ @Module({ controllers: [${className}] })
124
+ class ${moduleName} {}
125
+
126
+ createApp([${moduleName}], {
127
+ executionPolicy: {
128
+ timeout: 30000,
129
+ mode: "inline",
130
+ },
131
+ }).listen(3000);
132
+ `;
133
+ }
134
+ function buildControllerTemplate(controllerName) {
135
+ const className = `${toPascalCase(controllerName)}Controller`;
136
+ return `import {
137
+ Body,
138
+ Controller,
139
+ Get,
140
+ Param,
141
+ Query,
142
+ } from "fastnode-core";
143
+
144
+ @Controller("/${controllerName}")
145
+ export class ${className} {
146
+ @Get("/")
147
+ list(
148
+ @Query("page") page?: number,
149
+ @Query("search") search?: string
150
+ ) {
151
+ return {
152
+ resource: "${controllerName}",
153
+ page,
154
+ search,
155
+ };
156
+ }
157
+
158
+ @Get("/{id}")
159
+ findOne(
160
+ @Param("id") id: number,
161
+ @Query("expand") expand?: string
162
+ ) {
163
+ return {
164
+ id,
165
+ expand,
166
+ };
167
+ }
168
+
169
+ @Get("/request/echo")
170
+ echoRequest(
171
+ @Body() payload?: Record<string, unknown>,
172
+ @Body("name") name?: string
173
+ ) {
174
+ return {
175
+ payload,
176
+ name,
177
+ };
178
+ }
179
+ }
180
+ `;
181
+ }
182
+ function capitalize(value) {
183
+ return value.charAt(0).toUpperCase() + value.slice(1);
184
+ }
185
+ function toPascalCase(value) {
186
+ return toKebabCase(value)
187
+ .split("-")
188
+ .filter(Boolean)
189
+ .map(capitalize)
190
+ .join("");
191
+ }
192
+ function toKebabCase(value) {
193
+ return value
194
+ .replace(/([a-z0-9])([A-Z])/g, "$1-$2")
195
+ .replace(/[\s_]+/g, "-")
196
+ .toLowerCase();
197
+ }
198
+ function printUsage() {
199
+ console.log(`FastNode CLI
200
+
201
+ Usage:
202
+ fastnode create <project-name>
203
+ fastnode serve <project-name>
204
+ fastnode generate controller <controller-name>
205
+ `);
82
206
  }
package/package.json CHANGED
@@ -1,39 +1,41 @@
1
- {
2
- "name": "fastnode-cli",
3
- "version": "0.1.2",
4
- "description": "A CLI tool for generating and managing FastNode projects using NestJS, inspired by FastAPI.",
5
- "bin": {
6
- "fastnode": "dist/index.js"
7
- },
8
- "main": "dist/index.js",
9
- "scripts": {
10
- "start": "node bin/index.js",
11
- "build": "tsc"
12
- },
13
- "keywords": [
14
- "cli",
15
- "fastapi",
16
- "nestjs",
17
- "nodejs",
18
- "scaffolding",
19
- "generator",
20
- "fastnode"
1
+ {
2
+ "name": "fastnode-cli",
3
+ "version": "0.2.0",
4
+ "description": "The official CLI for scaffolding and serving FastNode applications.",
5
+ "bin": {
6
+ "fastnode": "dist/index.js"
7
+ },
8
+ "main": "dist/index.js",
9
+ "files": ["dist", "README.md"],
10
+ "scripts": {
11
+ "start": "node dist/index.js",
12
+ "build": "tsc",
13
+ "prepublishOnly": "npm run build"
14
+ },
15
+ "keywords": [
16
+ "cli",
17
+ "fastapi",
18
+ "nodejs",
19
+ "scaffolding",
20
+ "generator",
21
+ "fastnode"
21
22
  ],
22
23
  "author": "Valentine Emmanuel Ikechukwu",
23
24
  "license": "MIT",
24
- "dependencies": {
25
- "chalk": "^5.3.0",
26
- "commander": "^11.0.0",
27
- "fastnode-core": "latest",
28
- "reflect-metadata": "^0.1.13"
29
- },
30
- "devDependencies": {
31
- "typescript": "^5.0.0"
32
- },
25
+ "dependencies": {
26
+ "chalk": "^5.3.0",
27
+ "commander": "^11.0.0",
28
+ "fastnode-core": "^0.2.0",
29
+ "reflect-metadata": "^0.1.13"
30
+ },
31
+ "devDependencies": {
32
+ "@types/node": "^24.5.2",
33
+ "typescript": "^5.0.0"
34
+ },
33
35
  "engines": {
34
36
  "node": ">=18.0.0"
35
37
  },
36
- "publishConfig": {
37
- "access": "public"
38
- }
39
- }
38
+ "publishConfig": {
39
+ "access": "public"
40
+ }
41
+ }
package/src/index.ts DELETED
@@ -1,97 +0,0 @@
1
- #!/usr/bin/env node
2
- import { execSync } from "child_process";
3
- import fs from "fs";
4
- import path from "path";
5
-
6
- const [, , command, arg] = process.argv;
7
-
8
- if (command === "create" && arg) {
9
- const projectDir = path.resolve(arg);
10
- const moduleDir = path.join(projectDir, "src", arg);
11
-
12
- fs.mkdirSync(moduleDir, { recursive: true });
13
-
14
- // 1️⃣ main.ts
15
- const mainFile = path.join(moduleDir, "main.ts");
16
- fs.writeFileSync(
17
- mainFile,
18
- `import "reflect-metadata";
19
- import { Controller, Get, Module, createApp } from "fastnode-core";
20
-
21
- @Controller("/${arg}")
22
- class ${capitalize(arg)}Controller {
23
- @Get("/")
24
- hello() {
25
- return { message: "Hello from ${arg}" };
26
- }
27
- }
28
-
29
- @Module({ controllers: [${capitalize(arg)}Controller] })
30
- class ${capitalize(arg)}Module {}
31
-
32
- createApp([${capitalize(arg)}Module]).listen(3000);
33
- `
34
- );
35
-
36
- // 2️⃣ tsconfig.json
37
- fs.writeFileSync(
38
- path.join(projectDir, "tsconfig.json"),
39
- `{
40
- "compilerOptions": {
41
- "target": "ES2020",
42
- "module": "CommonJS",
43
- "rootDir": "./src",
44
- "outDir": "./dist",
45
- "strict": true,
46
- "esModuleInterop": true,
47
- "experimentalDecorators": true,
48
- "emitDecoratorMetadata": true,
49
- "useDefineForClassFields": false
50
- },
51
- "include": ["src"]
52
- }
53
- `
54
- );
55
-
56
- // 3️⃣ package.json
57
- fs.writeFileSync(
58
- path.join(projectDir, "package.json"),
59
- `{
60
- "name": "${arg}",
61
- "version": "1.0.0",
62
- "type": "commonjs",
63
- "main": "dist/index.js",
64
- "scripts": {
65
- "dev": "tsx src/${arg}/main.ts",
66
- "build": "tsc",
67
- "start": "node dist/${arg}/main.js"
68
- },
69
- "dependencies": {
70
- "fastnode-core": "latest",
71
- "reflect-metadata": "^0.1.13"
72
- },
73
- "devDependencies": {
74
- "tsx": "^4.7.0",
75
- "typescript": "^5.0.0"
76
- }
77
- }
78
- `
79
- );
80
-
81
- console.log(`βœ” Project '${arg}' created at ${projectDir}`);
82
- }
83
-
84
- if (command === "serve" && arg) {
85
- const entry = path.resolve(process.cwd(), `src/${arg}/main.ts`);
86
-
87
- if (!fs.existsSync(entry)) {
88
- console.error(`❌ Cannot find module at ${entry}`);
89
- process.exit(1);
90
- }
91
-
92
- execSync(`npx tsx ${entry}`, { stdio: "inherit" });
93
- }
94
-
95
- function capitalize(str: string): string {
96
- return str.charAt(0).toUpperCase() + str.slice(1);
97
- }
package/tsconfig.json DELETED
@@ -1,9 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "module": "CommonJS",
4
- "target": "ES2020",
5
- "esModuleInterop": true,
6
- "outDir": "dist"
7
- },
8
- "include": ["src"]
9
- }