4bnode 3.5.3 → 4.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 +138 -113
- package/add-api.js +288 -156
- package/add-crud.js +226 -0
- package/add-login.js +152 -319
- package/add-mongo-delete.js +67 -138
- package/add-mongo-insert.js +87 -204
- package/add-mongo-read.js +100 -216
- package/add-mongo-schema.js +431 -199
- package/add-mongo-update.js +89 -202
- package/add-mongo.js +125 -218
- package/add-serialport.js +53 -98
- package/add-socket.js +43 -66
- package/add-websocket.js +68 -140
- package/index.js +288 -155
- package/lib/env.js +18 -0
- package/lib/indexFile.js +87 -0
- package/lib/names.js +21 -0
- package/lib/project.js +122 -0
- package/lib/routeFile.js +56 -0
- package/lib/ui.js +161 -0
- package/package.json +9 -16
- package/skeleton/dev-api.js +2785 -0
- package/skeleton/index.js +10 -3
- package/skeleton/package.json +9 -5
- package/skeleton/public/fonts/NotoSans-Bold.woff2 +0 -0
- package/skeleton/public/fonts/NotoSans-Light.woff2 +0 -0
- package/skeleton/public/fonts/NotoSans-Medium.woff2 +0 -0
- package/skeleton/public/fonts/NotoSans-Regular.woff2 +0 -0
- package/skeleton/public/fonts/NotoSans-SemiBold.woff2 +0 -0
- package/skeleton/public/images/mongo.png +0 -0
- package/skeleton/public/images/mysql.png +0 -0
- package/skeleton/public/images/postgresql.png +0 -0
- package/skeleton/public/images/sqlite.png +0 -0
- package/skeleton/public/index.html +7562 -64
- package/README_4bnode.md +0 -197
- package/src/auth/passport.js +0 -21
- package/src/db.js +0 -13
- package/test-server/.env.development +0 -1
- package/test-server/.env.production +0 -1
- package/test-server/index.js +0 -82
- package/test-server/package-lock.json +0 -1267
- package/test-server/package.json +0 -20
- package/test-server/public/index.html +0 -90
package/lib/project.js
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
|
|
4
|
+
export function getProjectRoot() {
|
|
5
|
+
return process.cwd();
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function listRoutes() {
|
|
9
|
+
const routesDir = path.join(getProjectRoot(), "src", "routes");
|
|
10
|
+
if (!fs.existsSync(routesDir)) {
|
|
11
|
+
return [];
|
|
12
|
+
}
|
|
13
|
+
return fs.readdirSync(routesDir).filter((f) => f.endsWith(".js"));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function listModels() {
|
|
17
|
+
const modelsDir = path.join(getProjectRoot(), "src", "models");
|
|
18
|
+
if (!fs.existsSync(modelsDir)) {
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
return fs.readdirSync(modelsDir).filter((f) => f.endsWith(".js"));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function extractSchemaBlock(content) {
|
|
25
|
+
const marker = "new mongoose.Schema(";
|
|
26
|
+
const idx = content.indexOf(marker);
|
|
27
|
+
if (idx === -1) return null;
|
|
28
|
+
// Find the opening { after the marker
|
|
29
|
+
const braceStart = content.indexOf("{", idx + marker.length);
|
|
30
|
+
if (braceStart === -1) return null;
|
|
31
|
+
// Match balanced braces to find the outer schema object
|
|
32
|
+
let depth = 0;
|
|
33
|
+
for (let i = braceStart; i < content.length; i++) {
|
|
34
|
+
if (content[i] === "{") depth++;
|
|
35
|
+
else if (content[i] === "}") {
|
|
36
|
+
depth--;
|
|
37
|
+
if (depth === 0) return content.slice(braceStart + 1, i);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function getModelSchema(modelName, { includeId = false } = {}) {
|
|
44
|
+
const modelFilePath = path.join(
|
|
45
|
+
getProjectRoot(),
|
|
46
|
+
"src",
|
|
47
|
+
"models",
|
|
48
|
+
modelName + ".js"
|
|
49
|
+
);
|
|
50
|
+
if (!fs.existsSync(modelFilePath)) {
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const content = fs.readFileSync(modelFilePath, "utf8");
|
|
55
|
+
const schemaContent = extractSchemaBlock(content);
|
|
56
|
+
|
|
57
|
+
if (!schemaContent) {
|
|
58
|
+
return [];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const fieldRegex = /(\w+)\s*:\s*\{/g;
|
|
62
|
+
const fields = includeId ? ["_id"] : [];
|
|
63
|
+
let fieldMatch;
|
|
64
|
+
while ((fieldMatch = fieldRegex.exec(schemaContent)) !== null) {
|
|
65
|
+
fields.push(fieldMatch[1]);
|
|
66
|
+
}
|
|
67
|
+
return fields;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function getModelSchemaDetailed(modelName) {
|
|
71
|
+
const modelFilePath = path.join(
|
|
72
|
+
getProjectRoot(),
|
|
73
|
+
"src",
|
|
74
|
+
"models",
|
|
75
|
+
modelName + ".js"
|
|
76
|
+
);
|
|
77
|
+
if (!fs.existsSync(modelFilePath)) {
|
|
78
|
+
return [];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const content = fs.readFileSync(modelFilePath, "utf8");
|
|
82
|
+
const schemaContent = extractSchemaBlock(content);
|
|
83
|
+
if (!schemaContent) return [];
|
|
84
|
+
const fields = [];
|
|
85
|
+
// Match each field block: fieldName: { type: ..., required: ..., unique: ..., default: ... }
|
|
86
|
+
const fieldRegex = /(\w+)\s*:\s*\{([^}]*)\}/g;
|
|
87
|
+
let fm;
|
|
88
|
+
while ((fm = fieldRegex.exec(schemaContent)) !== null) {
|
|
89
|
+
const name = fm[1];
|
|
90
|
+
const props = fm[2];
|
|
91
|
+
const typeMatch = props.match(/type:\s*([\w.]+)/);
|
|
92
|
+
const type = typeMatch
|
|
93
|
+
? typeMatch[1].replace("mongoose.Schema.Types.", "")
|
|
94
|
+
: "String";
|
|
95
|
+
const required = /required:\s*true/.test(props);
|
|
96
|
+
const unique = /unique:\s*true/.test(props);
|
|
97
|
+
const defaultMatch = props.match(/default:\s*(.+?)(?:,\s*\w+:|$)/);
|
|
98
|
+
const field = { name, type, required, unique };
|
|
99
|
+
if (defaultMatch) {
|
|
100
|
+
field.default = defaultMatch[1].trim();
|
|
101
|
+
}
|
|
102
|
+
fields.push(field);
|
|
103
|
+
}
|
|
104
|
+
return fields;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function checkMongoConfig() {
|
|
108
|
+
const envPath = path.join(getProjectRoot(), ".env.development");
|
|
109
|
+
return (
|
|
110
|
+
fs.existsSync(envPath) &&
|
|
111
|
+
fs.readFileSync(envPath, "utf8").includes("MONGO_URI")
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function checkRoutesDir() {
|
|
116
|
+
return fs.existsSync(path.join(getProjectRoot(), "src", "routes"));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function checkModelsExist() {
|
|
120
|
+
const modelsDir = path.join(getProjectRoot(), "src", "models");
|
|
121
|
+
return fs.existsSync(modelsDir) && fs.readdirSync(modelsDir).length > 0;
|
|
122
|
+
}
|
package/lib/routeFile.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { getProjectRoot } from "./project.js";
|
|
4
|
+
|
|
5
|
+
export function getRouteFilePath(routeFile) {
|
|
6
|
+
return path.join(getProjectRoot(), "src", "routes", routeFile);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function addImportToRoute(routeFilePath, importStatement) {
|
|
10
|
+
let content = fs.readFileSync(routeFilePath, "utf8");
|
|
11
|
+
if (!content.includes(importStatement.trim())) {
|
|
12
|
+
content = importStatement.trim() + "\n" + content;
|
|
13
|
+
fs.writeFileSync(routeFilePath, content, "utf8");
|
|
14
|
+
}
|
|
15
|
+
return content;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function insertCodeIntoRoute(routeFilePath, codeBlock) {
|
|
19
|
+
let content = fs.readFileSync(routeFilePath, "utf8");
|
|
20
|
+
|
|
21
|
+
// Prefer inserting before "export default router;" as it's more reliable
|
|
22
|
+
const exportIndex = content.lastIndexOf("export default router;");
|
|
23
|
+
if (exportIndex !== -1) {
|
|
24
|
+
content =
|
|
25
|
+
content.slice(0, exportIndex) +
|
|
26
|
+
codeBlock.trim() +
|
|
27
|
+
"\n\n" +
|
|
28
|
+
content.slice(exportIndex);
|
|
29
|
+
} else {
|
|
30
|
+
// Fallback to inserting before last "});"
|
|
31
|
+
const lastClose = content.lastIndexOf("});");
|
|
32
|
+
if (lastClose !== -1) {
|
|
33
|
+
content =
|
|
34
|
+
content.slice(0, lastClose) +
|
|
35
|
+
codeBlock.trim() +
|
|
36
|
+
"\n" +
|
|
37
|
+
content.slice(lastClose);
|
|
38
|
+
} else {
|
|
39
|
+
content += "\n" + codeBlock.trim() + "\n";
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
fs.writeFileSync(routeFilePath, content, "utf8");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function detectHttpMethod(routeFilePath) {
|
|
47
|
+
const content = fs.readFileSync(routeFilePath, "utf8");
|
|
48
|
+
const methodRegex =
|
|
49
|
+
/router\.(get|post|put|patch|delete)\((['"`])\/.*?\2,\s*async\s*\(req,\s*res/;
|
|
50
|
+
const match = content.match(methodRegex);
|
|
51
|
+
return match ? match[1].toLowerCase() : "post";
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function getRequestDataSource(method) {
|
|
55
|
+
return method === "get" || method === "delete" ? "req.query" : "req.body";
|
|
56
|
+
}
|
package/lib/ui.js
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import gradient from "gradient-string";
|
|
3
|
+
import ora from "ora";
|
|
4
|
+
|
|
5
|
+
// Brand gradients
|
|
6
|
+
const brand = gradient(["#00c6ff", "#0072ff", "#7b2ff7"]);
|
|
7
|
+
const warm = gradient(["#f7971e", "#ffd200"]);
|
|
8
|
+
const cool = gradient(["#00f260", "#0575e6"]);
|
|
9
|
+
const fire = gradient(["#ff416c", "#ff4b2b"]);
|
|
10
|
+
|
|
11
|
+
// Colors
|
|
12
|
+
const dim = chalk.gray;
|
|
13
|
+
const accent = chalk.hex("#00c6ff");
|
|
14
|
+
const highlight = chalk.hex("#ffd200").bold;
|
|
15
|
+
const success = chalk.hex("#00f260").bold;
|
|
16
|
+
const warn = chalk.hex("#ffd200");
|
|
17
|
+
const err = chalk.hex("#ff416c").bold;
|
|
18
|
+
const info = chalk.hex("#0575e6");
|
|
19
|
+
const muted = chalk.hex("#888888");
|
|
20
|
+
const white = chalk.white;
|
|
21
|
+
const bold = chalk.white.bold;
|
|
22
|
+
|
|
23
|
+
const line = dim(" ─────────────────────────────────────────────────────");
|
|
24
|
+
|
|
25
|
+
const banner = `
|
|
26
|
+
██╗ ██╗██████╗ ███╗ ██╗ ██████╗ ██████╗ ███████╗
|
|
27
|
+
██║ ██║██╔══██╗████╗ ██║██╔═══██╗██╔══██╗██╔════╝
|
|
28
|
+
███████║██████╔╝██╔██╗ ██║██║ ██║██║ ██║█████╗
|
|
29
|
+
╚════██║██╔══██╗██║╚██╗██║██║ ██║██║ ██║██╔══╝
|
|
30
|
+
██║██████╔╝██║ ╚████║╚██████╔╝██████╔╝███████╗
|
|
31
|
+
╚═╝╚═════╝ ╚═╝ ╚═══╝ ╚═════╝ ╚═════╝ ╚══════╝`;
|
|
32
|
+
|
|
33
|
+
// ── Banner ──────────────────────────────────────────
|
|
34
|
+
|
|
35
|
+
export function showBanner() {
|
|
36
|
+
console.log(brand(banner));
|
|
37
|
+
console.log(line);
|
|
38
|
+
console.log(
|
|
39
|
+
dim(" ") + bold("4Brains Technologies") + dim(" | Node.js Project Generator")
|
|
40
|
+
);
|
|
41
|
+
console.log(line + "\n");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ── Section Header ──────────────────────────────────
|
|
45
|
+
|
|
46
|
+
export function showHeader(command, description) {
|
|
47
|
+
console.log();
|
|
48
|
+
console.log(dim(" ┌─────────────────────────────────────────────────┐"));
|
|
49
|
+
console.log(
|
|
50
|
+
dim(" │ ") + accent("◆ ") + highlight(command) + dim(" ") + white(description) + dim(" │")
|
|
51
|
+
);
|
|
52
|
+
console.log(dim(" └─────────────────────────────────────────────────┘"));
|
|
53
|
+
console.log();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ── Spinners ────────────────────────────────────────
|
|
57
|
+
|
|
58
|
+
export function createSpinner(text) {
|
|
59
|
+
return ora({
|
|
60
|
+
text: muted(text),
|
|
61
|
+
spinner: "dots12",
|
|
62
|
+
color: "cyan",
|
|
63
|
+
indent: 2,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ── Step indicators ─────────────────────────────────
|
|
68
|
+
|
|
69
|
+
export function showStep(step, total, text) {
|
|
70
|
+
const progress = accent(`[${step}/${total}]`);
|
|
71
|
+
console.log(` ${progress} ${white(text)}`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ── Success ─────────────────────────────────────────
|
|
75
|
+
|
|
76
|
+
export function showSuccess(lines) {
|
|
77
|
+
console.log();
|
|
78
|
+
console.log(dim(" ┌─────────────────────────────────────────────────┐"));
|
|
79
|
+
console.log(dim(" │") + success(" ✔ Done!") + dim(new Array(42).join(" ") + "│"));
|
|
80
|
+
console.log(dim(" └─────────────────────────────────────────────────┘"));
|
|
81
|
+
console.log();
|
|
82
|
+
for (const l of lines) {
|
|
83
|
+
console.log(dim(" │ ") + white(l));
|
|
84
|
+
}
|
|
85
|
+
console.log();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ── Task complete (for add-* commands) ──────────────
|
|
89
|
+
|
|
90
|
+
export function showTaskDone(title, details = []) {
|
|
91
|
+
console.log();
|
|
92
|
+
console.log(` ${success("✔")} ${cool(title)}`);
|
|
93
|
+
for (const d of details) {
|
|
94
|
+
console.log(` ${dim("→")} ${muted(d)}`);
|
|
95
|
+
}
|
|
96
|
+
console.log();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ── Warning ─────────────────────────────────────────
|
|
100
|
+
|
|
101
|
+
export function showWarning(message) {
|
|
102
|
+
console.log(`\n ${warn("⚠")} ${warn(message)}\n`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ── Error ───────────────────────────────────────────
|
|
106
|
+
|
|
107
|
+
export function showError(message) {
|
|
108
|
+
console.log(`\n ${err("✖")} ${err(message)}\n`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ── Info ────────────────────────────────────────────
|
|
112
|
+
|
|
113
|
+
export function showInfo(message) {
|
|
114
|
+
console.log(` ${info("ℹ")} ${muted(message)}`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ── Next Steps ──────────────────────────────────────
|
|
118
|
+
|
|
119
|
+
export function showNextSteps(steps) {
|
|
120
|
+
console.log(accent.bold(" Next steps:\n"));
|
|
121
|
+
for (let i = 0; i < steps.length; i++) {
|
|
122
|
+
const num = highlight(` ${i + 1} `);
|
|
123
|
+
console.log(` ${num} ${white(steps[i])}`);
|
|
124
|
+
}
|
|
125
|
+
console.log();
|
|
126
|
+
console.log(line);
|
|
127
|
+
console.log(dim(" Happy coding! ") + brand("★ 4Brains"));
|
|
128
|
+
console.log(line + "\n");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ── Installing deps (with spinner) ──────────────────
|
|
132
|
+
|
|
133
|
+
export async function installDeps(packages, opts = {}) {
|
|
134
|
+
const { execSync } = await import("child_process");
|
|
135
|
+
const names = Array.isArray(packages) ? packages : [packages];
|
|
136
|
+
const label = names.join(", ");
|
|
137
|
+
const spinner = createSpinner(`Installing ${label}...`);
|
|
138
|
+
spinner.start();
|
|
139
|
+
try {
|
|
140
|
+
execSync(`npm install ${names.join(" ")}`, {
|
|
141
|
+
stdio: "pipe",
|
|
142
|
+
...opts,
|
|
143
|
+
});
|
|
144
|
+
spinner.succeed(success(`Installed ${label}`));
|
|
145
|
+
} catch (e) {
|
|
146
|
+
spinner.fail(err(`Failed to install ${label}`));
|
|
147
|
+
throw e;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ── File action log ─────────────────────────────────
|
|
152
|
+
|
|
153
|
+
export function showFileAction(action, filePath) {
|
|
154
|
+
const icons = {
|
|
155
|
+
created: chalk.hex("#00f260")("+ "),
|
|
156
|
+
updated: chalk.hex("#0575e6")("~ "),
|
|
157
|
+
deleted: chalk.hex("#ff416c")("- "),
|
|
158
|
+
};
|
|
159
|
+
const icon = icons[action] || dim(" ");
|
|
160
|
+
console.log(` ${icon}${muted(filePath)}`);
|
|
161
|
+
}
|
package/package.json
CHANGED
|
@@ -1,28 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "4bnode",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "A
|
|
3
|
+
"version": "4.0.0",
|
|
4
|
+
"description": "A professional tool to generate a Node.js app by 4Brains Technologies",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
8
|
-
"4bnode": "index.js"
|
|
9
|
-
"add-api": "add-api.js",
|
|
10
|
-
"add-mongo": "add-mongo.js",
|
|
11
|
-
"add-socket": "add-socket.js",
|
|
12
|
-
"add-websocket": "add-websocket.js",
|
|
13
|
-
"add-mongo-schema": "add-mongo-schema.js",
|
|
14
|
-
"add-login": "add-login.js",
|
|
15
|
-
"add-mongo-insert": "add-mongo-insert.js",
|
|
16
|
-
"add-mongo-update": "add-mongo-update.js",
|
|
17
|
-
"add-mongo-read": "add-mongo-read.js",
|
|
18
|
-
"add-mongo-delete": "add-mongo-delete.js"
|
|
8
|
+
"4bnode": "index.js"
|
|
19
9
|
},
|
|
20
10
|
"scripts": {
|
|
21
11
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
22
12
|
},
|
|
23
13
|
"repository": {
|
|
24
14
|
"type": "git",
|
|
25
|
-
"url": "https://github.com/pawan-4brains/
|
|
15
|
+
"url": "https://github.com/pawan-4brains/4bnode"
|
|
26
16
|
},
|
|
27
17
|
"keywords": [
|
|
28
18
|
"node",
|
|
@@ -33,8 +23,11 @@
|
|
|
33
23
|
"Node.js skeleton"
|
|
34
24
|
],
|
|
35
25
|
"author": "4Brains",
|
|
36
|
-
"license": "
|
|
26
|
+
"license": "Proprietary",
|
|
37
27
|
"dependencies": {
|
|
38
|
-
"
|
|
28
|
+
"@inquirer/prompts": "^8.3.0",
|
|
29
|
+
"chalk": "^5.4.1",
|
|
30
|
+
"gradient-string": "^3.0.0",
|
|
31
|
+
"ora": "^9.3.0"
|
|
39
32
|
}
|
|
40
33
|
}
|