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.
- package/dist/index.js +148 -12
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|