4bnode 3.5.3 → 4.0.2

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.
Files changed (62) hide show
  1. package/README.md +144 -109
  2. package/add-api.js +288 -156
  3. package/add-crud.js +226 -0
  4. package/add-login.js +152 -319
  5. package/add-mongo-delete.js +67 -138
  6. package/add-mongo-insert.js +87 -204
  7. package/add-mongo-read.js +100 -216
  8. package/add-mongo-schema.js +431 -199
  9. package/add-mongo-update.js +89 -202
  10. package/add-mongo.js +125 -218
  11. package/add-serialport.js +53 -98
  12. package/add-socket.js +43 -66
  13. package/add-websocket.js +68 -140
  14. package/index.js +341 -156
  15. package/lib/env.js +18 -0
  16. package/lib/indexFile.js +134 -0
  17. package/lib/names.js +21 -0
  18. package/lib/project.js +122 -0
  19. package/lib/routeFile.js +56 -0
  20. package/lib/ui.js +161 -0
  21. package/package.json +9 -16
  22. package/skeleton/.4bnode/assets/fonts/NotoSans-Bold.woff2 +0 -0
  23. package/skeleton/.4bnode/assets/fonts/NotoSans-Light.woff2 +0 -0
  24. package/skeleton/.4bnode/assets/fonts/NotoSans-Medium.woff2 +0 -0
  25. package/skeleton/.4bnode/assets/fonts/NotoSans-Regular.woff2 +0 -0
  26. package/skeleton/.4bnode/assets/fonts/NotoSans-SemiBold.woff2 +0 -0
  27. package/skeleton/.4bnode/assets/images/mongo.png +0 -0
  28. package/skeleton/.4bnode/assets/images/mysql.png +0 -0
  29. package/skeleton/.4bnode/assets/images/postgresql.png +0 -0
  30. package/skeleton/.4bnode/assets/images/sqlite.png +0 -0
  31. package/skeleton/.4bnode/assets/js/App.js +115 -0
  32. package/skeleton/.4bnode/assets/js/Root.js +44 -0
  33. package/skeleton/.4bnode/assets/js/components/Modal.js +52 -0
  34. package/skeleton/.4bnode/assets/js/components/Sidebar.js +68 -0
  35. package/skeleton/.4bnode/assets/js/components/ToastContainer.js +15 -0
  36. package/skeleton/.4bnode/assets/js/context.js +7 -0
  37. package/skeleton/.4bnode/assets/js/lib.js +83 -0
  38. package/skeleton/.4bnode/assets/js/main.js +5 -0
  39. package/skeleton/.4bnode/assets/js/pages/ApiTesterPage.js +1594 -0
  40. package/skeleton/.4bnode/assets/js/pages/DashboardPage.js +435 -0
  41. package/skeleton/.4bnode/assets/js/pages/DatabasePage.js +461 -0
  42. package/skeleton/.4bnode/assets/js/pages/EnvironmentPage.js +141 -0
  43. package/skeleton/.4bnode/assets/js/pages/RealtimePage.js +186 -0
  44. package/skeleton/.4bnode/assets/js/pages/RoutesPage.js +1656 -0
  45. package/skeleton/.4bnode/assets/js/pages/SchemasPage.js +901 -0
  46. package/skeleton/.4bnode/assets/js/pages/SerialPortPage.js +170 -0
  47. package/skeleton/.4bnode/assets/js/screens/PasskeyScreen.js +105 -0
  48. package/skeleton/.4bnode/assets/js/screens/SplashScreen.js +66 -0
  49. package/skeleton/.4bnode/dev-api.js +2930 -0
  50. package/skeleton/.4bnode/ui.html +1556 -0
  51. package/skeleton/index.js +70 -17
  52. package/skeleton/package.json +9 -5
  53. package/skeleton/public/index.html +288 -86
  54. package/README_4bnode.md +0 -197
  55. package/src/auth/passport.js +0 -21
  56. package/src/db.js +0 -13
  57. package/test-server/.env.development +0 -1
  58. package/test-server/.env.production +0 -1
  59. package/test-server/index.js +0 -82
  60. package/test-server/package-lock.json +0 -1267
  61. package/test-server/package.json +0 -20
  62. package/test-server/public/index.html +0 -90
