@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 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
- // TODO: Implement your own logic here
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
- // TODO: Implement your own logic here
3
- // Example: Validate session against database or Redis
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
- // TODO: Implement your own logic here
3
- // Example: Check against database or environment variables
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
- // TODO: Implement your own logic here
3
- // Example: Validate OpenID token claims
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:[PORT]/documentation
412
- ❤️ Health check at: http://localhost:[PORT]/health
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
 
@@ -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.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: true,
259
+ enabled: ${swagger},
158
260
  path: "/documentation"
159
261
  },
160
262
  auth: {
161
263
  jwt: {
162
- key: process.env.JWT_SECRET || "your-secret-key-here",
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
  },
@@ -1,15 +1,15 @@
1
- const { input, select, confirm } = require("@inquirer/prompts");
2
- const fs = require("fs").promises;
3
- const path = require("path");
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: `${this.type === 'validator' ? 'Validator' : 'Route'} name (e.g., User):`,
24
- validate: v => v.trim() ? true : 'Name is required',
23
+ message: 'Route name (e.g., users):',
24
+ validate: v => v.trim() ? true : 'Route name is required',
25
25
  });
26
26
  }
27
27
 
28
- if (this.type === 'validator') {
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
- const targetDir = path.join(routesDir, subDir);
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.access(filePath);
53
- const overwrite = await confirm({
54
- message: `Route file ${filePath} already exists. Overwrite?`,
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
- // Helper to get template content
63
- const templatePath = path.join(__dirname, '..', 'templates', 'example-route.js');
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
- // Simple replacements if needed, or just use the example as a starter
67
- // For now, we'll just write the template
40
+ await fs.writeFile(routePath, routeContent);
68
41
 
69
- await fs.writeFile(filePath, content);
70
- console.log(`✅ Route created: ${filePath}`);
42
+ console.log(`✅ Route created: ${routePath}`);
71
43
  }
72
44
 
73
- async generateValidator(projectDir, name) {
74
- const validatorsDir = path.join(projectDir, 'validators');
75
- await fs.mkdir(validatorsDir, { recursive: true });
76
-
77
- const filePath = path.join(validatorsDir, `${name}.js`);
78
-
79
- // Check if file exists
80
- try {
81
- await fs.access(filePath);
82
- const overwrite = await confirm({
83
- message: `Validator file ${filePath} already exists. Overwrite?`,
84
- default: false
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
- await fs.writeFile(filePath, content);
97
- console.log(`✅ Validator created: ${filePath}`);
64
+ };`;
98
65
  }
99
66
  }
100
67
 
@@ -1,15 +1,17 @@
1
- const { input, confirm } = require("@inquirer/prompts");
2
- const fs = require("fs").promises;
3
- const path = require("path");
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 === 'worker' ? 'Worker' : 'Service'} name (e.g., Email):`,
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
- const dirName = this.type === 'worker' ? 'workers' : 'services';
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
- const filePath = path.join(targetDir, `${name}.js`);
33
-
34
- // Check if file exists
32
+ // Create directory if it doesn't exist
35
33
  try {
36
- await fs.access(filePath);
37
- const overwrite = await confirm({
38
- message: `${this.type === 'worker' ? 'Worker' : 'Service'} file ${filePath} already exists. Overwrite?`,
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
- // Get template
47
- const templateName = this.type === 'worker' ? 'example-worker.js' : 'example-service.js';
48
- const templatePath = path.join(__dirname, '..', 'templates', templateName);
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
- // Update settings.js to include the service/worker
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
- async updateSettings(projectDir, name, listName) {
59
- const settingsPath = path.join(projectDir, 'settings.js');
60
- let settingsContent = await fs.readFile(settingsPath, 'utf8');
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
- // Regex to find the array: services: [...] or workers: [...]
63
- const regex = new RegExp(`${listName}:\\s*\\[(.*?)\\]`, 's');
64
- const match = settingsContent.match(regex);
57
+ return `class ${name}Service {
58
+ constructor(app) {
59
+ this.app = app;
60
+ }
65
61
 
66
- if (match) {
67
- const listContent = match[1];
68
- // Simple check if name is already there
69
- if (!listContent.includes(`"${name}"`) && !listContent.includes(`'${name}'`)) {
70
- const newListContent = listContent.trim()
71
- ? `${listContent.trim()}, "${name}"`
72
- : `"${name}"`;
62
+ async doSomething() {
63
+ return 'done';
64
+ }
65
+ }
73
66
 
74
- settingsContent = settingsContent.replace(
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@triophore/falcon-cli",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/readme.md CHANGED
@@ -1,2 +1,62 @@
1
- - create project
2
- - create models for both mongo and sequalize
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 (server, context) {
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": "@triophore/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
+ }