fastnode-cli 0.6.0 → 0.6.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 (2) hide show
  1. package/dist/index.js +146 -97
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -7,7 +7,7 @@ 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, resource, name] = process.argv;
10
+ const [, , command, resource, name, moduleName] = process.argv;
11
11
  if (command === "create" && resource) {
12
12
  createProject(resource);
13
13
  }
@@ -15,152 +15,131 @@ else if (command === "serve" && resource) {
15
15
  serveProject(resource);
16
16
  }
17
17
  else if (command === "generate" && resource === "controller" && name) {
18
- generateController(name);
18
+ generateController(name, moduleName);
19
19
  }
20
20
  else {
21
21
  printUsage();
22
22
  }
23
23
  function createProject(projectName) {
24
24
  const projectDir = path_1.default.resolve(projectName);
25
- const moduleDir = path_1.default.join(projectDir, "src", projectName);
26
- fs_1.default.mkdirSync(moduleDir, { recursive: true });
27
- fs_1.default.writeFileSync(path_1.default.join(moduleDir, "main.ts"), buildMainTemplate(projectName));
25
+ const srcDir = path_1.default.join(projectDir, "src");
26
+ const constantsDir = path_1.default.join(srcDir, "constants");
27
+ const controllersDir = path_1.default.join(srcDir, "controllers");
28
+ const servicesDir = path_1.default.join(srcDir, "services");
29
+ const helpersDir = path_1.default.join(srcDir, "helpers");
30
+ fs_1.default.mkdirSync(constantsDir, { recursive: true });
31
+ fs_1.default.mkdirSync(controllersDir, { recursive: true });
32
+ fs_1.default.mkdirSync(servicesDir, { recursive: true });
33
+ fs_1.default.mkdirSync(helpersDir, { recursive: true });
34
+ fs_1.default.writeFileSync(path_1.default.join(srcDir, "main.ts"), buildMainTemplate());
35
+ fs_1.default.writeFileSync(path_1.default.join(srcDir, "app.module.ts"), buildAppModuleTemplate(projectName));
36
+ fs_1.default.writeFileSync(path_1.default.join(controllersDir, "app.controller.ts"), buildAppControllerTemplate(projectName));
37
+ fs_1.default.writeFileSync(path_1.default.join(constantsDir, "config.ts"), buildConfigTemplate());
28
38
  fs_1.default.writeFileSync(path_1.default.join(projectDir, "tsconfig.json"), `{
29
39
  "compilerOptions": {
30
- "target": "ES2020",
31
- "module": "CommonJS",
32
- "rootDir": "./src",
33
- "outDir": "./dist",
40
+ "target": "ES2022",
41
+ "module": "es2022",
42
+ "moduleResolution": "bundler",
43
+ "rootDir": "src",
44
+ "outDir": "dist",
34
45
  "strict": true,
35
46
  "esModuleInterop": true,
36
47
  "experimentalDecorators": true,
37
48
  "emitDecoratorMetadata": true,
38
- "useDefineForClassFields": false
49
+ "skipLibCheck": true,
50
+ "resolveJsonModule": true,
51
+ "types": ["node"]
39
52
  },
40
- "include": ["src"]
53
+ "include": ["src/**/*.ts"],
54
+ "exclude": ["dist", "node_modules"]
41
55
  }
42
56
  `);
43
57
  fs_1.default.writeFileSync(path_1.default.join(projectDir, "package.json"), `{
44
58
  "name": "${projectName}",
45
59
  "version": "1.0.0",
46
- "type": "commonjs",
47
- "main": "dist/index.js",
60
+ "type": "module",
48
61
  "scripts": {
49
- "dev": "tsx src/${projectName}/main.ts",
50
- "build": "tsc",
51
- "start": "node dist/${projectName}/main.js"
62
+ "dev": "tsx watch src/main.ts",
63
+ "build": "tsc -p tsconfig.json",
64
+ "start": "node dist/main.js"
52
65
  },
53
66
  "dependencies": {
54
- "fastnode-core": "latest",
55
- "reflect-metadata": "^0.1.13"
67
+ "fastnode-core": "^0.6.1",
68
+ "reflect-metadata": "^0.2.2"
56
69
  },
57
70
  "devDependencies": {
58
- "tsx": "^4.7.0",
59
- "typescript": "^5.0.0"
71
+ "@types/node": "^24.12.0",
72
+ "tsx": "^4.21.0",
73
+ "typescript": "^5.9.3"
60
74
  }
61
75
  }
62
76
  `);
63
- console.log(`✔ Project '${projectName}' created at ${projectDir}`);
77
+ console.log(`Project '${projectName}' created at ${projectDir}`);
64
78
  }
65
79
  function serveProject(projectName) {
66
- const entry = path_1.default.resolve(process.cwd(), `src/${projectName}/main.ts`);
80
+ const entry = path_1.default.resolve(process.cwd(), "src", "main.ts");
67
81
  if (!fs_1.default.existsSync(entry)) {
68
- console.error(`❌ Cannot find module at ${entry}`);
82
+ console.error(`Cannot find module at ${entry}`);
69
83
  process.exit(1);
70
84
  }
71
85
  (0, child_process_1.execSync)(`npx tsx ${entry}`, { stdio: "inherit" });
72
86
  }
