@triophore/falcon-cli 1.0.2 → 1.0.4
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/LICENSE +21 -0
- package/auth/basic.js +1 -3
- package/auth/cookie.js +2 -6
- package/auth/jwt.js +2 -5
- package/auth/openid.js +3 -2
- package/index.js +15 -15
- package/lib/ProjectGenerator.js +112 -10
- package/lib/RouteGenerator.js +33 -66
- package/lib/ServiceGenerator.js +33 -47
- package/lib/utils.js +5 -3
- package/package.json +1 -1
- package/readme.md +62 -2
- package/templates/example-route.js +11 -11
- package/templates/example-service.js +5 -5
- package/templates/example-worker.js +8 -8
- package/templates/templatev1.json +3 -7
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Triophore Technologies
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/auth/basic.js
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
module.exports.validate = async function (request, username, password, CONTXET) {
|
|
2
|
-
|
|
3
|
-
// Example: Check against database or environment variables
|
|
4
|
-
if (username === process.env.ADMIN_USER && password === process.env.ADMIN_PASSWORD) {
|
|
2
|
+
if (username === 'admin' && password === 'secret') {
|
|
5
3
|
return { isValid: true, credentials: { id: 1, name: 'Admin', roles: ['admin'], permissions: { admin: ['*'] } } };
|
|
6
4
|
}
|
|
7
5
|
return { isValid: false };
|
package/auth/cookie.js
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
module.exports.validate = async function (request, session, CONTXET) {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
/*
|
|
5
|
-
if (session.valid) {
|
|
6
|
-
return { isValid: true, credentials: { id: session.userId, ... } };
|
|
2
|
+
if (username === 'admin' && password === 'secret') {
|
|
3
|
+
return { isValid: true, credentials: { id: 1, name: 'Admin', roles: ['admin'], permissions: { admin: ['*'] } } };
|
|
7
4
|
}
|
|
8
|
-
*/
|
|
9
5
|
return { isValid: false };
|
|
10
6
|
}
|
package/auth/jwt.js
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
module.exports.validate = async function (decoded, CONTXET) {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
// For example, check if a specific environment variable matches a decoded value
|
|
5
|
-
if (decoded.role === process.env.EXPECTED_ADMIN_ROLE && decoded.secret === process.env.EXPECTED_ADMIN_SECRET) {
|
|
6
|
-
return { isValid: true, credentials: { id: decoded.sub, name: 'Admin', roles: ['admin'] } };
|
|
2
|
+
if (username === 'admin' && password === 'secret') {
|
|
3
|
+
return { isValid: true, credentials: { id: 1, name: 'Admin', roles: ['admin'], permissions: { admin: ['*'] } } };
|
|
7
4
|
}
|
|
8
5
|
return { isValid: false };
|
|
9
6
|
}
|
package/auth/openid.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
module.exports.validate = async function (decoded, CONTXET) {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
if (username === 'admin' && password === 'secret') {
|
|
3
|
+
return { isValid: true, credentials: { id: 1, name: 'Admin', roles: ['admin'], permissions: { admin: ['*'] } } };
|
|
4
|
+
}
|
|
4
5
|
return { isValid: false };
|
|
5
6
|
}
|
package/index.js
CHANGED
|
@@ -227,17 +227,17 @@ async function start() {
|
|
|
227
227
|
}
|
|
228
228
|
|
|
229
229
|
mkDir.push("models/mongo") // Fixed: falconjs expects models in models/mongo/
|
|
230
|
-
mkDir.push("routes")
|
|
230
|
+
mkDir.push("routes")
|
|
231
231
|
mkDir.push("services")
|
|
232
232
|
mkDir.push("workers")
|
|
233
233
|
mkDir.push("validators")
|
|
234
234
|
mkDir.push("init")
|
|
235
235
|
mkDir.push("logs")
|
|
236
|
-
|
|
236
|
+
|
|
237
237
|
if (enable_static) {
|
|
238
238
|
mkDir.push("public")
|
|
239
239
|
}
|
|
240
|
-
|
|
240
|
+
|
|
241
241
|
if (enable_templates) {
|
|
242
242
|
mkDir.push("templates")
|
|
243
243
|
}
|
|
@@ -332,27 +332,27 @@ async function start() {
|
|
|
332
332
|
|
|
333
333
|
// Create example files
|
|
334
334
|
await fs.promises.writeFile(
|
|
335
|
-
path.join(currentDir, "routes", "example.js"),
|
|
335
|
+
path.join(currentDir, "routes", "example.js"),
|
|
336
336
|
await fs.promises.readFile(path.join(__dirname, "templates", "example-route.js"))
|
|
337
337
|
);
|
|
338
|
-
|
|
338
|
+
|
|
339
339
|
await fs.promises.writeFile(
|
|
340
|
-
path.join(currentDir, "validators", "ExamplePayload.js"),
|
|
340
|
+
path.join(currentDir, "validators", "ExamplePayload.js"),
|
|
341
341
|
await fs.promises.readFile(path.join(__dirname, "templates", "example-validator.js"))
|
|
342
342
|
);
|
|
343
|
-
|
|
343
|
+
|
|
344
344
|
await fs.promises.writeFile(
|
|
345
|
-
path.join(currentDir, "services", "example.js"),
|
|
345
|
+
path.join(currentDir, "services", "example.js"),
|
|
346
346
|
await fs.promises.readFile(path.join(__dirname, "templates", "example-service.js"))
|
|
347
347
|
);
|
|
348
|
-
|
|
348
|
+
|
|
349
349
|
await fs.promises.writeFile(
|
|
350
|
-
path.join(currentDir, "workers", "example.js"),
|
|
350
|
+
path.join(currentDir, "workers", "example.js"),
|
|
351
351
|
await fs.promises.readFile(path.join(__dirname, "templates", "example-worker.js"))
|
|
352
352
|
);
|
|
353
|
-
|
|
353
|
+
|
|
354
354
|
await fs.promises.writeFile(
|
|
355
|
-
path.join(currentDir, "init", "post.js"),
|
|
355
|
+
path.join(currentDir, "init", "post.js"),
|
|
356
356
|
await fs.promises.readFile(path.join(__dirname, "templates", "post-init.js"))
|
|
357
357
|
);
|
|
358
358
|
|
|
@@ -408,8 +408,8 @@ MODE=DEV
|
|
|
408
408
|
2. npm install
|
|
409
409
|
3. npm run dev
|
|
410
410
|
|
|
411
|
-
📚 Documentation will be available at: http://localhost:
|
|
412
|
-
❤️ Health check at: http://localhost:
|
|
411
|
+
📚 Documentation will be available at: http://localhost:3000/documentation
|
|
412
|
+
❤️ Health check at: http://localhost:3000/health
|
|
413
413
|
`);
|
|
414
414
|
|
|
415
415
|
|
|
@@ -486,7 +486,7 @@ async function getProjectInfo() {
|
|
|
486
486
|
async function create_mongoose_model() {
|
|
487
487
|
mongoose_schema_builder(path.join(currentDir, "models", "mongo")) // Correct path for falconjs
|
|
488
488
|
}
|
|
489
|
-
// await fs.writeFileSync(model_path, JSON.stringify(res,null,2));
|
|
489
|
+
// await fs.writeFileSync(model_path, JSON.stringify(res,null,2));
|
|
490
490
|
|
|
491
491
|
|
|
492
492
|
|
package/lib/ProjectGenerator.js
CHANGED
|
@@ -4,6 +4,8 @@ const path = require("path");
|
|
|
4
4
|
const { spawn } = require('child_process');
|
|
5
5
|
|
|
6
6
|
const { showSuccess, runNpmInstall, createDirectory } = require('./utils');
|
|
7
|
+
const ModelGenerator = require('./ModelGenerator');
|
|
8
|
+
const RouteGenerator = require('./RouteGenerator');
|
|
7
9
|
|
|
8
10
|
class ProjectGenerator {
|
|
9
11
|
constructor(options = {}) {
|
|
@@ -23,6 +25,66 @@ class ProjectGenerator {
|
|
|
23
25
|
});
|
|
24
26
|
}
|
|
25
27
|
|
|
28
|
+
// Feature prompts
|
|
29
|
+
console.log('\n⚙️ Project Configuration:\n');
|
|
30
|
+
|
|
31
|
+
const useSwagger = await confirm({
|
|
32
|
+
message: 'Enable Swagger/OpenAPI documentation?',
|
|
33
|
+
default: true
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const useAuth = await confirm({
|
|
37
|
+
message: 'Include JWT Authentication setup?',
|
|
38
|
+
default: true
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Collect Models
|
|
42
|
+
const initialModels = []; // Array of { name, content }
|
|
43
|
+
const createModels = await confirm({
|
|
44
|
+
message: 'Would you like to create some initial database models?',
|
|
45
|
+
default: false
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
if (createModels) {
|
|
49
|
+
const modelGenerator = new ModelGenerator({ verbose: this.verbose });
|
|
50
|
+
|
|
51
|
+
while (true) {
|
|
52
|
+
const modelName = await input({
|
|
53
|
+
message: 'Model name (leave empty to stop adding models):'
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
if (!modelName.trim()) break;
|
|
57
|
+
|
|
58
|
+
// Immediately build the model content (fields, etc.)
|
|
59
|
+
console.log(`\n📝 Defining model: ${modelName.trim()}`);
|
|
60
|
+
const content = await modelGenerator.buildModelInteractively(modelName.trim());
|
|
61
|
+
|
|
62
|
+
initialModels.push({
|
|
63
|
+
name: modelName.trim(),
|
|
64
|
+
content: content
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
console.log(`✅ Model definition saved for: ${modelName.trim()}\n`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Collect Routes
|
|
72
|
+
const initialRoutes = [];
|
|
73
|
+
const createRoutes = await confirm({
|
|
74
|
+
message: 'Would you like to create some initial API routes?',
|
|
75
|
+
default: false
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
if (createRoutes) {
|
|
79
|
+
while (true) {
|
|
80
|
+
const routeName = await input({
|
|
81
|
+
message: 'Route name (leave empty to stop adding routes):'
|
|
82
|
+
});
|
|
83
|
+
if (!routeName.trim()) break;
|
|
84
|
+
initialRoutes.push(routeName.trim());
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
26
88
|
const projectPath = path.join(currentDir, projectName);
|
|
27
89
|
|
|
28
90
|
// Check if directory exists
|
|
@@ -43,7 +105,43 @@ class ProjectGenerator {
|
|
|
43
105
|
console.log(`\n🚀 Creating Falcon.js project: ${projectName}\n`);
|
|
44
106
|
|
|
45
107
|
// Create project structure
|
|
46
|
-
await this.createProjectStructure(projectPath, projectName, template
|
|
108
|
+
await this.createProjectStructure(projectPath, projectName, template, {
|
|
109
|
+
swagger: useSwagger,
|
|
110
|
+
auth: useAuth,
|
|
111
|
+
models: initialModels.map(m => m.name),
|
|
112
|
+
routes: initialRoutes
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// Generate Models and Routes
|
|
116
|
+
if (initialModels.length > 0 || initialRoutes.length > 0) {
|
|
117
|
+
const originalCwd = process.cwd();
|
|
118
|
+
try {
|
|
119
|
+
process.chdir(projectPath);
|
|
120
|
+
|
|
121
|
+
if (initialModels.length > 0) {
|
|
122
|
+
console.log('\n🏗️ Generatings initial models...');
|
|
123
|
+
const modelsDir = path.join(projectPath, 'models', 'mongo');
|
|
124
|
+
|
|
125
|
+
for (const model of initialModels) {
|
|
126
|
+
const modelPath = path.join(modelsDir, `${model.name.toLowerCase()}.js`);
|
|
127
|
+
await fs.writeFile(modelPath, model.content);
|
|
128
|
+
console.log(`✅ Model file created: ${modelPath}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (initialRoutes.length > 0) {
|
|
133
|
+
console.log('\n🛣️ Generating initial routes...');
|
|
134
|
+
const routeGenerator = new RouteGenerator({ verbose: this.verbose });
|
|
135
|
+
for (const route of initialRoutes) {
|
|
136
|
+
await routeGenerator.generate(route);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
} catch (err) {
|
|
140
|
+
console.error('⚠️ Failed to generate initial resources:', err.message);
|
|
141
|
+
} finally {
|
|
142
|
+
process.chdir(originalCwd);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
47
145
|
|
|
48
146
|
// Install dependencies
|
|
49
147
|
if (!this.skipInstall) {
|
|
@@ -54,7 +152,7 @@ class ProjectGenerator {
|
|
|
54
152
|
showSuccess(projectName, this.skipInstall);
|
|
55
153
|
}
|
|
56
154
|
|
|
57
|
-
async createProjectStructure(projectPath, projectName, template) {
|
|
155
|
+
async createProjectStructure(projectPath, projectName, template, options = {}) {
|
|
58
156
|
// Create directories
|
|
59
157
|
const directories = [
|
|
60
158
|
'models/mongo',
|
|
@@ -73,7 +171,7 @@ class ProjectGenerator {
|
|
|
73
171
|
|
|
74
172
|
// Create files
|
|
75
173
|
await this.createPackageJson(projectPath, projectName);
|
|
76
|
-
await this.createSettings(projectPath, projectName);
|
|
174
|
+
await this.createSettings(projectPath, projectName, options);
|
|
77
175
|
await this.createIndexJs(projectPath);
|
|
78
176
|
await this.createEnvFile(projectPath, projectName);
|
|
79
177
|
await this.createExampleFiles(projectPath);
|
|
@@ -95,13 +193,15 @@ class ProjectGenerator {
|
|
|
95
193
|
license: "ISC",
|
|
96
194
|
type: "commonjs",
|
|
97
195
|
dependencies: {
|
|
98
|
-
"@triophore/falconjs": "^1.0.
|
|
196
|
+
"@triophore/falconjs": "^1.0.1",
|
|
99
197
|
"@hapi/boom": "^10.0.1",
|
|
100
198
|
"@hapi/inert": "^7.1.0",
|
|
101
199
|
"@hapi/vision": "^7.0.3",
|
|
102
200
|
"joi": "^17.13.3",
|
|
103
201
|
"mongoose": "^8.3.1",
|
|
104
202
|
"redis": "^4.7.0",
|
|
203
|
+
"mqtt": "^5.13.3",
|
|
204
|
+
"log4js": "^6.9.1",
|
|
105
205
|
"rate-limiter-flexible": "^2.4.2",
|
|
106
206
|
"hapi-alive": "^2.0.4",
|
|
107
207
|
"hapi-swagger": "^17.2.1",
|
|
@@ -118,7 +218,9 @@ class ProjectGenerator {
|
|
|
118
218
|
);
|
|
119
219
|
}
|
|
120
220
|
|
|
121
|
-
async createSettings(projectPath, projectName) {
|
|
221
|
+
async createSettings(projectPath, projectName, options = {}) {
|
|
222
|
+
const { swagger = true, auth = true, models = [], routes = [] } = options;
|
|
223
|
+
|
|
122
224
|
const settings = `module.exports.settings = {
|
|
123
225
|
name: "${projectName}",
|
|
124
226
|
http: {
|
|
@@ -154,23 +256,23 @@ class ProjectGenerator {
|
|
|
154
256
|
}
|
|
155
257
|
},
|
|
156
258
|
swagger: {
|
|
157
|
-
enabled:
|
|
259
|
+
enabled: ${swagger},
|
|
158
260
|
path: "/documentation"
|
|
159
261
|
},
|
|
160
262
|
auth: {
|
|
161
263
|
jwt: {
|
|
162
|
-
|
|
264
|
+
secret: process.env.JWT_SECRET || "your-secret-key-here",
|
|
163
265
|
validate: async (decoded, request) => {
|
|
164
266
|
// Your JWT validation logic
|
|
165
267
|
return { id: decoded.id, roles: decoded.roles };
|
|
166
268
|
}
|
|
167
269
|
},
|
|
168
|
-
default: "jwt"
|
|
270
|
+
default: "${auth ? 'jwt' : 'none'}"
|
|
169
271
|
},
|
|
170
272
|
services: [],
|
|
171
273
|
workers: [],
|
|
172
|
-
models:
|
|
173
|
-
routes:
|
|
274
|
+
models: ${JSON.stringify(models.map(m => m.toLowerCase()))},
|
|
275
|
+
routes: ${JSON.stringify(routes.map(r => r.toLowerCase()))},
|
|
174
276
|
crud: {
|
|
175
277
|
exclude: []
|
|
176
278
|
},
|
package/lib/RouteGenerator.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
const
|
|
2
|
-
const fs = require(
|
|
3
|
-
const
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const fs = require('fs').promises;
|
|
3
|
+
const { input } = require('@inquirer/prompts');
|
|
4
4
|
|
|
5
5
|
class RouteGenerator {
|
|
6
6
|
constructor(options = {}) {
|
|
7
7
|
this.verbose = options.verbose || false;
|
|
8
|
-
this.type = options.type || 'route'; // 'route' or 'validator'
|
|
9
8
|
}
|
|
10
9
|
|
|
11
10
|
async generate(name) {
|
|
12
11
|
const currentDir = process.cwd();
|
|
12
|
+
const routesDir = path.join(currentDir, 'routes');
|
|
13
13
|
|
|
14
14
|
// Check if we're in a Falcon project
|
|
15
15
|
try {
|
|
@@ -20,81 +20,48 @@ class RouteGenerator {
|
|
|
20
20
|
|
|
21
21
|
if (!name) {
|
|
22
22
|
name = await input({
|
|
23
|
-
message:
|
|
24
|
-
validate: v => v.trim() ? true : '
|
|
23
|
+
message: 'Route name (e.g., users):',
|
|
24
|
+
validate: v => v.trim() ? true : 'Route name is required',
|
|
25
25
|
});
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
await this.generateValidator(currentDir, name);
|
|
30
|
-
} else {
|
|
31
|
-
await this.generateRoute(currentDir, name);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
async generateRoute(projectDir, name) {
|
|
36
|
-
const routesDir = path.join(projectDir, 'routes');
|
|
37
|
-
await fs.mkdir(routesDir, { recursive: true });
|
|
38
|
-
|
|
39
|
-
// Determine path and filename
|
|
40
|
-
// If name contains /, treat as path
|
|
41
|
-
const parts = name.split('/');
|
|
42
|
-
const fileName = parts.pop();
|
|
43
|
-
const subDir = parts.join('/');
|
|
28
|
+
console.log(`\n🔧 Generating route: ${name}\n`);
|
|
44
29
|
|
|
45
|
-
|
|
46
|
-
await fs.mkdir(targetDir, { recursive: true });
|
|
47
|
-
|
|
48
|
-
const filePath = path.join(targetDir, `${fileName}.js`);
|
|
49
|
-
|
|
50
|
-
// Check if file exists
|
|
30
|
+
// Create routes directory if it doesn't exist
|
|
51
31
|
try {
|
|
52
|
-
await fs.
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
default: false
|
|
56
|
-
});
|
|
57
|
-
if (!overwrite) return;
|
|
58
|
-
} catch (e) {
|
|
59
|
-
// File doesn't exist, proceed
|
|
32
|
+
await fs.mkdir(routesDir, { recursive: true });
|
|
33
|
+
} catch (error) {
|
|
34
|
+
// Directory already exists
|
|
60
35
|
}
|
|
61
36
|
|
|
62
|
-
|
|
63
|
-
const
|
|
64
|
-
let content = await fs.readFile(templatePath, 'utf8');
|
|
37
|
+
const routeContent = this.getRouteTemplate(name);
|
|
38
|
+
const routePath = path.join(routesDir, `${name.toLowerCase()}.js`);
|
|
65
39
|
|
|
66
|
-
|
|
67
|
-
// For now, we'll just write the template
|
|
40
|
+
await fs.writeFile(routePath, routeContent);
|
|
68
41
|
|
|
69
|
-
|
|
70
|
-
console.log(`✅ Route created: ${filePath}`);
|
|
42
|
+
console.log(`✅ Route created: ${routePath}`);
|
|
71
43
|
}
|
|
72
44
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
45
|
+
getRouteTemplate(name) {
|
|
46
|
+
return `module.exports.route = async function (context) {
|
|
47
|
+
context.server.route({
|
|
48
|
+
method: 'GET',
|
|
49
|
+
path: '/${name.toLowerCase()}',
|
|
50
|
+
options: {
|
|
51
|
+
tags: ['api', 'example'],
|
|
52
|
+
description: 'Example GET endpoint',
|
|
53
|
+
notes: 'Returns example data'
|
|
54
|
+
},
|
|
55
|
+
handler: async (request, h) => {
|
|
56
|
+
return {
|
|
57
|
+
success: true,
|
|
58
|
+
message: 'Hello from Falcon.js!',
|
|
59
|
+
timestamp: new Date().toISOString()
|
|
60
|
+
};
|
|
61
|
+
}
|
|
85
62
|
});
|
|
86
|
-
if (!overwrite) return;
|
|
87
|
-
} catch (e) {
|
|
88
|
-
// File doesn't exist, proceed
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const templatePath = path.join(__dirname, '..', 'templates', 'example-validator.js');
|
|
92
|
-
let content = await fs.readFile(templatePath, 'utf8');
|
|
93
|
-
|
|
94
|
-
// Replace Joi with Joi (no change needed usually, but good for future)
|
|
95
63
|
|
|
96
|
-
|
|
97
|
-
console.log(`✅ Validator created: ${filePath}`);
|
|
64
|
+
};`;
|
|
98
65
|
}
|
|
99
66
|
}
|
|
100
67
|
|
package/lib/ServiceGenerator.js
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
|
-
const
|
|
2
|
-
const fs = require(
|
|
3
|
-
const
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const fs = require('fs').promises;
|
|
3
|
+
const { input } = require('@inquirer/prompts');
|
|
4
4
|
|
|
5
5
|
class ServiceGenerator {
|
|
6
6
|
constructor(options = {}) {
|
|
7
7
|
this.verbose = options.verbose || false;
|
|
8
|
-
this.type = options.type || 'service'; // 'service' or 'worker'
|
|
8
|
+
this.type = options.type || 'service'; // can be 'service' or 'worker'
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
async generate(name) {
|
|
12
12
|
const currentDir = process.cwd();
|
|
13
|
+
const baseDir = this.type === 'worker' ? 'workers' : 'services';
|
|
14
|
+
const targetDir = path.join(currentDir, baseDir);
|
|
13
15
|
|
|
14
16
|
// Check if we're in a Falcon project
|
|
15
17
|
try {
|
|
@@ -20,65 +22,49 @@ class ServiceGenerator {
|
|
|
20
22
|
|
|
21
23
|
if (!name) {
|
|
22
24
|
name = await input({
|
|
23
|
-
message: `${this.type
|
|
25
|
+
message: `${this.type.charAt(0).toUpperCase() + this.type.slice(1)} name:`,
|
|
24
26
|
validate: v => v.trim() ? true : 'Name is required',
|
|
25
27
|
});
|
|
26
28
|
}
|
|
27
29
|
|
|
28
|
-
|
|
29
|
-
const targetDir = path.join(currentDir, dirName);
|
|
30
|
-
await fs.mkdir(targetDir, { recursive: true });
|
|
30
|
+
console.log(`\n🔧 Generating ${this.type}: ${name}\n`);
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
// Check if file exists
|
|
32
|
+
// Create directory if it doesn't exist
|
|
35
33
|
try {
|
|
36
|
-
await fs.
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
default: false
|
|
40
|
-
});
|
|
41
|
-
if (!overwrite) return;
|
|
42
|
-
} catch (e) {
|
|
43
|
-
// File doesn't exist, proceed
|
|
34
|
+
await fs.mkdir(targetDir, { recursive: true });
|
|
35
|
+
} catch (error) {
|
|
36
|
+
// Directory already exists
|
|
44
37
|
}
|
|
45
38
|
|
|
46
|
-
|
|
47
|
-
const
|
|
48
|
-
const
|
|
49
|
-
let content = await fs.readFile(templatePath, 'utf8');
|
|
39
|
+
const content = this.getTemplate(name);
|
|
40
|
+
const fileName = `${name.toLowerCase()}.js`;
|
|
41
|
+
const filePath = path.join(targetDir, fileName);
|
|
50
42
|
|
|
51
43
|
await fs.writeFile(filePath, content);
|
|
52
|
-
console.log(`✅ ${this.type === 'worker' ? 'Worker' : 'Service'} created: ${filePath}`);
|
|
53
44
|
|
|
54
|
-
|
|
55
|
-
await this.updateSettings(currentDir, name, dirName);
|
|
45
|
+
console.log(`✅ ${this.type.charAt(0).toUpperCase() + this.type.slice(1)} created: ${filePath}`);
|
|
56
46
|
}
|
|
57
47
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
48
|
+
getTemplate(name) {
|
|
49
|
+
if (this.type === 'worker') {
|
|
50
|
+
return `module.exports = async function (job) {
|
|
51
|
+
// Process job here
|
|
52
|
+
console.log('Processing job:', job.id);
|
|
53
|
+
return { done: true };
|
|
54
|
+
};`;
|
|
55
|
+
}
|
|
61
56
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
57
|
+
return `class ${name}Service {
|
|
58
|
+
constructor(app) {
|
|
59
|
+
this.app = app;
|
|
60
|
+
}
|
|
65
61
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const newListContent = listContent.trim()
|
|
71
|
-
? `${listContent.trim()}, "${name}"`
|
|
72
|
-
: `"${name}"`;
|
|
62
|
+
async doSomething() {
|
|
63
|
+
return 'done';
|
|
64
|
+
}
|
|
65
|
+
}
|
|
73
66
|
|
|
74
|
-
|
|
75
|
-
regex,
|
|
76
|
-
`${listName}: [${newListContent}]`
|
|
77
|
-
);
|
|
78
|
-
await fs.writeFile(settingsPath, settingsContent);
|
|
79
|
-
console.log(`✅ Added "${name}" to settings.js ${listName} array`);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
67
|
+
module.exports = ${name}Service;`;
|
|
82
68
|
}
|
|
83
69
|
}
|
|
84
70
|
|
package/lib/utils.js
CHANGED
|
@@ -11,6 +11,8 @@ const logo = `
|
|
|
11
11
|
╚═╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═══╝
|
|
12
12
|
|
|
13
13
|
🚀 Falcon.js CLI - Build powerful Node.js APIs
|
|
14
|
+
Powered by Triophore Technologies
|
|
15
|
+
Visit us at https://triophore.com
|
|
14
16
|
`;
|
|
15
17
|
|
|
16
18
|
function showLogo() {
|
|
@@ -79,15 +81,15 @@ function validateProjectName(name) {
|
|
|
79
81
|
if (!name || typeof name !== 'string') {
|
|
80
82
|
return 'Project name is required';
|
|
81
83
|
}
|
|
82
|
-
|
|
84
|
+
|
|
83
85
|
if (!/^[a-z0-9-_]+$/i.test(name)) {
|
|
84
86
|
return 'Project name can only contain letters, numbers, hyphens, and underscores';
|
|
85
87
|
}
|
|
86
|
-
|
|
88
|
+
|
|
87
89
|
if (name.length < 2) {
|
|
88
90
|
return 'Project name must be at least 2 characters long';
|
|
89
91
|
}
|
|
90
|
-
|
|
92
|
+
|
|
91
93
|
return true;
|
|
92
94
|
}
|
|
93
95
|
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -1,2 +1,62 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
# Falcon CLI
|
|
2
|
+
|
|
3
|
+
Command-line interface for creating and managing Falcon.js applications.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g falcon-cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
### Create a new project
|
|
14
|
+
```bash
|
|
15
|
+
falcon-cli create my-app
|
|
16
|
+
falcon-cli create my-app --template api
|
|
17
|
+
falcon-cli create my-app --skip-install
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### Generate components
|
|
21
|
+
```bash
|
|
22
|
+
# Generate a model
|
|
23
|
+
falcon-cli generate model User
|
|
24
|
+
falcon-cli g model Product --crud
|
|
25
|
+
|
|
26
|
+
# Generate a route
|
|
27
|
+
falcon-cli generate route users
|
|
28
|
+
|
|
29
|
+
# Generate a service
|
|
30
|
+
falcon-cli generate service email
|
|
31
|
+
|
|
32
|
+
# Generate a worker
|
|
33
|
+
falcon-cli generate worker image-processor
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Commands
|
|
37
|
+
|
|
38
|
+
- `create [name]` - Create a new Falcon.js project
|
|
39
|
+
- `generate <type> [name]` - Generate project components
|
|
40
|
+
- Types: `model`, `route`, `service`, `worker`, `validator`
|
|
41
|
+
|
|
42
|
+
## Options
|
|
43
|
+
|
|
44
|
+
- `--verbose, -V` - Run with verbose logging
|
|
45
|
+
- `--help, -h` - Show help
|
|
46
|
+
- `--version, -v` - Show version
|
|
47
|
+
|
|
48
|
+
## Project Structure
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
my-falcon-app/
|
|
52
|
+
├── index.js # Application entry point
|
|
53
|
+
├── settings.js # Falcon configuration
|
|
54
|
+
├── .env # Environment variables
|
|
55
|
+
├── models/mongo/ # Mongoose models
|
|
56
|
+
├── routes/ # API routes
|
|
57
|
+
├── services/ # Background services
|
|
58
|
+
├── workers/ # Job processors
|
|
59
|
+
├── validators/ # Joi validation schemas
|
|
60
|
+
├── init/ # Initialization scripts
|
|
61
|
+
└── logs/ # Application logs
|
|
62
|
+
```
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
* This file demonstrates the route structure expected by Falcon.js
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
module.exports.route = async function (
|
|
7
|
-
|
|
6
|
+
module.exports.route = async function (context) {
|
|
7
|
+
|
|
8
8
|
// Example GET route
|
|
9
|
-
server.route({
|
|
9
|
+
context.server.route({
|
|
10
10
|
method: 'GET',
|
|
11
11
|
path: '/api/example',
|
|
12
12
|
options: {
|
|
@@ -24,7 +24,7 @@ module.exports.route = async function (server, context) {
|
|
|
24
24
|
});
|
|
25
25
|
|
|
26
26
|
// Example POST route with validation
|
|
27
|
-
server.route({
|
|
27
|
+
context.server.route({
|
|
28
28
|
method: 'POST',
|
|
29
29
|
path: '/api/example',
|
|
30
30
|
options: {
|
|
@@ -36,7 +36,7 @@ module.exports.route = async function (server, context) {
|
|
|
36
36
|
},
|
|
37
37
|
handler: async (request, h) => {
|
|
38
38
|
const { name, email } = request.payload;
|
|
39
|
-
|
|
39
|
+
|
|
40
40
|
// Example: Save to database if models are available
|
|
41
41
|
if (context.models.example) {
|
|
42
42
|
const result = await context.models.example.create({
|
|
@@ -44,14 +44,14 @@ module.exports.route = async function (server, context) {
|
|
|
44
44
|
email,
|
|
45
45
|
createdAt: new Date()
|
|
46
46
|
});
|
|
47
|
-
|
|
47
|
+
|
|
48
48
|
return {
|
|
49
49
|
success: true,
|
|
50
50
|
data: result,
|
|
51
51
|
message: 'Data saved successfully'
|
|
52
52
|
};
|
|
53
53
|
}
|
|
54
|
-
|
|
54
|
+
|
|
55
55
|
return {
|
|
56
56
|
success: true,
|
|
57
57
|
message: 'Data received',
|
|
@@ -61,7 +61,7 @@ module.exports.route = async function (server, context) {
|
|
|
61
61
|
});
|
|
62
62
|
|
|
63
63
|
// Example route that sends job to worker
|
|
64
|
-
server.route({
|
|
64
|
+
context.server.route({
|
|
65
65
|
method: 'POST',
|
|
66
66
|
path: '/api/example/job',
|
|
67
67
|
options: {
|
|
@@ -70,7 +70,7 @@ module.exports.route = async function (server, context) {
|
|
|
70
70
|
},
|
|
71
71
|
handler: async (request, h) => {
|
|
72
72
|
const jobData = request.payload;
|
|
73
|
-
|
|
73
|
+
|
|
74
74
|
// Send job to worker via MQTT (if available)
|
|
75
75
|
if (context.mqtt_client) {
|
|
76
76
|
context.mqtt_client.publish('worker_example_job', JSON.stringify({
|
|
@@ -78,13 +78,13 @@ module.exports.route = async function (server, context) {
|
|
|
78
78
|
data: jobData,
|
|
79
79
|
timestamp: new Date().toISOString()
|
|
80
80
|
}));
|
|
81
|
-
|
|
81
|
+
|
|
82
82
|
return {
|
|
83
83
|
success: true,
|
|
84
84
|
message: 'Job queued for processing'
|
|
85
85
|
};
|
|
86
86
|
}
|
|
87
|
-
|
|
87
|
+
|
|
88
88
|
return {
|
|
89
89
|
success: false,
|
|
90
90
|
message: 'Job queue not available'
|
|
@@ -4,7 +4,7 @@ const { FalconBaseService } = require('@triophore/falconjs');
|
|
|
4
4
|
* Example service - rename and customize as needed
|
|
5
5
|
*/
|
|
6
6
|
class ExampleService extends FalconBaseService {
|
|
7
|
-
|
|
7
|
+
|
|
8
8
|
constructor() {
|
|
9
9
|
super('example'); // Service ID for MQTT topics
|
|
10
10
|
}
|
|
@@ -15,7 +15,7 @@ class ExampleService extends FalconBaseService {
|
|
|
15
15
|
async onMessage(topic, msg) {
|
|
16
16
|
try {
|
|
17
17
|
const message = JSON.parse(msg.toString());
|
|
18
|
-
|
|
18
|
+
|
|
19
19
|
switch (topic) {
|
|
20
20
|
case 'service_example':
|
|
21
21
|
await this.handleRequest(message);
|
|
@@ -33,9 +33,9 @@ class ExampleService extends FalconBaseService {
|
|
|
33
33
|
*/
|
|
34
34
|
async handleRequest(request) {
|
|
35
35
|
console.log('Processing request:', request);
|
|
36
|
-
|
|
36
|
+
|
|
37
37
|
// Your service logic here
|
|
38
|
-
|
|
38
|
+
|
|
39
39
|
// Send response back via MQTT
|
|
40
40
|
this.publish({
|
|
41
41
|
status: 'completed',
|
|
@@ -49,7 +49,7 @@ class ExampleService extends FalconBaseService {
|
|
|
49
49
|
*/
|
|
50
50
|
async run() {
|
|
51
51
|
console.log('Example service is running...');
|
|
52
|
-
|
|
52
|
+
|
|
53
53
|
// Your initialization logic here
|
|
54
54
|
}
|
|
55
55
|
}
|
|
@@ -4,7 +4,7 @@ const { FalconBaseWorker } = require('@triophore/falconjs');
|
|
|
4
4
|
* Example worker - rename and customize as needed
|
|
5
5
|
*/
|
|
6
6
|
class ExampleWorker extends FalconBaseWorker {
|
|
7
|
-
|
|
7
|
+
|
|
8
8
|
constructor() {
|
|
9
9
|
super('example'); // Worker ID for MQTT topics
|
|
10
10
|
}
|
|
@@ -15,7 +15,7 @@ class ExampleWorker extends FalconBaseWorker {
|
|
|
15
15
|
async onMessage(topic, msg) {
|
|
16
16
|
try {
|
|
17
17
|
const message = JSON.parse(msg.toString());
|
|
18
|
-
|
|
18
|
+
|
|
19
19
|
switch (topic) {
|
|
20
20
|
case 'worker_example_job':
|
|
21
21
|
await this.processJob(message);
|
|
@@ -33,12 +33,12 @@ class ExampleWorker extends FalconBaseWorker {
|
|
|
33
33
|
*/
|
|
34
34
|
async processJob(job) {
|
|
35
35
|
const { jobId, data } = job;
|
|
36
|
-
|
|
36
|
+
|
|
37
37
|
console.log(`Processing job ${jobId}:`, data);
|
|
38
|
-
|
|
38
|
+
|
|
39
39
|
// Simulate work
|
|
40
40
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
41
|
-
|
|
41
|
+
|
|
42
42
|
// Update job status in database if available
|
|
43
43
|
if (this.models.job) {
|
|
44
44
|
await this.models.job.findByIdAndUpdate(jobId, {
|
|
@@ -47,7 +47,7 @@ class ExampleWorker extends FalconBaseWorker {
|
|
|
47
47
|
result: { processed: true }
|
|
48
48
|
});
|
|
49
49
|
}
|
|
50
|
-
|
|
50
|
+
|
|
51
51
|
// Send completion notification
|
|
52
52
|
await this.publish('worker_example_complete', {
|
|
53
53
|
jobId: jobId,
|
|
@@ -62,13 +62,13 @@ class ExampleWorker extends FalconBaseWorker {
|
|
|
62
62
|
async run() {
|
|
63
63
|
const args = this.parseArgs();
|
|
64
64
|
console.log('Example worker started with args:', args);
|
|
65
|
-
|
|
65
|
+
|
|
66
66
|
// If specific job passed as argument, process it immediately
|
|
67
67
|
if (args.jobId) {
|
|
68
68
|
await this.processJob(args);
|
|
69
69
|
process.exit(0); // Exit after processing single job
|
|
70
70
|
}
|
|
71
|
-
|
|
71
|
+
|
|
72
72
|
// Otherwise, keep running and wait for MQTT jobs
|
|
73
73
|
console.log('Waiting for jobs...');
|
|
74
74
|
}
|
|
@@ -8,16 +8,12 @@
|
|
|
8
8
|
"dev": "nodemon index.js",
|
|
9
9
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
10
10
|
},
|
|
11
|
-
"keywords": [
|
|
12
|
-
"falcon",
|
|
13
|
-
"nodejs",
|
|
14
|
-
"api"
|
|
15
|
-
],
|
|
11
|
+
"keywords": ["falcon", "nodejs", "api"],
|
|
16
12
|
"author": "",
|
|
17
13
|
"license": "ISC",
|
|
18
14
|
"type": "commonjs",
|
|
19
15
|
"dependencies": {
|
|
20
|
-
"falconjs": "
|
|
16
|
+
"@triophore/falconjs": "^1.0.0",
|
|
21
17
|
"@hapi/boom": "^10.0.1",
|
|
22
18
|
"@hapi/inert": "^7.1.0",
|
|
23
19
|
"@hapi/vision": "^7.0.3",
|
|
@@ -35,4 +31,4 @@
|
|
|
35
31
|
"devDependencies": {
|
|
36
32
|
"nodemon": "^3.1.10"
|
|
37
33
|
}
|
|
38
|
-
}
|
|
34
|
+
}
|