4bnode
Version:
4bnode is a CLI-powered backend development platform with a built-in visual dashboard to generate, manage, and test Node.js/Express APIs faster.
90 lines (77 loc) • 3.18 kB
JavaScript
import fs from "fs";
import path from "path";
import { input } from "@inquirer/prompts";
import { getProjectRoot } from "./lib/project.js";
import { updateEnvFile } from "./lib/env.js";
import { readIndex, writeIndex } from "./lib/indexFile.js";
import {
showHeader,
showTaskDone,
showError,
showInfo,
showFileAction,
installDeps,
} from "./lib/ui.js";
import { buildDiscoveryModule, wireDiscoveryIntoIndex } from "./lib/codegen.js";
async function main() {
showHeader("add-bonjour", "Advertise this app on the local network (Bonjour / mDNS)");
showInfo(
"Other devices on the same Wi-Fi/LAN can then reach this app by name " +
"(http://<host>.local:<port>) — no hard-coded IPs. Works with Apple Bonjour, " +
"Android/Windows/Linux mDNS, and the bonsoir Flutter plugin.",
);
const root = getProjectRoot();
const envPath = path.join(root, ".env");
const discoveryPath = path.join(root, "src", "discovery.js");
const name = (
await input({ message: "Service name (blank = app name + hostname):", default: "" })
).trim();
const type = (
await input({ message: "Service type (without the leading underscore):", default: "http" })
).trim() || "http";
console.log();
// 1. Write the discovery module from the single source of truth (codegen). It's
// deterministic, so re-running just refreshes the file in place.
fs.mkdirSync(path.dirname(discoveryPath), { recursive: true });
const existed = fs.existsSync(discoveryPath);
fs.writeFileSync(discoveryPath, buildDiscoveryModule(), "utf8");
showFileAction(existed ? "updated" : "created", "src/discovery.js");
// 2. Wire it into index.js (idempotent — a no-op if already wired).
try {
const before = readIndex();
const after = wireDiscoveryIntoIndex(before);
if (after !== before) {
writeIndex(after);
showFileAction("updated", "index.js");
} else {
showInfo("index.js is already wired for discovery — leaving it as is.");
}
} catch (e) {
showInfo(
"Could not auto-wire index.js (" +
e.message +
"). Import { startAdvertising, destroyDiscovery } from './src/discovery.js' " +
"and call startAdvertising({ port }) after your server starts listening.",
);
}
// 3. Configuration in .env.
updateEnvFile(envPath, "BONJOUR_ENABLED", "on");
if (name) updateEnvFile(envPath, "BONJOUR_NAME", name);
if (type && type !== "http") updateEnvFile(envPath, "BONJOUR_TYPE", type);
showFileAction("updated", ".env");
// 4. Dependency (records it in package.json even if the install is offline).
await installDeps("bonjour-service");
showTaskDone("Bonjour / mDNS discovery enabled", [
name ? `Service name: ${name}` : "Service name: app name + hostname",
`Service type: _${type}._tcp`,
"Advertises once the server is listening; sends goodbye packets on clean shutdown",
"Browse peers on the network from the /_dev dashboard → Discovery",
"Disable anytime with BONJOUR_ENABLED=off in .env",
]);
}
main().catch((err) => {
if (err.name === "ExitPromptError") process.exit(0);
showError(err.message);
process.exit(1);
});