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.
- package/README.md +144 -109
- 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 +341 -156
- package/lib/env.js +18 -0
- package/lib/indexFile.js +134 -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/.4bnode/assets/fonts/NotoSans-Bold.woff2 +0 -0
- package/skeleton/.4bnode/assets/fonts/NotoSans-Light.woff2 +0 -0
- package/skeleton/.4bnode/assets/fonts/NotoSans-Medium.woff2 +0 -0
- package/skeleton/.4bnode/assets/fonts/NotoSans-Regular.woff2 +0 -0
- package/skeleton/.4bnode/assets/fonts/NotoSans-SemiBold.woff2 +0 -0
- package/skeleton/.4bnode/assets/images/mongo.png +0 -0
- package/skeleton/.4bnode/assets/images/mysql.png +0 -0
- package/skeleton/.4bnode/assets/images/postgresql.png +0 -0
- package/skeleton/.4bnode/assets/images/sqlite.png +0 -0
- package/skeleton/.4bnode/assets/js/App.js +115 -0
- package/skeleton/.4bnode/assets/js/Root.js +44 -0
- package/skeleton/.4bnode/assets/js/components/Modal.js +52 -0
- package/skeleton/.4bnode/assets/js/components/Sidebar.js +68 -0
- package/skeleton/.4bnode/assets/js/components/ToastContainer.js +15 -0
- package/skeleton/.4bnode/assets/js/context.js +7 -0
- package/skeleton/.4bnode/assets/js/lib.js +83 -0
- package/skeleton/.4bnode/assets/js/main.js +5 -0
- package/skeleton/.4bnode/assets/js/pages/ApiTesterPage.js +1594 -0
- package/skeleton/.4bnode/assets/js/pages/DashboardPage.js +435 -0
- package/skeleton/.4bnode/assets/js/pages/DatabasePage.js +461 -0
- package/skeleton/.4bnode/assets/js/pages/EnvironmentPage.js +141 -0
- package/skeleton/.4bnode/assets/js/pages/RealtimePage.js +186 -0
- package/skeleton/.4bnode/assets/js/pages/RoutesPage.js +1656 -0
- package/skeleton/.4bnode/assets/js/pages/SchemasPage.js +901 -0
- package/skeleton/.4bnode/assets/js/pages/SerialPortPage.js +170 -0
- package/skeleton/.4bnode/assets/js/screens/PasskeyScreen.js +105 -0
- package/skeleton/.4bnode/assets/js/screens/SplashScreen.js +66 -0
- package/skeleton/.4bnode/dev-api.js +2930 -0
- package/skeleton/.4bnode/ui.html +1556 -0
- package/skeleton/index.js +70 -17
- package/skeleton/package.json +9 -5
- package/skeleton/public/index.html +288 -86
- 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,41 @@
|
|
|
2
2
|
|
|
3
3
|
import fs from "fs";
|
|
4
4
|
import path from "path";
|
|
5
|
-
import
|
|
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
|
|
15
|
-
|
|
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((
|
|
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
|
|
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
|
-
|
|
100
|
+
scaffoldSpinner.succeed(chalk.hex("#00f260").bold("Project scaffolded"));
|
|
51
101
|
|
|
52
|
-
//
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
118
|
+
showNextSteps([
|
|
119
|
+
`cd ${projectName}`,
|
|
120
|
+
"npm run dev",
|
|
121
|
+
"Open http://localhost:3000/_dev",
|
|
122
|
+
]);
|
|
61
123
|
}
|
|
62
124
|
|
|
63
|
-
function
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
125
|
+
function runCommand(script, args = []) {
|
|
126
|
+
execFileSync("node", [path.join(__dirname, script), ...args], {
|
|
127
|
+
stdio: "inherit",
|
|
128
|
+
});
|
|
67
129
|
}
|
|
68
130
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
)}
|
|
87
|
-
${
|
|
88
|
-
|
|
89
|
-
)}
|
|
90
|
-
${
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
)}
|
|
94
|
-
${
|
|
95
|
-
|
|
96
|
-
)}
|
|
97
|
-
${
|
|
98
|
-
|
|
99
|
-
)}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
${
|
|
103
|
-
|
|
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
|
|
276
|
+
const subCommand = args[1];
|
|
277
|
+
const extraParam = args[2];
|
|
120
278
|
|
|
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",
|
|
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
|
-
}
|
|
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);
|
|
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
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
-
|
|
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);
|
|
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
|
-
|
|
190
|
-
process.exit(1);
|
|
316
|
+
if (option.requiresModels && !checkModelsExist()) {
|
|
317
|
+
missing.push(` ${missing.length + 1}. 4bnode schema Create a model first`);
|
|
191
318
|
}
|
|
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"));
|
|
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
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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
|
+
}
|