73
- function generateController(controllerName) {
87
+ function generateController(controllerName, moduleName) {
74
88
  const normalizedName = toKebabCase(controllerName);
75
89
  const className = `${toPascalCase(controllerName)}Controller`;
76
- const controllerDir = path_1.default.resolve(process.cwd(), "src", normalizedName);
90
+ const moduleFile = resolveModuleFile(moduleName);
91
+ const controllerDir = path_1.default.resolve(process.cwd(), "src", "controllers");
77
92
  const controllerFile = path_1.default.join(controllerDir, `${normalizedName}.controller.ts`);
78
93
  fs_1.default.mkdirSync(controllerDir, { recursive: true });
79
94
  if (fs_1.default.existsSync(controllerFile)) {
80
- console.error(`❌ Controller already exists at ${controllerFile}`);
95
+ console.error(`Controller already exists at ${controllerFile}`);
81
96
  process.exit(1);
82
97
  }
83
98
  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.`);
99
+ updateModuleControllers(moduleFile, className, normalizedName);
100
+ console.log(`Controller '${normalizedName}' created at ${controllerFile}`);
101
+ console.log(`Added ${className} to ${path_1.default.basename(moduleFile)}.`);
86
102
  }
87
- function buildMainTemplate(projectName) {
88
- const className = `${toPascalCase(projectName)}Controller`;
89
- const moduleName = `${toPascalCase(projectName)}Module`;
103
+ function buildMainTemplate() {
90
104
  return `import "reflect-metadata";
91
- import {
92
- Body,
93
- Context,
94
- Controller,
95
- EmitToClient,
96
- ExecutionPolicy,
97
- Get,
98
- Module,
99
- Param,
100
- Post,
101
- Query,
102
- SocketController,
103
- Subscribe,
104
- createApp,
105
- } from "fastnode-core";
106
-
107
- @Controller("/${projectName}")
108
- @ExecutionPolicy({ timeout: 5000 })
109
- class ${className} {
110
- @Get("/")
111
- async hello(
112
- @Query("name") name?: string,
113
- @Context() ctx?: any
114
- ) {
115
- await ctx?.sleep(25);
105
+ import { createApp } from "fastnode-core";
106
+ import { AppModule } from "./app.module.js";
107
+ import { API_CONFIG } from "./constants/config.js";
116
108
 
117
- return {
118
- message: \`Hello from ${projectName}\${name ? \`, \${name}\` : ""}\`,
119
- deadline: ctx?.snapshot?.().deadline,
120
- };
121
- }
109
+ export const app = createApp([AppModule], {
122
110
 
123
- @Get("/{item_id}")
124
- getOne(
125
- @Param("item_id") itemId: number,
126
- @Query("include") include?: string
127
- ) {
128
- return {
129
- item_id: itemId,
130
- include,
131
- };
132
- }
111
+ }).listen({ port: API_CONFIG.port, host: API_CONFIG.host });
112
+ `;
113
+ }
114
+ function buildAppModuleTemplate(projectName) {
115
+ const controllerName = `${toPascalCase(projectName)}Controller`;
116
+ return `import { Module } from "fastnode-core";
117
+ import { ${controllerName} } from "./controllers/app.controller.js";
133
118
 
134
- @Post("/")
135
- create() {
136
- return {
137
- created: true,
138
- };
139
- }
119
+ @Module({ controllers: [${controllerName}] })
120
+ export class AppModule {}
121
+ `;
140
122
  }
123
+ function buildAppControllerTemplate(projectName) {
124
+ const className = `${toPascalCase(projectName)}Controller`;
125
+ return `import { Controller, Get } from "fastnode-core";
141
126
 
142
- @SocketController("/chat")
143
- class ${toPascalCase(projectName)}SocketController {
144
- @Subscribe("ping")
145
- @EmitToClient()
146
- ping(@Body("message") message?: string) {
127
+ @Controller("/")
128
+ export class ${className} {
129
+ @Get("/")
130
+ getHello() {
147
131
  return {
148
- message: message ?? "pong",
132
+ message: "Hello from ${projectName}",
149
133
  };
150
134
  }
151
135
  }
152
-
153
- @Module({ controllers: [${className}, ${toPascalCase(projectName)}SocketController] })
154
- class ${moduleName} {}
155
-
156
- // Drop files into ./static to serve them automatically, for example static/logo.png -> /logo.png.
157
- // Upload routes can use @Upload() and @File() to save files under ./static/uploads by default.
158
- createApp([${moduleName}], {
159
- executionPolicy: {
160
- timeout: 30000,
161
- mode: "inline",
162
- },
163
- }).listen(3000);
136
+ `;
137
+ }
138
+ function buildConfigTemplate() {
139
+ return `export const API_CONFIG = {
140
+ host: "0.0.0.0",
141
+ port: 3000,
142
+ };
164
143
  `;
165
144
  }
166
145
  function buildControllerTemplate(controllerName) {
@@ -169,8 +148,8 @@ function buildControllerTemplate(controllerName) {
169
148
  Body,
170
149
  Controller,
171
150
  Get,
172
- Post,
173
151
  Param,
152
+ Post,
174
153
  Query,
175
154
  } from "fastnode-core";
176
155
 
@@ -212,6 +191,76 @@ export class ${className} {
212
191
  }
213
192
  `;
214
193
  }
194
+ function resolveModuleFile(moduleName) {
195
+ const srcDir = path_1.default.resolve(process.cwd(), "src");
196
+ if (!fs_1.default.existsSync(srcDir)) {
197
+ console.error(`Cannot find src directory at ${srcDir}`);
198
+ process.exit(1);
199
+ }
200
+ if (!moduleName) {
201
+ const defaultModuleFile = path_1.default.join(srcDir, "app.module.ts");
202
+ if (!fs_1.default.existsSync(defaultModuleFile)) {
203
+ console.error(`Cannot find default module at ${defaultModuleFile}`);
204
+ process.exit(1);
205
+ }
206
+ return defaultModuleFile;
207
+ }
208
+ const expectedFileName = `${toKebabCase(moduleName)}.module.ts`;
209
+ const moduleFile = findFileRecursive(srcDir, expectedFileName);
210
+ if (!moduleFile) {
211
+ console.error(`Cannot find module '${moduleName}'. Expected ${expectedFileName} under ${srcDir}`);
212
+ process.exit(1);
213
+ }
214
+ return moduleFile;
215
+ }
216
+ function findFileRecursive(dir, fileName) {
217
+ const entries = fs_1.default.readdirSync(dir, { withFileTypes: true });
218
+ for (const entry of entries) {
219
+ const fullPath = path_1.default.join(dir, entry.name);
220
+ if (entry.isDirectory()) {
221
+ const match = findFileRecursive(fullPath, fileName);
222
+ if (match) {
223
+ return match;
224
+ }
225
+ }
226
+ else if (entry.isFile() && entry.name === fileName) {
227
+ return fullPath;
228
+ }
229
+ }
230
+ return undefined;
231
+ }
232
+ function updateModuleControllers(moduleFile, controllerClassName, controllerName) {
233
+ const controllerFile = path_1.default.resolve(process.cwd(), "src", "controllers", `${controllerName}.controller.ts`);
234
+ const importStatement = `import { ${controllerClassName} } from "${buildRelativeImportPath(moduleFile, controllerFile)}";`;
235
+ let moduleSource = fs_1.default.readFileSync(moduleFile, "utf8");
236
+ if (!moduleSource.includes(importStatement)) {
237
+ moduleSource = `${importStatement}\n${moduleSource}`;
238
+ }
239
+ const controllersMatch = moduleSource.match(/controllers\s*:\s*\[([\s\S]*?)\]/);
240
+ if (!controllersMatch) {
241
+ console.error(`Could not find a controllers array inside ${moduleFile}`);
242
+ process.exit(1);
243
+ }
244
+ const existingControllers = controllersMatch[1]
245
+ .split(",")
246
+ .map((value) => value.trim())
247
+ .filter(Boolean);
248
+ if (!existingControllers.includes(controllerClassName)) {
249
+ existingControllers.push(controllerClassName);
250
+ }
251
+ moduleSource = moduleSource.replace(/controllers\s*:\s*\[[\s\S]*?\]/, `controllers: [${existingControllers.join(", ")}]`);
252
+ fs_1.default.writeFileSync(moduleFile, moduleSource);
253
+ }
254
+ function buildRelativeImportPath(fromFile, toFile) {
255
+ const relativePath = path_1.default
256
+ .relative(path_1.default.dirname(fromFile), toFile)
257
+ .replace(/\\/g, "/")
258
+ .replace(/\.ts$/, ".js");
259
+ if (relativePath.startsWith(".")) {
260
+ return relativePath;
261
+ }
262
+ return `./${relativePath}`;
263
+ }
215
264
  function capitalize(value) {
216
265
  return value.charAt(0).toUpperCase() + value.slice(1);
217
266
  }
@@ -234,6 +283,6 @@ function printUsage() {
234
283
  Usage:
235
284
  fastnode create <project-name>
236
285
  fastnode serve <project-name>
237
- fastnode generate controller <controller-name>
286
+ fastnode generate controller <controller-name> [module-name]
238
287
  `);
239
288
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fastnode-cli",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "The official CLI for scaffolding and serving FastNode applications.",
5
5
  "bin": {
6
6
  "fastnode": "dist/index.js"
@@ -25,7 +25,7 @@
25
25
  "dependencies": {
26
26
  "chalk": "^5.3.0",
27
27
  "commander": "^11.0.0",
28
- "fastnode-core": "^0.6.0",
28
+ "fastnode-core": "^0.6.1",
29
29
  "reflect-metadata": "^0.1.13"
30
30
  },
31
31
  "devDependencies": {