fastnode-cli 0.6.1 → 0.6.2

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 +148 -12
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -14,6 +14,9 @@ if (command === "create" && resource) {
14
14
  else if (command === "serve" && resource) {
15
15
  serveProject(resource);
16
16
  }
17
+ else if (command === "build") {
18
+ buildProject();
19
+ }
17
20
  else if (command === "generate" && resource === "controller" && name) {
18
21
  generateController(name, moduleName);
19
22
  }
@@ -79,11 +82,36 @@ function createProject(projectName) {
79
82
  function serveProject(projectName) {
80
83
  const entry = path_1.default.resolve(process.cwd(), "src", "main.ts");
81
84
  if (!fs_1.default.existsSync(entry)) {
82
- console.error(`Cannot find module at ${entry}`);
83
- process.exit(1);
85
+ fail(`Cannot find module at ${entry}`);
84
86
  }
85
87
  (0, child_process_1.execSync)(`npx tsx ${entry}`, { stdio: "inherit" });
86
88
  }
89
+ function buildProject() {
90
+ const errors = validateProjectConfiguration();
91
+ if (errors.length > 0) {
92
+ printValidationErrors(errors);
93
+ process.exit(1);
94
+ }
95
+ try {
96
+ clearBuildOutput();
97
+ (0, child_process_1.execSync)("npx tsc -p tsconfig.json", { stdio: "inherit" });
98
+ console.log("Build completed successfully.");
99
+ }
100
+ catch (error) {
101
+ console.error("Build failed.");
102
+ if (error instanceof Error && error.message) {
103
+ console.error(error.message);
104
+ }
105
+ process.exit(1);
106
+ }
107
+ }
108
+ function clearBuildOutput() {
109
+ const distDir = path_1.default.resolve(process.cwd(), "dist");
110
+ if (!fs_1.default.existsSync(distDir)) {
111
+ return;
112
+ }
113
+ fs_1.default.rmSync(distDir, { recursive: true, force: true });
114
+ }
87
115
  function generateController(controllerName, moduleName) {
88
116
  const normalizedName = toKebabCase(controllerName);
89
117
  const className = `${toPascalCase(controllerName)}Controller`;
@@ -92,14 +120,112 @@ function generateController(controllerName, moduleName) {
92
120
  const controllerFile = path_1.default.join(controllerDir, `${normalizedName}.controller.ts`);
93
121
  fs_1.default.mkdirSync(controllerDir, { recursive: true });
94
122
  if (fs_1.default.existsSync(controllerFile)) {
95
- console.error(`Controller already exists at ${controllerFile}`);
96
- process.exit(1);
123
+ fail(`Controller already exists at ${controllerFile}`);
97
124
  }
98
125
  fs_1.default.writeFileSync(controllerFile, buildControllerTemplate(normalizedName));
99
126
  updateModuleControllers(moduleFile, className, normalizedName);
100
127
  console.log(`Controller '${normalizedName}' created at ${controllerFile}`);
101
128
  console.log(`Added ${className} to ${path_1.default.basename(moduleFile)}.`);
102
129
  }
130
+ function validateProjectConfiguration() {
131
+ const errors = [];
132
+ const packageJsonPath = path_1.default.resolve(process.cwd(), "package.json");
133
+ const tsconfigPath = path_1.default.resolve(process.cwd(), "tsconfig.json");
134
+ const mainFilePath = path_1.default.resolve(process.cwd(), "src", "main.ts");
135
+ const packageJson = readJsonFile(packageJsonPath, "package.json", errors);
136
+ const tsconfig = readJsonFile(tsconfigPath, "tsconfig.json", errors);
137
+ if (!fs_1.default.existsSync(mainFilePath)) {
138
+ errors.push(`Missing entry file: ${mainFilePath}`);
139
+ }
140
+ validatePackageJson(packageJson, errors);
141
+ validateTsconfig(tsconfig, errors);
142
+ return errors;
143
+ }
144
+ function validatePackageJson(packageJson, errors) {
145
+ if (!packageJson) {
146
+ return;
147
+ }
148
+ const scripts = asRecord(packageJson.scripts);
149
+ const dependencies = asRecord(packageJson.dependencies);
150
+ const devDependencies = asRecord(packageJson.devDependencies);
151
+ if (packageJson.type !== "module") {
152
+ errors.push(`package.json: expected "type" to be "module".`);
153
+ }
154
+ if (typeof scripts?.build !== "string" || !scripts.build.includes("tsc")) {
155
+ errors.push(`package.json: expected scripts.build to run TypeScript, for example "tsc -p tsconfig.json".`);
156
+ }
157
+ if (typeof dependencies?.["fastnode-core"] !== "string") {
158
+ errors.push(`package.json: missing dependency "fastnode-core".`);
159
+ }
160
+ else if (!hasLocalInstalledPackage("fastnode-core")) {
161
+ errors.push(`package.json: dependency "fastnode-core" is not installed. Run npm install.`);
162
+ }
163
+ if (typeof dependencies?.["reflect-metadata"] !== "string") {
164
+ errors.push(`package.json: missing dependency "reflect-metadata".`);
165
+ }
166
+ else if (!hasLocalInstalledPackage("reflect-metadata")) {
167
+ errors.push(`package.json: dependency "reflect-metadata" is not installed. Run npm install.`);
168
+ }
169
+ if (typeof devDependencies?.typescript !== "string") {
170
+ errors.push(`package.json: missing devDependency "typescript".`);
171
+ }
172
+ else if (!hasLocalInstalledPackage("typescript")) {
173
+ errors.push(`package.json: devDependency "typescript" is not installed. Run npm install.`);
174
+ }
175
+ }
176
+ function validateTsconfig(tsconfig, errors) {
177
+ if (!tsconfig) {
178
+ return;
179
+ }
180
+ const compilerOptions = asRecord(tsconfig.compilerOptions);
181
+ if (!compilerOptions) {
182
+ errors.push(`tsconfig.json: missing compilerOptions.`);
183
+ return;
184
+ }
185
+ if (compilerOptions.rootDir !== "src") {
186
+ errors.push(`tsconfig.json: expected compilerOptions.rootDir to be "src".`);
187
+ }
188
+ if (compilerOptions.outDir !== "dist") {
189
+ errors.push(`tsconfig.json: expected compilerOptions.outDir to be "dist".`);
190
+ }
191
+ if (compilerOptions.moduleResolution !== "bundler") {
192
+ errors.push(`tsconfig.json: expected compilerOptions.moduleResolution to be "bundler".`);
193
+ }
194
+ if (compilerOptions.experimentalDecorators !== true) {
195
+ errors.push(`tsconfig.json: expected compilerOptions.experimentalDecorators to be true.`);
196
+ }
197
+ if (compilerOptions.emitDecoratorMetadata !== true) {
198
+ errors.push(`tsconfig.json: expected compilerOptions.emitDecoratorMetadata to be true.`);
199
+ }
200
+ if (compilerOptions.module !== "es2022" &&
201
+ compilerOptions.module !== "ES2022") {
202
+ errors.push(`tsconfig.json: expected compilerOptions.module to be "es2022".`);
203
+ }
204
+ }
205
+ function readJsonFile(filePath, label, errors) {
206
+ if (!fs_1.default.existsSync(filePath)) {
207
+ errors.push(`Missing ${label} at ${filePath}`);
208
+ return undefined;
209
+ }
210
+ try {
211
+ const content = fs_1.default.readFileSync(filePath, "utf8");
212
+ return JSON.parse(content);
213
+ }
214
+ catch (error) {
215
+ const message = error instanceof Error ? error.message : "Unknown JSON parsing error.";
216
+ errors.push(`Invalid ${label}: ${message}`);
217
+ return undefined;
218
+ }
219
+ }
220
+ function printValidationErrors(errors) {
221
+ console.error("FastNode build validation failed.");
222
+ console.error("");
223
+ for (const error of errors) {
224
+ console.error(`- ${error}`);
225
+ }
226
+ console.error("");
227
+ console.error("Update your package.json and tsconfig.json, then run `fastnode build` again.");
228
+ }
103
229
  function buildMainTemplate() {
104
230
  return `import "reflect-metadata";
105
231
  import { createApp } from "fastnode-core";
@@ -194,22 +320,19 @@ export class ${className} {
194
320
  function resolveModuleFile(moduleName) {
195
321
  const srcDir = path_1.default.resolve(process.cwd(), "src");
196
322
  if (!fs_1.default.existsSync(srcDir)) {
197
- console.error(`Cannot find src directory at ${srcDir}`);
198
- process.exit(1);
323
+ fail(`Cannot find src directory at ${srcDir}`);
199
324
  }
200
325
  if (!moduleName) {
201
326
  const defaultModuleFile = path_1.default.join(srcDir, "app.module.ts");
202
327
  if (!fs_1.default.existsSync(defaultModuleFile)) {
203
- console.error(`Cannot find default module at ${defaultModuleFile}`);
204
- process.exit(1);
328
+ fail(`Cannot find default module at ${defaultModuleFile}`);
205
329
  }
206
330
  return defaultModuleFile;
207
331
  }
208
332
  const expectedFileName = `${toKebabCase(moduleName)}.module.ts`;
209
333
  const moduleFile = findFileRecursive(srcDir, expectedFileName);
210
334
  if (!moduleFile) {
211
- console.error(`Cannot find module '${moduleName}'. Expected ${expectedFileName} under ${srcDir}`);
212
- process.exit(1);
335
+ fail(`Cannot find module '${moduleName}'. Expected ${expectedFileName} under ${srcDir}`);
213
336
  }
214
337
  return moduleFile;
215
338
  }
@@ -238,8 +361,7 @@ function updateModuleControllers(moduleFile, controllerClassName, controllerName
238
361
  }
239
362
  const controllersMatch = moduleSource.match(/controllers\s*:\s*\[([\s\S]*?)\]/);
240
363
  if (!controllersMatch) {
241
- console.error(`Could not find a controllers array inside ${moduleFile}`);
242
- process.exit(1);
364
+ fail(`Could not find a controllers array inside ${moduleFile}`);
243
365
  }
244
366
  const existingControllers = controllersMatch[1]
245
367
  .split(",")
@@ -261,6 +383,19 @@ function buildRelativeImportPath(fromFile, toFile) {
261
383
  }
262
384
  return `./${relativePath}`;
263
385
  }
386
+ function asRecord(value) {
387
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
388
+ return undefined;
389
+ }
390
+ return value;
391
+ }
392
+ function hasLocalInstalledPackage(packageName) {
393
+ return fs_1.default.existsSync(path_1.default.resolve(process.cwd(), "node_modules", packageName));
394
+ }
395
+ function fail(message) {
396
+ console.error(message);
397
+ process.exit(1);
398
+ }
264
399
  function capitalize(value) {
265
400
  return value.charAt(0).toUpperCase() + value.slice(1);
266
401
  }
@@ -283,6 +418,7 @@ function printUsage() {
283
418
  Usage:
284
419
  fastnode create <project-name>
285
420
  fastnode serve <project-name>
421
+ fastnode build
286
422
  fastnode generate controller <controller-name> [module-name]
287
423
  `);
288
424
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fastnode-cli",
3
- "version": "0.6.1",
3
+ "version": "0.6.2",
4
4
  "description": "The official CLI for scaffolding and serving FastNode applications.",
5
5
  "bin": {
6
6
  "fastnode": "dist/index.js"