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/index.js
CHANGED
|
@@ -2,27 +2,40 @@
|
|
|
2
2
|
|
|
3
3
|
import fs from "fs";
|
|
4
4
|
import path from "path";
|
|
5
|
-
import {
|
|
5
|
+
import { execFileSync } from "child_process";
|
|
6
6
|
import { fileURLToPath } from "url";
|
|
7
7
|
import chalk from "chalk";
|
|
8
|
+
import { input, select } from "@inquirer/prompts";
|
|
9
|
+
import { sanitizeName } from "./lib/names.js";
|
|
10
|
+
import {
|
|
11
|
+
checkMongoConfig,
|
|
12
|
+
checkRoutesDir,
|
|
13
|
+
checkModelsExist,
|
|
14
|
+
listModels,
|
|
15
|
+
listRoutes,
|
|
16
|
+
getModelSchemaDetailed,
|
|
17
|
+
} from "./lib/project.js";
|
|
18
|
+
import {
|
|
19
|
+
showBanner,
|
|
20
|
+
createSpinner,
|
|
21
|
+
showSuccess,
|
|
22
|
+
showNextSteps,
|
|
23
|
+
showError,
|
|
24
|
+
showHeader,
|
|
25
|
+
} from "./lib/ui.js";
|
|
8
26
|
|
|
9
27
|
const __filename = fileURLToPath(import.meta.url);
|
|
10
28
|
const __dirname = path.dirname(__filename);
|
|
11
29
|
const skeletonPath = path.join(__dirname, "skeleton");
|
|
12
30
|
|
|
13
31
|
function copyRecursiveSync(src, dest) {
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
const isDirectory = exists && stats.isDirectory();
|
|
17
|
-
if (isDirectory) {
|
|
32
|
+
const stats = fs.statSync(src);
|
|
33
|
+
if (stats.isDirectory()) {
|
|
18
34
|
if (!fs.existsSync(dest)) {
|
|
19
35
|
fs.mkdirSync(dest);
|
|
20
36
|
}
|
|
21
|
-
fs.readdirSync(src).forEach((
|
|
22
|
-
copyRecursiveSync(
|
|
23
|
-
path.join(src, childItemName),
|
|
24
|
-
path.join(dest, childItemName)
|
|
25
|
-
);
|
|
37
|
+
fs.readdirSync(src).forEach((child) => {
|
|
38
|
+
copyRecursiveSync(path.join(src, child), path.join(dest, child));
|
|
26
39
|
});
|
|
27
40
|
} else {
|
|
28
41
|
fs.copyFileSync(src, dest);
|
|
@@ -41,74 +54,179 @@ function updatePackageName(destinationPath, projectName) {
|
|
|
41
54
|
}
|
|
42
55
|
|
|
43
56
|
function createSkeletonApp(projectName) {
|
|
57
|
+
showBanner();
|
|
58
|
+
|
|
44
59
|
const targetPath = path.resolve(projectName);
|
|
60
|
+
|
|
61
|
+
const scaffoldSpinner = createSpinner("Scaffolding project structure...");
|
|
62
|
+
scaffoldSpinner.start();
|
|
45
63
|
if (!fs.existsSync(targetPath)) {
|
|
46
64
|
fs.mkdirSync(targetPath, { recursive: true });
|
|
47
65
|
}
|
|
48
66
|
copyRecursiveSync(skeletonPath, targetPath);
|
|
49
67
|
updatePackageName(targetPath, projectName);
|
|
50
|
-
|
|
68
|
+
scaffoldSpinner.succeed(chalk.hex("#00f260").bold("Project scaffolded"));
|
|
51
69
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
70
|
+
const installSpinner = createSpinner("Installing dependencies...");
|
|
71
|
+
installSpinner.start();
|
|
72
|
+
execFileSync("npm", ["install"], { stdio: "pipe", cwd: targetPath });
|
|
73
|
+
installSpinner.succeed(chalk.hex("#00f260").bold("Dependencies installed"));
|
|
56
74
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
75
|
+
showSuccess([
|
|
76
|
+
`Project ${chalk.hex("#00c6ff").bold(projectName)} created at ${chalk.gray(targetPath)}`,
|
|
77
|
+
`Express 5.x + Dotenv 17.x + CORS ready to go`,
|
|
78
|
+
]);
|
|
79
|
+
|
|
80
|
+
showNextSteps([
|
|
81
|
+
`cd ${projectName}`,
|
|
82
|
+
"npm run dev",
|
|
83
|
+
"Open http://localhost:3000",
|
|
84
|
+
]);
|
|
61
85
|
}
|
|
62
86
|
|
|
63
|
-
function
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
87
|
+
function runCommand(script, args = []) {
|
|
88
|
+
execFileSync("node", [path.join(__dirname, script), ...args], {
|
|
89
|
+
stdio: "inherit",
|
|
90
|
+
});
|
|
67
91
|
}
|
|
68
92
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
93
|
+
// ── Add menu options ────────────────────────────────
|
|
94
|
+
|
|
95
|
+
const addOptions = [
|
|
96
|
+
{
|
|
97
|
+
name: "API Endpoint Create a new route",
|
|
98
|
+
value: "api",
|
|
99
|
+
script: "add-api.js",
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: "MongoDB Setup database connection",
|
|
103
|
+
value: "mongo",
|
|
104
|
+
script: "add-mongo.js",
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
name: "CRUD Add create/read/update/delete routes",
|
|
108
|
+
value: "crud",
|
|
109
|
+
script: "add-crud.js",
|
|
110
|
+
requiresRoutes: true,
|
|
111
|
+
requiresModels: true,
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
name: "Login & Auth Add JWT authentication",
|
|
115
|
+
value: "login",
|
|
116
|
+
script: "add-login.js",
|
|
117
|
+
requiresRoutes: true,
|
|
118
|
+
requiresModels: true,
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
name: "Socket.IO Real-time with Socket.IO",
|
|
122
|
+
value: "socket",
|
|
123
|
+
script: "add-socket.js",
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
name: "WebSocket Native WebSocket support",
|
|
127
|
+
value: "websocket",
|
|
128
|
+
script: "add-websocket.js",
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
name: "SerialPort Serial port communication",
|
|
132
|
+
value: "serialport",
|
|
133
|
+
script: "add-serialport.js",
|
|
134
|
+
},
|
|
135
|
+
];
|
|
136
|
+
|
|
137
|
+
// ── List ────────────────────────────────────────────
|
|
138
|
+
|
|
139
|
+
function showList(filter) {
|
|
140
|
+
showHeader("list", "Project overview");
|
|
141
|
+
|
|
142
|
+
const c = chalk.hex("#00c6ff");
|
|
143
|
+
const g = chalk.hex("#22c55e");
|
|
144
|
+
const y = chalk.hex("#ffd200");
|
|
145
|
+
const d = chalk.gray;
|
|
146
|
+
|
|
147
|
+
const showModels = !filter || filter === "models";
|
|
148
|
+
const showRoutes = !filter || filter === "routes";
|
|
149
|
+
|
|
150
|
+
if (showModels) {
|
|
151
|
+
const models = listModels();
|
|
152
|
+
console.log(y.bold(" Models"));
|
|
153
|
+
console.log(d(" ─────────────────────────────────"));
|
|
154
|
+
if (models.length === 0) {
|
|
155
|
+
console.log(d(" No models found.\n"));
|
|
156
|
+
} else {
|
|
157
|
+
models.forEach((file) => {
|
|
158
|
+
const name = path.basename(file, ".js");
|
|
159
|
+
const fields = getModelSchemaDetailed(name);
|
|
160
|
+
const fieldNames = fields.map((f) => {
|
|
161
|
+
let label = f.name;
|
|
162
|
+
if (f.required) label = g(label);
|
|
163
|
+
if (f.unique) label = `${label}${y("*")}`;
|
|
164
|
+
return label;
|
|
165
|
+
});
|
|
166
|
+
console.log(` ${c(name)} ${d("→")} ${fieldNames.join(d(", "))}`);
|
|
167
|
+
});
|
|
168
|
+
console.log();
|
|
169
|
+
console.log(d(` ${g("green")} = required ${y("*")} = unique`));
|
|
170
|
+
console.log();
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (showRoutes) {
|
|
175
|
+
const routes = listRoutes();
|
|
176
|
+
console.log(y.bold(" Routes"));
|
|
177
|
+
console.log(d(" ─────────────────────────────────"));
|
|
178
|
+
if (routes.length === 0) {
|
|
179
|
+
console.log(d(" No routes found.\n"));
|
|
180
|
+
} else {
|
|
181
|
+
routes.forEach((file) => {
|
|
182
|
+
const name = path.basename(file, ".js");
|
|
183
|
+
console.log(` ${c(name)} ${d(`→ src/routes/${file}`)}`);
|
|
184
|
+
});
|
|
185
|
+
console.log();
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Show config status
|
|
190
|
+
if (!filter) {
|
|
191
|
+
console.log(y.bold(" Config"));
|
|
192
|
+
console.log(d(" ─────────────────────────────────"));
|
|
193
|
+
console.log(` MongoDB ${checkMongoConfig() ? g("✔ configured") : d("✖ not configured")}`);
|
|
194
|
+
console.log(` Routes dir ${checkRoutesDir() ? g("✔ exists") : d("✖ not found")}`);
|
|
195
|
+
console.log();
|
|
196
|
+
}
|
|
76
197
|
}
|
|
77
198
|
|
|
199
|
+
// ── Help ────────────────────────────────────────────
|
|
200
|
+
|
|
78
201
|
function showHelp() {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
)}
|
|
87
|
-
${
|
|
88
|
-
|
|
89
|
-
)}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
)}
|
|
94
|
-
${
|
|
95
|
-
|
|
96
|
-
)}
|
|
97
|
-
${
|
|
98
|
-
|
|
99
|
-
)
|
|
100
|
-
|
|
101
|
-
${
|
|
102
|
-
|
|
103
|
-
${chalk.blue("add-mongo-read")} Add a read route for MongoDB.
|
|
104
|
-
${chalk.blue("add-mongo-delete")} Add a delete route for MongoDB.
|
|
105
|
-
|
|
106
|
-
Options:
|
|
107
|
-
${chalk.blue("-h, --help")} Show this help message.
|
|
108
|
-
`)
|
|
109
|
-
);
|
|
202
|
+
showBanner();
|
|
203
|
+
const c = chalk.hex("#00c6ff");
|
|
204
|
+
const d = chalk.gray;
|
|
205
|
+
const h = chalk.hex("#ffd200").bold;
|
|
206
|
+
|
|
207
|
+
console.log(h(" COMMANDS\n"));
|
|
208
|
+
console.log(` ${c("init")} ${d("[name]")} Create a new Node.js project`);
|
|
209
|
+
console.log(` ${c("add")} ${d("[feature]")} Add a feature (interactive menu)`);
|
|
210
|
+
console.log(` ${c("schema")} ${d("[name]")} Manage schemas (create/view/edit)`);
|
|
211
|
+
console.log(` ${c("list")} ${d("[filter]")} List models, routes & config status`);
|
|
212
|
+
console.log(` ${c("ui")} Start dev server with visual dashboard`);
|
|
213
|
+
console.log();
|
|
214
|
+
console.log(h(" ADD FEATURES\n"));
|
|
215
|
+
console.log(` ${c("add api")} ${d("[name]")} API endpoint`);
|
|
216
|
+
console.log(` ${c("add mongo")} MongoDB connection`);
|
|
217
|
+
console.log(` ${c("add crud")} CRUD routes (create/read/update/delete)`);
|
|
218
|
+
console.log(` ${c("add login")} JWT authentication & login`);
|
|
219
|
+
console.log(` ${c("add socket")} Socket.IO integration`);
|
|
220
|
+
console.log(` ${c("add websocket")} Native WebSocket support`);
|
|
221
|
+
console.log(` ${c("add serialport")} Serial port communication`);
|
|
222
|
+
console.log();
|
|
223
|
+
console.log(h(" OPTIONS\n"));
|
|
224
|
+
console.log(` ${c("-h, --help")} Show this help message`);
|
|
225
|
+
console.log();
|
|
110
226
|
}
|
|
111
227
|
|
|
228
|
+
// ── Main ────────────────────────────────────────────
|
|
229
|
+
|
|
112
230
|
const args = process.argv.slice(2);
|
|
113
231
|
if (args.length < 1 || args.includes("-h") || args.includes("--help")) {
|
|
114
232
|
showHelp();
|
|
@@ -116,111 +234,126 @@ if (args.length < 1 || args.includes("-h") || args.includes("--help")) {
|
|
|
116
234
|
}
|
|
117
235
|
|
|
118
236
|
const command = args[0];
|
|
119
|
-
const
|
|
237
|
+
const subCommand = args[1];
|
|
238
|
+
const extraParam = args[2];
|
|
120
239
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
} else if (command === "add-api") {
|
|
128
|
-
if (!param) {
|
|
129
|
-
console.error(chalk.red("Usage: node-app add-api <endpointName>"));
|
|
130
|
-
process.exit(1);
|
|
131
|
-
}
|
|
132
|
-
execSync(`node ${path.join(__dirname, "add-api.js")} ${param}`, {
|
|
133
|
-
stdio: "inherit",
|
|
134
|
-
});
|
|
135
|
-
} else if (command === "add-mongo") {
|
|
136
|
-
execSync(`node ${path.join(__dirname, "add-mongo.js")}`, {
|
|
137
|
-
stdio: "inherit",
|
|
138
|
-
});
|
|
139
|
-
} else if (command === "add-socket") {
|
|
140
|
-
execSync(`node ${path.join(__dirname, "add-socket.js")}`, {
|
|
141
|
-
stdio: "inherit",
|
|
142
|
-
});
|
|
143
|
-
} else if (command === "add-websocket") {
|
|
144
|
-
execSync(`node ${path.join(__dirname, "add-websocket.js")}`, {
|
|
145
|
-
stdio: "inherit",
|
|
240
|
+
async function promptName(label) {
|
|
241
|
+
return input({
|
|
242
|
+
message: `Enter ${label}:`,
|
|
243
|
+
validate: (v) => {
|
|
244
|
+
try { sanitizeName(v); return true; } catch (e) { return e.message; }
|
|
245
|
+
},
|
|
146
246
|
});
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
execSync(`node ${path.join(__dirname, "add-mongo-schema.js")} ${param}`, {
|
|
161
|
-
stdio: "inherit",
|
|
162
|
-
});
|
|
163
|
-
} else if (command === "add-login") {
|
|
164
|
-
if (!checkRoutesDir()) {
|
|
165
|
-
console.error(chalk.red("No routes directory found in src/routes"));
|
|
166
|
-
process.exit(1);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
async function handleAdd(featureOverride, paramOverride) {
|
|
250
|
+
let feature = featureOverride || subCommand;
|
|
251
|
+
const param = paramOverride || extraParam;
|
|
252
|
+
|
|
253
|
+
if (!feature) {
|
|
254
|
+
showHeader("add", "What would you like to add?");
|
|
255
|
+
const choice = await select({
|
|
256
|
+
message: "Select a feature:",
|
|
257
|
+
choices: addOptions.map((o) => ({ name: o.name, value: o.value })),
|
|
258
|
+
});
|
|
259
|
+
feature = choice;
|
|
167
260
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
console.error(chalk.red("No routes directory found in src/routes"));
|
|
261
|
+
|
|
262
|
+
// Map feature to option
|
|
263
|
+
const option = addOptions.find((o) => o.value === feature);
|
|
264
|
+
if (!option) {
|
|
265
|
+
showError(`Unknown feature "${feature}". Run 4bnode add to see options.`);
|
|
174
266
|
process.exit(1);
|
|
175
267
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
268
|
+
|
|
269
|
+
// Pre-flight checks with helpful guidance
|
|
270
|
+
const missing = [];
|
|
271
|
+
if (option.requiresMongo && !checkMongoConfig()) {
|
|
272
|
+
missing.push(" 1. 4bnode add mongo Setup database first");
|
|
179
273
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
});
|
|
183
|
-
} else if (command === "add-mongo-update") {
|
|
184
|
-
if (!checkRoutesDir()) {
|
|
185
|
-
console.error(chalk.red("No routes directory found in src/routes"));
|
|
186
|
-
process.exit(1);
|
|
274
|
+
if (option.requiresRoutes && !checkRoutesDir()) {
|
|
275
|
+
missing.push(` ${missing.length + 1}. 4bnode add api <name> Create a route first`);
|
|
187
276
|
}
|
|
188
|
-
if (!checkModelsExist()) {
|
|
189
|
-
|
|
190
|
-
process.exit(1);
|
|
277
|
+
if (option.requiresModels && !checkModelsExist()) {
|
|
278
|
+
missing.push(` ${missing.length + 1}. 4bnode schema Create a model first`);
|
|
191
279
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
});
|
|
195
|
-
} else if (command === "add-mongo-read") {
|
|
196
|
-
if (!checkRoutesDir()) {
|
|
197
|
-
console.error(chalk.red("No routes directory found in src/routes"));
|
|
280
|
+
if (missing.length > 0) {
|
|
281
|
+
showError(`Cannot run "${feature}" yet. Run these first:\n\n${missing.join("\n")}`);
|
|
198
282
|
process.exit(1);
|
|
199
283
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
284
|
+
|
|
285
|
+
// Handle features that need a name param
|
|
286
|
+
if (feature === "api") {
|
|
287
|
+
const name = param || await promptName("endpoint name");
|
|
288
|
+
runCommand(option.script, [sanitizeName(name)]);
|
|
289
|
+
} else {
|
|
290
|
+
runCommand(option.script);
|
|
203
291
|
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
async function main() {
|
|
295
|
+
if (command === "init") {
|
|
296
|
+
const name = subCommand || await promptName("project name");
|
|
297
|
+
createSkeletonApp(sanitizeName(name));
|
|
298
|
+
} else if (command === "add") {
|
|
299
|
+
await handleAdd();
|
|
300
|
+
} else if (command === "schema") {
|
|
301
|
+
// Top-level schema manager: create / view / edit
|
|
302
|
+
if (subCommand) {
|
|
303
|
+
runCommand("add-mongo-schema.js", [subCommand]);
|
|
304
|
+
} else {
|
|
305
|
+
runCommand("add-mongo-schema.js");
|
|
306
|
+
}
|
|
307
|
+
} else if (command === "list") {
|
|
308
|
+
showList(subCommand);
|
|
309
|
+
} else if (command === "ui") {
|
|
310
|
+
// Must be inside a 4bnode project (has package.json + dev-api.js)
|
|
311
|
+
const projectRoot = process.cwd();
|
|
312
|
+
const hasPackageJson = fs.existsSync(path.join(projectRoot, "package.json"));
|
|
313
|
+
const hasDevApi = fs.existsSync(path.join(projectRoot, "dev-api.js"));
|
|
314
|
+
if (!hasPackageJson || !hasDevApi) {
|
|
315
|
+
showError("Not inside a 4bnode project. Run 'npx 4bnode init <name>' first, then cd into the project folder.");
|
|
316
|
+
process.exit(1);
|
|
317
|
+
}
|
|
318
|
+
console.log(chalk.hex("#00f260").bold("\n Starting dev server...\n"));
|
|
319
|
+
execFileSync("npm", ["run", "dev"], {
|
|
320
|
+
stdio: "inherit",
|
|
321
|
+
cwd: projectRoot,
|
|
322
|
+
});
|
|
211
323
|
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
324
|
+
// Legacy commands → route through handleAdd
|
|
325
|
+
else {
|
|
326
|
+
// Legacy: "add-mongo-schema" or "edit" → schema manager
|
|
327
|
+
if (command === "add-mongo-schema" || command === "edit") {
|
|
328
|
+
runCommand("add-mongo-schema.js", subCommand ? [command === "edit" ? "--edit" : "", subCommand].filter(Boolean) : (command === "edit" ? ["--edit"] : []));
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const legacyMap = {
|
|
333
|
+
"add-api": "api",
|
|
334
|
+
"add-mongo": "mongo",
|
|
335
|
+
"add-crud": "crud",
|
|
336
|
+
"add-login": "login",
|
|
337
|
+
"add-mongo-insert": "crud",
|
|
338
|
+
"add-mongo-update": "crud",
|
|
339
|
+
"add-mongo-read": "crud",
|
|
340
|
+
"add-mongo-delete": "crud",
|
|
341
|
+
"add-socket": "socket",
|
|
342
|
+
"add-websocket": "websocket",
|
|
343
|
+
"add-serialport": "serialport",
|
|
344
|
+
};
|
|
345
|
+
const mapped = legacyMap[command];
|
|
346
|
+
if (mapped) {
|
|
347
|
+
await handleAdd(mapped, subCommand);
|
|
348
|
+
} else {
|
|
349
|
+
showError(`Unknown command "${command}". Run 4bnode --help for usage.`);
|
|
350
|
+
process.exit(1);
|
|
351
|
+
}
|
|
215
352
|
}
|
|
216
|
-
execSync(`node ${path.join(__dirname, "add-mongo-delete.js")}`, {
|
|
217
|
-
stdio: "inherit",
|
|
218
|
-
});
|
|
219
|
-
} else if (command === "add-serialport") {
|
|
220
|
-
execSync(`node ${path.join(__dirname, "add-serialport.js")}`, {
|
|
221
|
-
stdio: "inherit",
|
|
222
|
-
});
|
|
223
|
-
} else {
|
|
224
|
-
console.error(chalk.red("Unknown command. Use -h or --help for help."));
|
|
225
|
-
process.exit(1);
|
|
226
353
|
}
|
|
354
|
+
|
|
355
|
+
main().catch((err) => {
|
|
356
|
+
if (err.name === "ExitPromptError") process.exit(0);
|
|
357
|
+
showError(err.message);
|
|
358
|
+
process.exit(1);
|
|
359
|
+
});
|
package/lib/env.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
|
|
3
|
+
export function updateEnvFile(filePath, key, value) {
|
|
4
|
+
const line = `${key}=${value}`;
|
|
5
|
+
|
|
6
|
+
if (fs.existsSync(filePath)) {
|
|
7
|
+
let content = fs.readFileSync(filePath, "utf8");
|
|
8
|
+
const regex = new RegExp(`^${key}=.*`, "m");
|
|
9
|
+
if (regex.test(content)) {
|
|
10
|
+
content = content.replace(regex, line);
|
|
11
|
+
} else {
|
|
12
|
+
content = content.trimEnd() + "\n" + line + "\n";
|
|
13
|
+
}
|
|
14
|
+
fs.writeFileSync(filePath, content, "utf8");
|
|
15
|
+
} else {
|
|
16
|
+
fs.writeFileSync(filePath, line + "\n", "utf8");
|
|
17
|
+
}
|
|
18
|
+
}
|
package/lib/indexFile.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { getProjectRoot } from "./project.js";
|
|
4
|
+
|
|
5
|
+
function getIndexPath() {
|
|
6
|
+
return path.join(getProjectRoot(), "index.js");
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function readIndex() {
|
|
10
|
+
return fs.readFileSync(getIndexPath(), "utf8");
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function writeIndex(content) {
|
|
14
|
+
fs.writeFileSync(getIndexPath(), content, "utf8");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function addImport(importStatement) {
|
|
18
|
+
let content = readIndex();
|
|
19
|
+
if (!content.includes(importStatement.trim())) {
|
|
20
|
+
content = importStatement.trim() + "\n" + content;
|
|
21
|
+
writeIndex(content);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function addRouteRegistration(importLine, useLine) {
|
|
26
|
+
let content = readIndex();
|
|
27
|
+
|
|
28
|
+
if (!content.includes(importLine.trim())) {
|
|
29
|
+
content = importLine.trim() + "\n" + content;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!content.includes(useLine.trim())) {
|
|
33
|
+
if (content.includes("app.listen(port")) {
|
|
34
|
+
content = content.replace(
|
|
35
|
+
"app.listen(port",
|
|
36
|
+
useLine.trim() + "\n" + "app.listen(port"
|
|
37
|
+
);
|
|
38
|
+
} else if (content.includes("server.listen(port")) {
|
|
39
|
+
content = content.replace(
|
|
40
|
+
"server.listen(port",
|
|
41
|
+
useLine.trim() + "\n" + "server.listen(port"
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
writeIndex(content);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function insertBeforeListen(codeBlock) {
|
|
50
|
+
let content = readIndex();
|
|
51
|
+
const listenMatch = content.match(/(app|server)\.listen\(port/);
|
|
52
|
+
if (listenMatch) {
|
|
53
|
+
content = content.replace(
|
|
54
|
+
listenMatch[0],
|
|
55
|
+
codeBlock.trim() + "\n\n" + listenMatch[0]
|
|
56
|
+
);
|
|
57
|
+
} else {
|
|
58
|
+
content += "\n" + codeBlock.trim() + "\n";
|
|
59
|
+
}
|
|
60
|
+
writeIndex(content);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function replaceAppListen(newListenBlock) {
|
|
64
|
+
let content = readIndex();
|
|
65
|
+
content = content.replace(/app\.listen\(port[\s\S]*?\}\);?\n?/m, "");
|
|
66
|
+
if (!content.includes("server.listen(port")) {
|
|
67
|
+
content += "\n" + newListenBlock.trim() + "\n";
|
|
68
|
+
}
|
|
69
|
+
writeIndex(content);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function insertBeforeRoutes(codeBlock) {
|
|
73
|
+
let content = readIndex();
|
|
74
|
+
const routesStart = content.indexOf("app.use(");
|
|
75
|
+
if (routesStart !== -1) {
|
|
76
|
+
content =
|
|
77
|
+
content.slice(0, routesStart) +
|
|
78
|
+
codeBlock.trim() +
|
|
79
|
+
"\n\n" +
|
|
80
|
+
content.slice(routesStart);
|
|
81
|
+
} else {
|
|
82
|
+
content += "\n" + codeBlock.trim() + "\n";
|
|
83
|
+
}
|
|
84
|
+
writeIndex(content);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export { readIndex, writeIndex, getIndexPath };
|
package/lib/names.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export function toCamelCase(str) {
|
|
2
|
+
return str.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function toPascalCase(str) {
|
|
6
|
+
const camel = toCamelCase(str);
|
|
7
|
+
return camel.charAt(0).toUpperCase() + camel.slice(1);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function sanitizeName(input) {
|
|
11
|
+
if (!input || typeof input !== "string") {
|
|
12
|
+
throw new Error("Name is required.");
|
|
13
|
+
}
|
|
14
|
+
const trimmed = input.trim();
|
|
15
|
+
if (!/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(trimmed)) {
|
|
16
|
+
throw new Error(
|
|
17
|
+
`Invalid name "${trimmed}". Use only letters, numbers, hyphens, and underscores. Must start with a letter.`
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
return trimmed;
|
|
21
|
+
}
|