fastnode-cli 0.5.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 -96
  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,151 +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
- createApp([${moduleName}], {
158
- executionPolicy: {
159
- timeout: 30000,
160
- mode: "inline",
161
- },
162
- }).listen(3000);
136
+ `;
137
+ }
138
+ function buildConfigTemplate() {
139
+ return `export const API_CONFIG = {
140
+ host: "0.0.0.0",
141
+ port: 3000,
142
+ };
163
143
  `;
164
144
  }
165
145
  function buildControllerTemplate(controllerName) {
@@ -168,8 +148,8 @@ function buildControllerTemplate(controllerName) {
168
148
  Body,
169
149
  Controller,
170
150
  Get,
171
- Post,
172
151
  Param,
152
+ Post,
173
153
  Query,
174
154
  } from "fastnode-core";
175
155
 
@@ -211,6 +191,76 @@ export class ${className} {
211
191
  }
212
192
  `;
213
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
+ }
214
264
  function capitalize(value) {
215
265
  return value.charAt(0).toUpperCase() + value.slice(1);
216
266
  }
@@ -233,6 +283,6 @@ function printUsage() {
233
283
  Usage:
234
284
  fastnode create <project-name>
235
285
  fastnode serve <project-name>
236
- fastnode generate controller <controller-name>
286
+ fastnode generate controller <controller-name> [module-name]
237
287
  `);
238
288
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fastnode-cli",
3
- "version": "0.5.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.5.0",
28
+ "fastnode-core": "^0.6.1",
29
29
  "reflect-metadata": "^0.1.13"
30
30
  },
31
31
  "devDependencies": {