@triophore/falcon-cli 1.0.0
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/README.md +62 -0
- package/auth/basic.js +8 -0
- package/auth/cookie.js +10 -0
- package/auth/jwks.js +6 -0
- package/auth/jwt.js +9 -0
- package/auth/openid.js +5 -0
- package/auth/webscoket.js +6 -0
- package/builder/EnvBuilder.js +0 -0
- package/builder/createCjsModule.js +95 -0
- package/builder/editModelInteractive.js +159 -0
- package/builder/interactiveModelBuilder.js +215 -0
- package/builder/interactiveMongobuilder.js +189 -0
- package/builder/interactiveUnifiedBuilder.js +277 -0
- package/builder/joiValidatorBuilder.js +218 -0
- package/builder/mongooseModelBuilder.js +290 -0
- package/builder/mongooseModelBuilder2.js +313 -0
- package/builder/runMigrations.js +106 -0
- package/builder/sequelizeModelBuilder.js +180 -0
- package/cli.js +60 -0
- package/commands/create.js +57 -0
- package/commands/generate.js +74 -0
- package/dev/Uset.schema.json +18 -0
- package/dev/buildSchemaInteractive.js +189 -0
- package/dev/buildSequelizeSchemaInteractive.js +128 -0
- package/dev/createJoiSchemaFromJson.js +137 -0
- package/dev/createModelFromJson.js +280 -0
- package/dev/generateAllFiles.js +45 -0
- package/dev/generateJoiFile.js +95 -0
- package/dev/generateSequelizeFiles.js +167 -0
- package/dev/interactiveJoiBuilder.js +177 -0
- package/dev/ra.js +22 -0
- package/dev/rj.js +18 -0
- package/dev/run.js +16 -0
- package/dev/run_seq.js +18 -0
- package/dev/tracker.js +23 -0
- package/editJsConfig.js +188 -0
- package/index.js +548 -0
- package/lib/ModelGenerator.js +203 -0
- package/lib/ProjectGenerator.js +246 -0
- package/lib/utils.js +100 -0
- package/logo.js +3 -0
- package/package.json +35 -0
- package/readme.md +2 -0
- package/schema.json +42 -0
- package/templates/auth_vals.json +3 -0
- package/templates/config.js +0 -0
- package/templates/example-route.js +94 -0
- package/templates/example-service.js +63 -0
- package/templates/example-validator.js +15 -0
- package/templates/example-worker.js +83 -0
- package/templates/index.txt +41 -0
- package/templates/post-init.js +78 -0
- package/templates/settings.js +192 -0
- package/templates/template1.settings.txt +15 -0
- package/templates/templatev1.json +38 -0
- package/validateJsConfig.js +125 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
const { input, select, confirm, checkbox } = require("@inquirer/prompts");
|
|
2
|
+
const fs = require("fs").promises;
|
|
3
|
+
const path = require("path");
|
|
4
|
+
|
|
5
|
+
class ModelGenerator {
|
|
6
|
+
constructor(options = {}) {
|
|
7
|
+
this.verbose = options.verbose || false;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async generate(modelName, options = {}) {
|
|
11
|
+
const currentDir = process.cwd();
|
|
12
|
+
const modelsDir = path.join(currentDir, 'models', 'mongo');
|
|
13
|
+
|
|
14
|
+
// Check if we're in a Falcon project
|
|
15
|
+
try {
|
|
16
|
+
await fs.access(path.join(currentDir, 'settings.js'));
|
|
17
|
+
} catch (error) {
|
|
18
|
+
throw new Error('Not in a Falcon.js project directory. Run this command from your project root.');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Get model name if not provided
|
|
22
|
+
if (!modelName) {
|
|
23
|
+
modelName = await input({
|
|
24
|
+
message: 'Model name (e.g., User):',
|
|
25
|
+
validate: v => v.trim() ? true : 'Model name is required',
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
console.log(`\nš§ Generating model: ${modelName}\n`);
|
|
30
|
+
|
|
31
|
+
// Create models directory if it doesn't exist
|
|
32
|
+
try {
|
|
33
|
+
await fs.mkdir(modelsDir, { recursive: true });
|
|
34
|
+
} catch (error) {
|
|
35
|
+
// Directory already exists
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Generate model interactively
|
|
39
|
+
const modelContent = await this.buildModelInteractively(modelName);
|
|
40
|
+
|
|
41
|
+
// Write model file
|
|
42
|
+
const modelPath = path.join(modelsDir, `${modelName.toLowerCase()}.js`);
|
|
43
|
+
await fs.writeFile(modelPath, modelContent);
|
|
44
|
+
|
|
45
|
+
console.log(`ā
Model created: ${modelPath}`);
|
|
46
|
+
|
|
47
|
+
// Update settings.js to include the model
|
|
48
|
+
await this.updateSettings(currentDir, modelName.toLowerCase());
|
|
49
|
+
|
|
50
|
+
// Generate CRUD routes if requested
|
|
51
|
+
if (options.crud) {
|
|
52
|
+
await this.generateCrudRoutes(currentDir, modelName);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async buildModelInteractively(modelName) {
|
|
57
|
+
const fields = {};
|
|
58
|
+
const indexes = [];
|
|
59
|
+
|
|
60
|
+
// Add timestamps option
|
|
61
|
+
const useTimestamps = await confirm({
|
|
62
|
+
message: 'Enable timestamps (createdAt, updatedAt)?',
|
|
63
|
+
default: true
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
console.log('\nš Add fields to your model:\n');
|
|
67
|
+
|
|
68
|
+
// Add fields interactively
|
|
69
|
+
while (true) {
|
|
70
|
+
const addField = await confirm({
|
|
71
|
+
message: 'Add a field?',
|
|
72
|
+
default: true
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
if (!addField) break;
|
|
76
|
+
|
|
77
|
+
const fieldName = await input({
|
|
78
|
+
message: 'Field name:',
|
|
79
|
+
validate: v => v && !fields[v] ? true : 'Invalid or duplicate field name',
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const fieldType = await select({
|
|
83
|
+
message: 'Field type:',
|
|
84
|
+
choices: [
|
|
85
|
+
{ name: 'String', value: 'String' },
|
|
86
|
+
{ name: 'Number', value: 'Number' },
|
|
87
|
+
{ name: 'Boolean', value: 'Boolean' },
|
|
88
|
+
{ name: 'Date', value: 'Date' },
|
|
89
|
+
{ name: 'ObjectId (Reference)', value: 'ObjectId' },
|
|
90
|
+
{ name: 'Array', value: 'Array' },
|
|
91
|
+
{ name: 'Mixed (Object)', value: 'Mixed' }
|
|
92
|
+
]
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const fieldOptions = {};
|
|
96
|
+
|
|
97
|
+
// Common options
|
|
98
|
+
const isRequired = await confirm({
|
|
99
|
+
message: 'Is this field required?',
|
|
100
|
+
default: false
|
|
101
|
+
});
|
|
102
|
+
if (isRequired) fieldOptions.required = true;
|
|
103
|
+
|
|
104
|
+
const hasIndex = await confirm({
|
|
105
|
+
message: 'Add index to this field?',
|
|
106
|
+
default: false
|
|
107
|
+
});
|
|
108
|
+
if (hasIndex) fieldOptions.index = true;
|
|
109
|
+
|
|
110
|
+
// Type-specific options
|
|
111
|
+
if (fieldType === 'String') {
|
|
112
|
+
const isUnique = await confirm({
|
|
113
|
+
message: 'Should this field be unique?',
|
|
114
|
+
default: false
|
|
115
|
+
});
|
|
116
|
+
if (isUnique) fieldOptions.unique = true;
|
|
117
|
+
|
|
118
|
+
const maxLength = await input({
|
|
119
|
+
message: 'Maximum length (optional):',
|
|
120
|
+
validate: v => !v || !isNaN(v) ? true : 'Must be a number'
|
|
121
|
+
});
|
|
122
|
+
if (maxLength) fieldOptions.maxlength = parseInt(maxLength);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (fieldType === 'ObjectId') {
|
|
126
|
+
const refModel = await input({
|
|
127
|
+
message: 'Reference model name:',
|
|
128
|
+
validate: v => v.trim() ? true : 'Reference model is required'
|
|
129
|
+
});
|
|
130
|
+
fieldOptions.ref = refModel;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
fields[fieldName] = {
|
|
134
|
+
type: fieldType,
|
|
135
|
+
...fieldOptions
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return this.generateModelCode(modelName, fields, { timestamps: useTimestamps });
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
generateModelCode(modelName, fields, options = {}) {
|
|
143
|
+
const fieldsCode = Object.entries(fields)
|
|
144
|
+
.map(([name, config]) => {
|
|
145
|
+
const configStr = JSON.stringify(config, null, 4)
|
|
146
|
+
.replace(/"([^"]+)":/g, '$1:')
|
|
147
|
+
.replace(/"/g, "'");
|
|
148
|
+
return ` ${name}: ${configStr}`;
|
|
149
|
+
})
|
|
150
|
+
.join(',\n');
|
|
151
|
+
|
|
152
|
+
const optionsCode = options.timestamps ? '{ timestamps: true }' : '{}';
|
|
153
|
+
|
|
154
|
+
return `module.exports = async function (mongoose) {
|
|
155
|
+
const ${modelName}Schema = new mongoose.Schema({
|
|
156
|
+
${fieldsCode}
|
|
157
|
+
}, ${optionsCode});
|
|
158
|
+
|
|
159
|
+
// Add any custom methods here
|
|
160
|
+
// ${modelName}Schema.methods.customMethod = function() {
|
|
161
|
+
// // Custom instance method
|
|
162
|
+
// };
|
|
163
|
+
|
|
164
|
+
// ${modelName}Schema.statics.customStatic = function() {
|
|
165
|
+
// // Custom static method
|
|
166
|
+
// };
|
|
167
|
+
|
|
168
|
+
return mongoose.model('${modelName}', ${modelName}Schema);
|
|
169
|
+
};`;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async updateSettings(projectDir, modelName) {
|
|
173
|
+
const settingsPath = path.join(projectDir, 'settings.js');
|
|
174
|
+
let settingsContent = await fs.readFile(settingsPath, 'utf8');
|
|
175
|
+
|
|
176
|
+
// Add model to models array if not already present
|
|
177
|
+
const modelRegex = /models:\s*\[(.*?)\]/s;
|
|
178
|
+
const match = settingsContent.match(modelRegex);
|
|
179
|
+
|
|
180
|
+
if (match) {
|
|
181
|
+
const modelsArray = match[1];
|
|
182
|
+
if (!modelsArray.includes(`"${modelName}"`)) {
|
|
183
|
+
const newModelsArray = modelsArray.trim()
|
|
184
|
+
? `${modelsArray.trim()}, "${modelName}"`
|
|
185
|
+
: `"${modelName}"`;
|
|
186
|
+
settingsContent = settingsContent.replace(
|
|
187
|
+
modelRegex,
|
|
188
|
+
`models: [${newModelsArray}]`
|
|
189
|
+
);
|
|
190
|
+
await fs.writeFile(settingsPath, settingsContent);
|
|
191
|
+
console.log(`ā
Added "${modelName}" to settings.js models array`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async generateCrudRoutes(projectDir, modelName) {
|
|
197
|
+
// This would generate basic CRUD routes
|
|
198
|
+
console.log(`š§ Generating CRUD routes for ${modelName}...`);
|
|
199
|
+
// Implementation would go here
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
module.exports = ModelGenerator;
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
const { input, select, confirm } = require("@inquirer/prompts");
|
|
2
|
+
const fs = require("fs").promises;
|
|
3
|
+
const path = require("path");
|
|
4
|
+
const { spawn } = require('child_process');
|
|
5
|
+
|
|
6
|
+
const { showSuccess, runNpmInstall, createDirectory } = require('./utils');
|
|
7
|
+
|
|
8
|
+
class ProjectGenerator {
|
|
9
|
+
constructor(options = {}) {
|
|
10
|
+
this.verbose = options.verbose || false;
|
|
11
|
+
this.skipInstall = options.skipInstall || false;
|
|
12
|
+
this.useDefaults = options.useDefaults || false;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async create(projectName, template = 'basic') {
|
|
16
|
+
const currentDir = process.cwd();
|
|
17
|
+
|
|
18
|
+
// Get project name if not provided
|
|
19
|
+
if (!projectName) {
|
|
20
|
+
projectName = await input({
|
|
21
|
+
message: 'Project name:',
|
|
22
|
+
validate: v => v.trim() ? true : 'Project name is required',
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const projectPath = path.join(currentDir, projectName);
|
|
27
|
+
|
|
28
|
+
// Check if directory exists
|
|
29
|
+
try {
|
|
30
|
+
await fs.access(projectPath);
|
|
31
|
+
const overwrite = await confirm({
|
|
32
|
+
message: `Directory "${projectName}" already exists. Overwrite?`,
|
|
33
|
+
default: false
|
|
34
|
+
});
|
|
35
|
+
if (!overwrite) {
|
|
36
|
+
console.log('ā Project creation cancelled');
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
} catch (error) {
|
|
40
|
+
// Directory doesn't exist, which is good
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
console.log(`\nš Creating Falcon.js project: ${projectName}\n`);
|
|
44
|
+
|
|
45
|
+
// Create project structure
|
|
46
|
+
await this.createProjectStructure(projectPath, projectName, template);
|
|
47
|
+
|
|
48
|
+
// Install dependencies
|
|
49
|
+
if (!this.skipInstall) {
|
|
50
|
+
console.log('š¦ Installing dependencies...');
|
|
51
|
+
await runNpmInstall(projectPath);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
showSuccess(projectName, this.skipInstall);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async createProjectStructure(projectPath, projectName, template) {
|
|
58
|
+
// Create directories
|
|
59
|
+
const directories = [
|
|
60
|
+
'models/mongo',
|
|
61
|
+
'routes',
|
|
62
|
+
'services',
|
|
63
|
+
'workers',
|
|
64
|
+
'validators',
|
|
65
|
+
'init',
|
|
66
|
+
'logs',
|
|
67
|
+
'public'
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
for (const dir of directories) {
|
|
71
|
+
await createDirectory(path.join(projectPath, dir));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Create files
|
|
75
|
+
await this.createPackageJson(projectPath, projectName);
|
|
76
|
+
await this.createSettings(projectPath, projectName);
|
|
77
|
+
await this.createIndexJs(projectPath);
|
|
78
|
+
await this.createEnvFile(projectPath, projectName);
|
|
79
|
+
await this.createExampleFiles(projectPath);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async createPackageJson(projectPath, projectName) {
|
|
83
|
+
const packageJson = {
|
|
84
|
+
name: projectName,
|
|
85
|
+
version: "1.0.0",
|
|
86
|
+
description: "Falcon.js application",
|
|
87
|
+
main: "index.js",
|
|
88
|
+
scripts: {
|
|
89
|
+
start: "node index.js",
|
|
90
|
+
dev: "nodemon index.js",
|
|
91
|
+
test: "echo \"Error: no test specified\" && exit 1"
|
|
92
|
+
},
|
|
93
|
+
keywords: ["falcon", "nodejs", "api"],
|
|
94
|
+
author: "",
|
|
95
|
+
license: "ISC",
|
|
96
|
+
type: "commonjs",
|
|
97
|
+
dependencies: {
|
|
98
|
+
"falconjs": "file:../falconjs",
|
|
99
|
+
"@hapi/boom": "^10.0.1",
|
|
100
|
+
"@hapi/inert": "^7.1.0",
|
|
101
|
+
"@hapi/vision": "^7.0.3",
|
|
102
|
+
"joi": "^17.13.3",
|
|
103
|
+
"mongoose": "^8.3.1",
|
|
104
|
+
"redis": "^4.7.0",
|
|
105
|
+
"mqtt": "^5.13.3",
|
|
106
|
+
"log4js": "^6.9.1",
|
|
107
|
+
"rate-limiter-flexible": "^2.4.2",
|
|
108
|
+
"hapi-alive": "^2.0.4",
|
|
109
|
+
"hapi-swagger": "^17.2.1",
|
|
110
|
+
"dotenv": "^16.0.0"
|
|
111
|
+
},
|
|
112
|
+
devDependencies: {
|
|
113
|
+
"nodemon": "^3.1.10"
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
await fs.writeFile(
|
|
118
|
+
path.join(projectPath, 'package.json'),
|
|
119
|
+
JSON.stringify(packageJson, null, 2)
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async createSettings(projectPath, projectName) {
|
|
124
|
+
const settings = `module.exports.settings = {
|
|
125
|
+
name: "${projectName}",
|
|
126
|
+
http: {
|
|
127
|
+
host: process.env.HTTP_HOST || "localhost",
|
|
128
|
+
port: process.env.HTTP_PORT || 3000
|
|
129
|
+
},
|
|
130
|
+
database: {
|
|
131
|
+
mongodb: {
|
|
132
|
+
database: process.env.MONGODB_URL || "mongodb://localhost:27017/${projectName}_db"
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
mqtt: {
|
|
136
|
+
internal: true,
|
|
137
|
+
external: false
|
|
138
|
+
},
|
|
139
|
+
log: {
|
|
140
|
+
appenders: {
|
|
141
|
+
file: {
|
|
142
|
+
type: "file",
|
|
143
|
+
filename: process.env.LOG_FILE_NAME || "logs/app.log",
|
|
144
|
+
maxLogSize: process.env.LOG_MAX_SIZE || "10M",
|
|
145
|
+
backups: 3
|
|
146
|
+
},
|
|
147
|
+
console: {
|
|
148
|
+
type: "console"
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
categories: {
|
|
152
|
+
default: {
|
|
153
|
+
appenders: ["file", "console"],
|
|
154
|
+
level: process.env.LOG_LEVEL || "info"
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
swagger: {
|
|
159
|
+
enabled: true,
|
|
160
|
+
path: "/documentation"
|
|
161
|
+
},
|
|
162
|
+
auth: {
|
|
163
|
+
jwt: {
|
|
164
|
+
key: process.env.JWT_SECRET || "your-secret-key-here",
|
|
165
|
+
validate: async (decoded, request) => {
|
|
166
|
+
// Your JWT validation logic
|
|
167
|
+
return { id: decoded.id, roles: decoded.roles };
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
default: "jwt"
|
|
171
|
+
},
|
|
172
|
+
services: [],
|
|
173
|
+
workers: [],
|
|
174
|
+
models: [],
|
|
175
|
+
routes: [],
|
|
176
|
+
crud: {
|
|
177
|
+
exclude: []
|
|
178
|
+
},
|
|
179
|
+
postInit: "post"
|
|
180
|
+
};`;
|
|
181
|
+
|
|
182
|
+
await fs.writeFile(path.join(projectPath, 'settings.js'), settings);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async createIndexJs(projectPath) {
|
|
186
|
+
const indexContent = await fs.readFile(
|
|
187
|
+
path.join(__dirname, '../templates/index.txt'),
|
|
188
|
+
'utf8'
|
|
189
|
+
);
|
|
190
|
+
await fs.writeFile(path.join(projectPath, 'index.js'), indexContent);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async createEnvFile(projectPath, projectName) {
|
|
194
|
+
const envContent = `# Falcon.js Environment Configuration
|
|
195
|
+
# Generated by falcon-cli
|
|
196
|
+
|
|
197
|
+
# Server Configuration
|
|
198
|
+
HTTP_HOST=localhost
|
|
199
|
+
HTTP_PORT=3000
|
|
200
|
+
|
|
201
|
+
# Database Configuration
|
|
202
|
+
MONGODB_URL=mongodb://localhost:27017/${projectName}_db
|
|
203
|
+
|
|
204
|
+
# MQTT Configuration
|
|
205
|
+
MQTT_PORT=1883
|
|
206
|
+
MQTT_URL=mqtt://localhost:1883
|
|
207
|
+
|
|
208
|
+
# Redis Configuration (optional)
|
|
209
|
+
REDIS_ENABLE=false
|
|
210
|
+
REDIS_URL=redis://localhost:6379
|
|
211
|
+
|
|
212
|
+
# Authentication
|
|
213
|
+
JWT_SECRET=your-super-secret-jwt-key-change-this-in-production
|
|
214
|
+
|
|
215
|
+
# Logging
|
|
216
|
+
LOG_LEVEL=info
|
|
217
|
+
LOG_FILE_NAME=logs/app.log
|
|
218
|
+
LOG_MAX_SIZE=10M
|
|
219
|
+
|
|
220
|
+
# Development Mode
|
|
221
|
+
MODE=DEV
|
|
222
|
+
`;
|
|
223
|
+
|
|
224
|
+
await fs.writeFile(path.join(projectPath, '.env'), envContent);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async createExampleFiles(projectPath) {
|
|
228
|
+
const templates = [
|
|
229
|
+
{ src: 'example-route.js', dest: 'routes/example.js' },
|
|
230
|
+
{ src: 'example-validator.js', dest: 'validators/ExamplePayload.js' },
|
|
231
|
+
{ src: 'example-service.js', dest: 'services/example.js' },
|
|
232
|
+
{ src: 'example-worker.js', dest: 'workers/example.js' },
|
|
233
|
+
{ src: 'post-init.js', dest: 'init/post.js' }
|
|
234
|
+
];
|
|
235
|
+
|
|
236
|
+
for (const template of templates) {
|
|
237
|
+
const content = await fs.readFile(
|
|
238
|
+
path.join(__dirname, '../templates', template.src),
|
|
239
|
+
'utf8'
|
|
240
|
+
);
|
|
241
|
+
await fs.writeFile(path.join(projectPath, template.dest), content);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
module.exports = ProjectGenerator;
|
package/lib/utils.js
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
const fs = require("fs").promises;
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const { spawn } = require('child_process');
|
|
4
|
+
|
|
5
|
+
const logo = `
|
|
6
|
+
āāāāāāāā āāāāāā āāā āāāāāāā āāāāāāā āāāā āāā
|
|
7
|
+
āāāāāāāāāāāāāāāāāāā āāāāāāāāāāāāāāāāāāāāāā āāā
|
|
8
|
+
āāāāāā āāāāāāāāāāā āāā āāā āāāāāāāāā āāā
|
|
9
|
+
āāāāāā āāāāāāāāāāā āāā āāā āāāāāāāāāāāāā
|
|
10
|
+
āāā āāā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā āāāāāā
|
|
11
|
+
āāā āāā āāāāāāāāāāā āāāāāāā āāāāāāā āāā āāāāā
|
|
12
|
+
|
|
13
|
+
š Falcon.js CLI - Build powerful Node.js APIs
|
|
14
|
+
`;
|
|
15
|
+
|
|
16
|
+
function showLogo() {
|
|
17
|
+
console.log(logo);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function showSuccess(projectName, skipInstall = false) {
|
|
21
|
+
console.log(`
|
|
22
|
+
ā
Falcon.js project created successfully!
|
|
23
|
+
|
|
24
|
+
š Project structure:
|
|
25
|
+
āāā index.js # Main application entry point
|
|
26
|
+
āāā settings.js # Falcon configuration
|
|
27
|
+
āāā .env # Environment variables
|
|
28
|
+
āāā package.json # Dependencies
|
|
29
|
+
āāā routes/ # API route handlers
|
|
30
|
+
āāā models/mongo/ # Database models
|
|
31
|
+
āāā services/ # Background services
|
|
32
|
+
āāā workers/ # Job processors
|
|
33
|
+
āāā validators/ # Joi validation schemas
|
|
34
|
+
āāā init/ # Initialization scripts
|
|
35
|
+
āāā logs/ # Application logs
|
|
36
|
+
|
|
37
|
+
š Next steps:
|
|
38
|
+
1. cd ${projectName}${skipInstall ? '\n 2. npm install' : ''}
|
|
39
|
+
${skipInstall ? '3' : '2'}. npm run dev
|
|
40
|
+
|
|
41
|
+
š Documentation will be available at: http://localhost:3000/documentation
|
|
42
|
+
ā¤ļø Health check at: http://localhost:3000/health
|
|
43
|
+
`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function createDirectory(dirPath) {
|
|
47
|
+
try {
|
|
48
|
+
await fs.mkdir(dirPath, { recursive: true });
|
|
49
|
+
} catch (error) {
|
|
50
|
+
if (error.code !== 'EEXIST') {
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function runNpmInstall(projectPath, packageName = null) {
|
|
57
|
+
return new Promise((resolve, reject) => {
|
|
58
|
+
const args = packageName ? ['install', packageName] : ['install'];
|
|
59
|
+
const npm = spawn('npm', args, {
|
|
60
|
+
cwd: projectPath,
|
|
61
|
+
stdio: 'inherit'
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
npm.on('close', (code) => {
|
|
65
|
+
if (code === 0) {
|
|
66
|
+
resolve();
|
|
67
|
+
} else {
|
|
68
|
+
reject(new Error(`npm install failed with code ${code}`));
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
npm.on('error', (error) => {
|
|
73
|
+
reject(error);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function validateProjectName(name) {
|
|
79
|
+
if (!name || typeof name !== 'string') {
|
|
80
|
+
return 'Project name is required';
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (!/^[a-z0-9-_]+$/i.test(name)) {
|
|
84
|
+
return 'Project name can only contain letters, numbers, hyphens, and underscores';
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (name.length < 2) {
|
|
88
|
+
return 'Project name must be at least 2 characters long';
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
module.exports = {
|
|
95
|
+
showLogo,
|
|
96
|
+
showSuccess,
|
|
97
|
+
createDirectory,
|
|
98
|
+
runNpmInstall,
|
|
99
|
+
validateProjectName
|
|
100
|
+
};
|
package/logo.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@triophore/falcon-cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
8
|
+
},
|
|
9
|
+
"keywords": [],
|
|
10
|
+
"author": "",
|
|
11
|
+
"license": "ISC",
|
|
12
|
+
"type": "commonjs",
|
|
13
|
+
"bin": {
|
|
14
|
+
"falcon-cli": "./cli.js"
|
|
15
|
+
},
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@inquirer/prompts": "^7.9.0",
|
|
21
|
+
"acorn": "^8.15.0",
|
|
22
|
+
"ajv": "^8.17.1",
|
|
23
|
+
"escodegen": "^2.1.0",
|
|
24
|
+
"estraverse": "^5.3.0",
|
|
25
|
+
"joi": "^18.0.1",
|
|
26
|
+
"mongoose": "^8.19.3",
|
|
27
|
+
"prompt-sync": "^4.2.0",
|
|
28
|
+
"reflect-metadata": "^0.2.2",
|
|
29
|
+
"sequelize": "^6.37.7",
|
|
30
|
+
"unzipper": "^0.12.3",
|
|
31
|
+
"yargs": "^18.0.0",
|
|
32
|
+
"@hapi/boom": "^10.0.1",
|
|
33
|
+
"rate-limiter-flexible": "^2.4.2"
|
|
34
|
+
}
|
|
35
|
+
}
|
package/readme.md
ADDED
package/schema.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$id": "configSchema",
|
|
3
|
+
"type": "object",
|
|
4
|
+
"required": ["crud", "routes", "models"],
|
|
5
|
+
"properties": {
|
|
6
|
+
"crud": {
|
|
7
|
+
"type": "object",
|
|
8
|
+
"required": ["exclude"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"exclude": {
|
|
11
|
+
"type": "array",
|
|
12
|
+
"items": { "type": "string" },
|
|
13
|
+
"minItems": 1
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"additionalProperties": false
|
|
17
|
+
},
|
|
18
|
+
"auth": { "type": "object" },
|
|
19
|
+
"services": {
|
|
20
|
+
"type": "array",
|
|
21
|
+
"items": { "type": "string" }
|
|
22
|
+
},
|
|
23
|
+
"workers": {
|
|
24
|
+
"type": "array",
|
|
25
|
+
"items": { "type": "string" }
|
|
26
|
+
},
|
|
27
|
+
"preInit": { "type": "string" },
|
|
28
|
+
"postInit": { "type": "string" },
|
|
29
|
+
"routes": {
|
|
30
|
+
"type": "array",
|
|
31
|
+
"items": { "type": "string" },
|
|
32
|
+
"minItems": 1,
|
|
33
|
+
"uniqueItems": true
|
|
34
|
+
},
|
|
35
|
+
"models": {
|
|
36
|
+
"type": "array",
|
|
37
|
+
"items": { "type": "string" },
|
|
38
|
+
"minItems": 1
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"additionalProperties": false
|
|
42
|
+
}
|
|
File without changes
|