4bnode 3.5.2 → 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.
Files changed (43) hide show
  1. package/README.md +139 -114
  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 +288 -155
  15. package/lib/env.js +18 -0
  16. package/lib/indexFile.js +87 -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/dev-api.js +2785 -0
  23. package/skeleton/index.js +10 -3
  24. package/skeleton/package.json +9 -5
  25. package/skeleton/public/fonts/NotoSans-Bold.woff2 +0 -0
  26. package/skeleton/public/fonts/NotoSans-Light.woff2 +0 -0
  27. package/skeleton/public/fonts/NotoSans-Medium.woff2 +0 -0
  28. package/skeleton/public/fonts/NotoSans-Regular.woff2 +0 -0
  29. package/skeleton/public/fonts/NotoSans-SemiBold.woff2 +0 -0
  30. package/skeleton/public/images/mongo.png +0 -0
  31. package/skeleton/public/images/mysql.png +0 -0
  32. package/skeleton/public/images/postgresql.png +0 -0
  33. package/skeleton/public/images/sqlite.png +0 -0
  34. package/skeleton/public/index.html +7562 -64
  35. package/README_4bnode.md +0 -197
  36. package/src/auth/passport.js +0 -21
  37. package/src/db.js +0 -13
  38. package/test-server/.env.development +0 -1
  39. package/test-server/.env.production +0 -1
  40. package/test-server/index.js +0 -82
  41. package/test-server/package-lock.json +0 -1267
  42. package/test-server/package.json +0 -20
  43. 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 { execSync } from "child_process";
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 exists = fs.existsSync(src);
15
- const stats = exists && fs.statSync(src);
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((childItemName) => {
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
- console.log(`Node.js skeleton app created successfully in ${projectName}.`);
68
+ scaffoldSpinner.succeed(chalk.hex("#00f260").bold("Project scaffolded"));
51
69
 
52
- // Install dependencies
53
- execSync("npm install", { stdio: "inherit", cwd: targetPath });
54
- console.log("Dependencies installed successfully.");
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
- 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;
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 checkRoutesDir() {
64
- const projectRoot = process.cwd();
65
- const routesDir = path.join(projectRoot, "src", "routes");
66
- return fs.existsSync(routesDir);
87
+ function runCommand(script, args = []) {
88
+ execFileSync("node", [path.join(__dirname, script), ...args], {
89
+ stdio: "inherit",
90
+ });
67
91
  }
68
92
 
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
- );
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
- 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
- );
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 param = args[1];
237
+ const subCommand = args[1];
238
+ const extraParam = args[2];
120
239
 
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",
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
- } 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);
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
- 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"));
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
- if (!checkModelsExist()) {
177
- console.error(chalk.red("No model files found in src/models"));
178
- process.exit(1);
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
- 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);
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
- console.error(chalk.red("No model files found in src/models"));
190
- process.exit(1);
277
+ if (option.requiresModels && !checkModelsExist()) {
278
+ missing.push(` ${missing.length + 1}. 4bnode schema Create a model first`);
191
279
  }
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"));
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
- if (!checkModelsExist()) {
201
- console.error(chalk.red("No model files found in src/models"));
202
- process.exit(1);
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
- 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);
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
- if (!checkModelsExist()) {
213
- console.error(chalk.red("No model files found in src/models"));
214
- process.exit(1);
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
+ }
@@ -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
+ }