package/index.js CHANGED
@@ -2,27 +2,41 @@
2
2
 
3
3
  import fs from "fs";
4
4
  import path from "path";
5
- import { execSync } from "child_process";
5
+ import crypto from "crypto";
6
+ import { execFileSync } from "child_process";
6
7
  import { fileURLToPath } from "url";
7
8
  import chalk from "chalk";
9
+ import { input, select, password } from "@inquirer/prompts";
10
+ import { sanitizeName } from "./lib/names.js";
11
+ import {
12
+ checkMongoConfig,
13
+ checkRoutesDir,
14
+ checkModelsExist,
15
+ listModels,
16
+ listRoutes,
17
+ getModelSchemaDetailed,
18
+ } from "./lib/project.js";
19
+ import {
20
+ showBanner,
21
+ createSpinner,
22
+ showSuccess,
23
+ showNextSteps,
24
+ showError,
25
+ showHeader,
26
+ } from "./lib/ui.js";
8
27
 
9
28
  const __filename = fileURLToPath(import.meta.url);
10
29
  const __dirname = path.dirname(__filename);
11
30
  const skeletonPath = path.join(__dirname, "skeleton");
12
31
 
13
32
  function copyRecursiveSync(src, dest) {
14
- const exists = fs.existsSync(src);
15
- const stats = exists && fs.statSync(src);
16
- const isDirectory = exists && stats.isDirectory();
17
- if (isDirectory) {
33
+ const stats = fs.statSync(src);
34
+ if (stats.isDirectory()) {
18
35
  if (!fs.existsSync(dest)) {
19
36
  fs.mkdirSync(dest);
20
37
  }
21
- fs.readdirSync(src).forEach((childItemName) => {
22
- copyRecursiveSync(
23
- path.join(src, childItemName),
24
- path.join(dest, childItemName)
25
- );
38
+ fs.readdirSync(src).forEach((child) => {
39
+ copyRecursiveSync(path.join(src, child), path.join(dest, child));
26
40
  });
27
41
  } else {
28
42
  fs.copyFileSync(src, dest);
@@ -40,75 +54,218 @@ function updatePackageName(destinationPath, projectName) {
40
54
  );
41
55
  }
42
56
 
43
- function createSkeletonApp(projectName) {
57
+ function hashPasskey(pin) {
58
+ return crypto.createHash("sha256").update(pin).digest("hex");
59
+ }
60
+
61
+ async function promptPasskey() {
62
+ const passkey = await password({
63
+ message: "Set a 6-digit passkey for the dev dashboard:",
64
+ mask: "*",
65
+ validate: (v) => {
66
+ if (!/^\d{6}$/.test(v)) return "Passkey must be exactly 6 digits";
67
+ return true;
68
+ },
69
+ });
70
+
71
+ const confirm = await password({
72
+ message: "Confirm passkey:",
73
+ mask: "*",
74
+ validate: (v) => {
75
+ if (v !== passkey) return "Passkeys do not match";
76
+ return true;
77
+ },
78
+ });
79
+
80
+ return passkey;
81
+ }
82
+
83
+ function savePasskey(projectPath, passkey) {
84
+ const hash = hashPasskey(passkey);
85
+ fs.writeFileSync(path.join(projectPath, ".4bnode", ".passkey"), hash, "utf8");
86
+ }
87
+
88
+ async function createSkeletonApp(projectName) {
89
+ showBanner();
90
+
44
91
  const targetPath = path.resolve(projectName);
92
+
93
+ const scaffoldSpinner = createSpinner("Scaffolding project structure...");
94
+ scaffoldSpinner.start();
45
95
  if (!fs.existsSync(targetPath)) {
46
96
  fs.mkdirSync(targetPath, { recursive: true });
47
97
  }
48
98
  copyRecursiveSync(skeletonPath, targetPath);
49
99
  updatePackageName(targetPath, projectName);
50
- console.log(`Node.js skeleton app created successfully in ${projectName}.`);
100
+ scaffoldSpinner.succeed(chalk.hex("#00f260").bold("Project scaffolded"));
51
101
 
52
- // Install dependencies
53
- execSync("npm install", { stdio: "inherit", cwd: targetPath });
54
- console.log("Dependencies installed successfully.");
55
- }
102
+ // Ask for dashboard passkey
103
+ console.log();
104
+ const passkey = await promptPasskey();
105
+ savePasskey(targetPath, passkey);
106
+ console.log(chalk.hex("#00f260").bold(" ✔ Dashboard passkey set"));
107
+
108
+ const installSpinner = createSpinner("Installing dependencies...");
109
+ installSpinner.start();
110
+ execFileSync("npm", ["install"], { stdio: "pipe", cwd: targetPath });
111
+ installSpinner.succeed(chalk.hex("#00f260").bold("Dependencies installed"));
112
+
113
+ showSuccess([
114
+ `Project ${chalk.hex("#00c6ff").bold(projectName)} created at ${chalk.gray(targetPath)}`,
115
+ `Express 5.x + Dotenv 17.x + CORS ready to go`,
116
+ ]);
56
117
 
57
- function checkModelsExist() {
58
- const projectRoot = process.cwd();
59
- const modelsDir = path.join(projectRoot, "src", "models");
60
- return fs.existsSync(modelsDir) && fs.readdirSync(modelsDir).length > 0;
118
+ showNextSteps([
119
+ `cd ${projectName}`,
120
+ "npm run dev",
121
+ "Open http://localhost:3000/_dev",
122
+ ]);
61
123
  }
62
124
 
63
- function checkRoutesDir() {
64
- const projectRoot = process.cwd();
65
- const routesDir = path.join(projectRoot, "src", "routes");
66
- return fs.existsSync(routesDir);
125
+ function runCommand(script, args = []) {
126
+ execFileSync("node", [path.join(__dirname, script), ...args], {
127
+ stdio: "inherit",
128
+ });
67
129
  }
68
130
 
69
- function checkMongoConfig() {
70
- const projectRoot = process.cwd();
71
- const envPath = path.join(projectRoot, ".env.development");
72
- return (
73
- fs.existsSync(envPath) &&
74
- fs.readFileSync(envPath, "utf8").includes("MONGO_URI")
75
- );
131
+ // ── Add menu options ────────────────────────────────
132
+
133
+ const addOptions = [
134
+ {
135
+ name: "API Endpoint Create a new route",
136
+ value: "api",
137
+ script: "add-api.js",
138
+ },
139
+ {
140
+ name: "MongoDB Setup database connection",
141
+ value: "mongo",
142
+ script: "add-mongo.js",
143
+ },
144
+ {
145
+ name: "CRUD Add create/read/update/delete routes",
146
+ value: "crud",
147
+ script: "add-crud.js",
148
+ requiresRoutes: true,
149
+ requiresModels: true,
150
+ },
151
+ {
152
+ name: "Login & Auth Add JWT authentication",
153
+ value: "login",
154
+ script: "add-login.js",
155
+ requiresRoutes: true,
156
+ requiresModels: true,
157
+ },
158
+ {
159
+ name: "Socket.IO Real-time with Socket.IO",
160
+ value: "socket",
161
+ script: "add-socket.js",
162
+ },
163
+ {
164
+ name: "WebSocket Native WebSocket support",
165
+ value: "websocket",
166
+ script: "add-websocket.js",
167
+ },
168
+ {
169
+ name: "SerialPort Serial port communication",
170
+ value: "serialport",
171
+ script: "add-serialport.js",
172
+ },
173
+ ];
174
+
175
+ // ── List ────────────────────────────────────────────
176
+
177
+ function showList(filter) {
178
+ showHeader("list", "Project overview");
179
+
180
+ const c = chalk.hex("#00c6ff");
181
+ const g = chalk.hex("#22c55e");
182
+ const y = chalk.hex("#ffd200");
183
+ const d = chalk.gray;
184
+
185
+ const showModels = !filter || filter === "models";
186
+ const showRoutes = !filter || filter === "routes";
187
+
188
+ if (showModels) {
189
+ const models = listModels();
190
+ console.log(y.bold(" Models"));
191
+ console.log(d(" ─────────────────────────────────"));
192
+ if (models.length === 0) {
193
+ console.log(d(" No models found.\n"));
194
+ } else {
195
+ models.forEach((file) => {
196
+ const name = path.basename(file, ".js");
197
+ const fields = getModelSchemaDetailed(name);
198
+ const fieldNames = fields.map((f) => {
199
+ let label = f.name;
200
+ if (f.required) label = g(label);
201
+ if (f.unique) label = `${label}${y("*")}`;
202
+ return label;
203
+ });
204
+ console.log(` ${c(name)} ${d("→")} ${fieldNames.join(d(", "))}`);
205
+ });
206
+ console.log();
207
+ console.log(d(` ${g("green")} = required ${y("*")} = unique`));
208
+ console.log();
209
+ }
210
+ }
211
+
212
+ if (showRoutes) {
213
+ const routes = listRoutes();
214
+ console.log(y.bold(" Routes"));
215
+ console.log(d(" ─────────────────────────────────"));
216
+ if (routes.length === 0) {
217
+ console.log(d(" No routes found.\n"));
218
+ } else {
219
+ routes.forEach((file) => {
220
+ const name = path.basename(file, ".js");
221
+ console.log(` ${c(name)} ${d(`→ src/routes/${file}`)}`);
222
+ });
223
+ console.log();
224
+ }
225
+ }
226
+
227
+ // Show config status
228
+ if (!filter) {
229
+ console.log(y.bold(" Config"));
230
+ console.log(d(" ─────────────────────────────────"));
231
+ console.log(` MongoDB ${checkMongoConfig() ? g("✔ configured") : d("✖ not configured")}`);
232
+ console.log(` Routes dir ${checkRoutesDir() ? g("✔ exists") : d("✖ not found")}`);
233
+ console.log();
234
+ }
76
235
  }
77
236
 
237
+ // ── Help ────────────────────────────────────────────
238
+
78
239
  function showHelp() {
79
- console.log(
80
- chalk.green(`
81
- Usage: ${chalk.bold("node-app <command> [options]")}
82
-
83
- Commands:
84
- ${chalk.blue(
85
- "init <projectName>"
86
- )} Create a new Node.js project with the given name.
87
- ${chalk.blue(
88
- "add-api <endpointName>"
89
- )} Add a new API endpoint with the given name.
90
- ${chalk.blue("add-mongo")} Configure MongoDB for the project.
91
- ${chalk.blue(
92
- "add-socket"
93
- )} Add Socket.IO support to the project.
94
- ${chalk.blue(
95
- "add-websocket"
96
- )} Add WebSocket support to the project.
97
- ${chalk.blue(
98
- "add-mongo-schema <schemaName>"
99
- )} Add a new MongoDB schema with the given name.
100
- ${chalk.blue("add-login")} Add a login route to the project.
101
- ${chalk.blue("add-mongo-insert")} Add an insert route for MongoDB.
102
- ${chalk.blue("add-mongo-update")} Add an update route for MongoDB.
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
- );
240
+ showBanner();
241
+ const c = chalk.hex("#00c6ff");
242
+ const d = chalk.gray;
243
+ const h = chalk.hex("#ffd200").bold;
244
+
245
+ console.log(h(" COMMANDS\n"));
246
+ console.log(` ${c("init")} ${d("[name]")} Create a new Node.js project`);
247
+ console.log(` ${c("add")} ${d("[feature]")} Add a feature (interactive menu)`);
248
+ console.log(` ${c("schema")} ${d("[name]")} Manage schemas (create/view/edit)`);
249
+ console.log(` ${c("list")} ${d("[filter]")} List models, routes & config status`);
250
+ console.log(` ${c("ui")} Start dev server with visual dashboard`);
251
+ console.log(` ${c("passkey")} Change dashboard passkey`);
252
+ console.log();
253
+ console.log(h(" ADD FEATURES\n"));
254
+ console.log(` ${c("add api")} ${d("[name]")} API endpoint`);
255
+ console.log(` ${c("add mongo")} MongoDB connection`);
256
+ console.log(` ${c("add crud")} CRUD routes (create/read/update/delete)`);
257
+ console.log(` ${c("add login")} JWT authentication & login`);
258
+ console.log(` ${c("add socket")} Socket.IO integration`);
259
+ console.log(` ${c("add websocket")} Native WebSocket support`);
260
+ console.log(` ${c("add serialport")} Serial port communication`);
261
+ console.log();
262
+ console.log(h(" OPTIONS\n"));
263
+ console.log(` ${c("-h, --help")} Show this help message`);
264
+ console.log();
110
265
  }
111
266
 
267
+ // ── Main ────────────────────────────────────────────
268
+
112
269
  const args = process.argv.slice(2);
113
270
  if (args.length < 1 || args.includes("-h") || args.includes("--help")) {
114
271
  showHelp();
@@ -116,111 +273,139 @@ if (args.length < 1 || args.includes("-h") || args.includes("--help")) {
116
273
  }
117
274
 
118
275
  const command = args[0];
119
- const param = args[1];
276
+ const subCommand = args[1];
277
+ const extraParam = args[2];
120
278
 
121
- if (command === "init") {
122
- if (!param) {
123
- console.error(chalk.red("Usage: node-app init <projectName>"));
124
- process.exit(1);
125
- }
126
- createSkeletonApp(param);
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",
279
+ async function promptName(label) {
280
+ return input({
281
+ message: `Enter ${label}:`,
282
+ validate: (v) => {
283
+ try { sanitizeName(v); return true; } catch (e) { return e.message; }
284
+ },
146
285
  });
147
- } else if (command === "add-mongo-schema") {
148
- if (!param) {
149
- console.error(chalk.red("Usage: node-app add-mongo-schema <schemaName>"));
150
- process.exit(1);
151
- }
152
- if (!checkMongoConfig()) {
153
- console.error(
154
- chalk.red(
155
- "MongoDB is not configured. Please run 'node-app add-mongo' first."
156
- )
157
- );
158
- process.exit(1);
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);
286
+ }
287
+
288
+ async function handleAdd(featureOverride, paramOverride) {
289
+ let feature = featureOverride || subCommand;
290
+ const param = paramOverride || extraParam;
291
+
292
+ if (!feature) {
293
+ showHeader("add", "What would you like to add?");
294
+ const choice = await select({
295
+ message: "Select a feature:",
296
+ choices: addOptions.map((o) => ({ name: o.name, value: o.value })),
297
+ });
298
+ feature = choice;
167
299
  }
168
- execSync(`node ${path.join(__dirname, "add-login.js")}`, {
169
- stdio: "inherit",
170
- });
171
- } else if (command === "add-mongo-insert") {
172
- if (!checkRoutesDir()) {
173
- console.error(chalk.red("No routes directory found in src/routes"));
300
+
301
+ // Map feature to option
302
+ const option = addOptions.find((o) => o.value === feature);
303
+ if (!option) {
304
+ showError(`Unknown feature "${feature}". Run 4bnode add to see options.`);
174
305
  process.exit(1);
175
306
  }
176
- if (!checkModelsExist()) {
177
- console.error(chalk.red("No model files found in src/models"));
178
- process.exit(1);
307
+
308
+ // Pre-flight checks with helpful guidance
309
+ const missing = [];
310
+ if (option.requiresMongo && !checkMongoConfig()) {
311
+ missing.push(" 1. 4bnode add mongo Setup database first");
179
312
  }
180
- execSync(`node ${path.join(__dirname, "add-mongo-insert.js")}`, {
181
- stdio: "inherit",
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);
313
+ if (option.requiresRoutes && !checkRoutesDir()) {
314
+ missing.push(` ${missing.length + 1}. 4bnode add api <name> Create a route first`);
187
315
  }
188
- if (!checkModelsExist()) {
189
- console.error(chalk.red("No model files found in src/models"));
190
- process.exit(1);
316
+ if (option.requiresModels && !checkModelsExist()) {
317
+ missing.push(` ${missing.length + 1}. 4bnode schema Create a model first`);
191
318
  }
192
- execSync(`node ${path.join(__dirname, "add-mongo-update.js")}`, {
193
- stdio: "inherit",
194
- });
195
- } else if (command === "add-mongo-read") {
196
- if (!checkRoutesDir()) {
197
- console.error(chalk.red("No routes directory found in src/routes"));
319
+ if (missing.length > 0) {
320
+ showError(`Cannot run "${feature}" yet. Run these first:\n\n${missing.join("\n")}`);
198
321
  process.exit(1);
199
322
  }
200
- if (!checkModelsExist()) {
201
- console.error(chalk.red("No model files found in src/models"));
202
- process.exit(1);
323
+
324
+ // Handle features that need a name param
325
+ if (feature === "api") {
326
+ const name = param || await promptName("endpoint name");
327
+ runCommand(option.script, [sanitizeName(name)]);
328
+ } else {
329
+ runCommand(option.script);
203
330
  }
204
- execSync(`node ${path.join(__dirname, "add-mongo-read.js")}`, {
205
- stdio: "inherit",
206
- });
207
- } else if (command === "add-mongo-delete") {
208
- if (!checkRoutesDir()) {
209
- console.error(chalk.red("No routes directory found in src/routes"));
210
- process.exit(1);
331
+ }
332
+
333
+ async function main() {
334
+ if (command === "init") {
335
+ const name = subCommand || await promptName("project name");
336
+ createSkeletonApp(sanitizeName(name));
337
+ } else if (command === "add") {
338
+ await handleAdd();
339
+ } else if (command === "schema") {
340
+ // Top-level schema manager: create / view / edit
341
+ if (subCommand) {
342
+ runCommand("add-mongo-schema.js", [subCommand]);
343
+ } else {
344
+ runCommand("add-mongo-schema.js");
345
+ }
346
+ } else if (command === "list") {
347
+ showList(subCommand);
348
+ } else if (command === "passkey") {
349
+ // Must be inside a 4bnode project
350
+ const projectRoot = process.cwd();
351
+ const passkeyFile = path.join(projectRoot, ".4bnode", ".passkey");
352
+ if (!fs.existsSync(path.join(projectRoot, ".4bnode"))) {
353
+ showError("Not inside a 4bnode project. Run 'npx 4bnode init <name>' first.");
354
+ process.exit(1);
355
+ }
356
+ showBanner();
357
+ console.log();
358
+ const newPasskey = await promptPasskey();
359
+ savePasskey(projectRoot, newPasskey);
360
+ console.log(chalk.hex("#00f260").bold("\n ✔ Dashboard passkey updated successfully\n"));
361
+ } else if (command === "ui") {
362
+ // Must be inside a 4bnode project (has package.json + .4bnode/)
363
+ const projectRoot = process.cwd();
364
+ const hasPackageJson = fs.existsSync(path.join(projectRoot, "package.json"));
365
+ const hasDevApi = fs.existsSync(path.join(projectRoot, ".4bnode", "dev-api.js")) || fs.existsSync(path.join(projectRoot, "dev-api.js"));
366
+ if (!hasPackageJson || !hasDevApi) {
367
+ showError("Not inside a 4bnode project. Run 'npx 4bnode init <name>' first, then cd into the project folder.");
368
+ process.exit(1);
369
+ }
370
+ console.log(chalk.hex("#00f260").bold("\n Starting dev server...\n"));
371
+ execFileSync("npm", ["run", "dev"], {
372
+ stdio: "inherit",
373
+ cwd: projectRoot,
374
+ });
211
375
  }
212
- if (!checkModelsExist()) {
213
- console.error(chalk.red("No model files found in src/models"));
214
- process.exit(1);
376
+ // Legacy commands → route through handleAdd
377
+ else {
378
+ // Legacy: "add-mongo-schema" or "edit" → schema manager
379
+ if (command === "add-mongo-schema" || command === "edit") {
380
+ runCommand("add-mongo-schema.js", subCommand ? [command === "edit" ? "--edit" : "", subCommand].filter(Boolean) : (command === "edit" ? ["--edit"] : []));
381
+ return;
382
+ }
383
+
384
+ const legacyMap = {
385
+ "add-api": "api",
386
+ "add-mongo": "mongo",
387
+ "add-crud": "crud",
388
+ "add-login": "login",
389
+ "add-mongo-insert": "crud",
390
+ "add-mongo-update": "crud",
391
+ "add-mongo-read": "crud",
392
+ "add-mongo-delete": "crud",
393
+ "add-socket": "socket",
394
+ "add-websocket": "websocket",
395
+ "add-serialport": "serialport",
396
+ };
397
+ const mapped = legacyMap[command];
398
+ if (mapped) {
399
+ await handleAdd(mapped, subCommand);
400
+ } else {
401
+ showError(`Unknown command "${command}". Run 4bnode --help for usage.`);
402
+ process.exit(1);
403
+ }
215
404
  }
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
405
  }
406
+
407
+ main().catch((err) => {
408
+ if (err.name === "ExitPromptError") process.exit(0);
409
+ showError(err.message);
410
+ process.exit(1);
411
+ });
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
+ }