Skip to content

Instantly share code, notes, and snippets.

@GuilhermeRossato
Last active November 2, 2021 20:57
Show Gist options
  • Save GuilhermeRossato/ee209d0d7d03144f6bd5340e399d9b9b to your computer and use it in GitHub Desktop.
Save GuilhermeRossato/ee209d0d7d03144f6bd5340e399d9b9b to your computer and use it in GitHub Desktop.
NodeJS Script to Serve Folder (either host local web server or run npm scripts) from the Context Menu (right click on folder)

Serve Folder Script

A NodeJS script to serve a web server on a folder or run npm run start if it's available with a tutorial to add it to context menu so that you can right click into a folder and quickly execute the project on that folder.

Useful for configuring as a context menu option to either serve a local web server at that folder or run the default npm script.

If it detects a package.json with a start or a serve script, it will run the npm run start at that folder, otherwise it will start a local web server with the first html file it finds (or / for index.html if it exists).

Installation

This installation guide will help you to configure this script to run when you right click a folder (opening the context menu) and selecting a command to execute this script for that project.

All you need to do is run this script at the folder of your project, basically if you were at the terminal all you had to do was this:

cd <project_path> && node <index.js_path>/index.js

The instructions below will help you install it on Windows 10: (adapting to a different OS should be different but easy)

  1. Clone this into a folder you will not delete
  2. Attach script to directory context menu via registry editor:
    1. Press Win+R, type regedit and open Registry Editor.
    2. Navigate to \HKEY_CLASSES_ROOT\Directory\shell or \HKEY_CLASS_ROOT\Directory\shell or \HKEY_USERS\XXXXX\SOFTWARE\Classes\Directory\Background\shell depending on your windows version (whichever exists).
    3. Create a key called ServeFolderContextMenu and another inside called command, resulting in \HKEY_CLASSES_ROOT\Directory\shell\ServeFolderContextMenu\command.
    4. On the command key, set the default value to "C:\Program Files\nodejs\node.exe" "[PATH TO THIS REPOSITORY]\index.js" %V, replacing [PATH TO THIS REPOSITORY] accordingly.
    5. Create a new character set variable (REG_SZ) on the ServeFolderContextMenu with the name NoWorkingDirectory and no content inside.
    6. Change the default value of the ServeFolderContextMenu to the string you want to show up on context menu, such as Serve project or something.

The resulting registries should look like this:

image1 image2

Try to right click into a folder, if its not showing then restart your session by logging off and logging in again. If it still doesn't work then review step 2 as there are multiple places to configure this context menu option on windows and its something that windows loves to change.

  1. Log off and log back in (or restart the computer)
// @ts-check
// Script to either host a local web server or run npm scripts
const http = require("http");
const fs = require("fs");
const path = require("path");
const cp = require("child_process");
const folder = process.argv.length <= 2 ? process.cwd() : path.resolve(process.argv[2]);
console.log(folder);
if (!fs.existsSync(folder)) {
console.log("Error: Path does not exist");
process.exit(1);
}
if (fs.existsSync(folder + "/package.json")) {
let obj;
try {
obj = JSON.parse(fs.readFileSync(folder + "/package.json", "utf8"));
} catch (err) {
console.log("Could not interpret package json:");
console.log(err);
}
if (!obj) {
startLocalWebServer();
} else {
const npmArgs = ["run"];
if (obj.scripts && obj.scripts.start) {
npmArgs.push('start');
} else if (obj.scripts && obj.scripts.serve) {
npmArgs.push('serve');
}
if (npmArgs.length > 1) {
startNPMScript(npmArgs);
} else {
startLocalWebServer();
}
}
} else {
startLocalWebServer();
}
/**
* Returns a UTC-3 datetime string in the format "yyyy-mm-dd hh/mm/ss"
* @param {Date} date
*/
function getBrazilianDateTimeString(date = new Date()) {
date.setTime(date.getTime() - 3 * 60 * 60 * 1000);
return date.toISOString().replace("T", " ").split(".")[0];
}
function getLogPrefix() {
return "[" + getBrazilianDateTimeString() + "]";
}
function startLocalWebServer() {
const server = http.createServer(onRequestStart);
server.listen(8080, onStartListening);
function onStartListening() {
const htmlFiles = fs.readdirSync(folder).filter(file => file.endsWith(".html"));
let startScript = "start http://localhost:8080/";
if (htmlFiles.length > 0 && !htmlFiles.includes("index.html")) {
startScript = "start http://localhost:8080/"+htmlFiles[0];
}
cp.execSync(startScript);
console.log(getLogPrefix(), "Started web server at http://localhost:8080/");
}
function getMimeTypeFromExtension(extension = "txt") {
if (extension[0] === ".") {
extension = extension.substr(1);
}
return {
"aac": "audio/aac",
"abw": "application/x-abiword",
"arc": "application/x-freearc",
"avi": "video/x-msvideo",
"azw": "application/vnd.amazon.ebook",
"bin": "application/octet-stream",
"bmp": "image/bmp",
"bz": "application/x-bzip",
"bz2": "application/x-bzip2",
"cda": "application/x-cdf",
"csh": "application/x-csh",
"css": "text/css",
"csv": "text/csv",
"doc": "application/msword",
"docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"eot": "application/vnd.ms-fontobject",
"epub": "application/epub+zip",
"gz": "application/gzip",
"gif": "image/gif",
"htm": "text/html",
"html": "text/html",
"ico": "image/vnd.microsoft.icon",
"ics": "text/calendar",
"jar": "application/java-archive",
"jpeg": "image/jpeg",
"jpg": "image/jpeg",
"js": "text/javascript",
"json": "application/json",
"jsonld": "application/ld+json",
"mid": "audio/midi audio/x-midi",
"midi": "audio/midi audio/x-midi",
"mjs": "text/javascript",
"mp3": "audio/mpeg",
"mp4": "video/mp4",
"mpeg": "video/mpeg",
"mpkg": "application/vnd.apple.installer+xml",
"odp": "application/vnd.oasis.opendocument.presentation",
"ods": "application/vnd.oasis.opendocument.spreadsheet",
"odt": "application/vnd.oasis.opendocument.text",
"oga": "audio/ogg",
"ogv": "video/ogg",
"ogx": "application/ogg",
"opus": "audio/opus",
"otf": "font/otf",
"png": "image/png",
"pdf": "application/pdf",
"php": "application/x-httpd-php",
"ppt": "application/vnd.ms-powerpoint",
"pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
"rar": "application/vnd.rar",
"rtf": "application/rtf",
"sh": "application/x-sh",
"svg": "image/svg+xml",
"swf": "application/x-shockwave-flash",
"tar": "application/x-tar",
"tif": "image/tiff",
"tiff": "image/tiff",
"ts": "video/mp2t",
"ttf": "font/ttf",
"txt": "text/plain",
"vsd": "application/vnd.visio",
"wav": "audio/wav",
"weba": "audio/webm",
"webm": "video/webm",
"webp": "image/webp",
"woff": "font/woff",
"woff2": "font/woff2",
"xhtml": "application/xhtml+xml",
"xls": "application/vnd.ms-excel",
"xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"xml": "application/xml",
"xul": "application/vnd.mozilla.xul+xml",
"zip": "application/zip",
"3gp": "video/3gpp",
"3g2": "video/3gpp2",
"7z": "application/x-7z-compressed"
}[extension] || "application/octet-stream";
}
/**
* @param {http.IncomingMessage} req
* @param {http.ServerResponse} res
*/
function onRequestStart(req, res) {
console.log(getLogPrefix(), req.method + " request from", req.socket.address(), "for", req.url);
if (req.method !== "GET") {
res.writeHead(405, "Method Not Allowed");
return res.end();
}
const target = path.resolve(folder + req.url);
if (!fs.existsSync(target)) {
res.writeHead(404, "Not Found");
return res.end();
}
res.setHeader("Content-type", getMimeTypeFromExtension(path.extname(target) || ".html"));
if (fs.statSync(target).isDirectory()) {
if (fs.existsSync(target + "/index.html")) {
res.write(fs.readFileSync(target + "/index.html"), (err) => {
if (err) {
console.log(err);
}
res.end();
});
return;
} else {
res.writeHead(404, "Not Found");
return res.end();
}
}
res.write(fs.readFileSync(target), (err) => {
if (err) {
console.log(err);
}
res.end();
});
return;
}
}
function startNPMScript(npmArgs) {
const npmPath = "C:\\Program Files\\nodejs\\npm";
if (!fs.existsSync(npmPath)) {
console.log("Could not find npm at " + npmPath);
return process.exit(1);
}
const child = cp.spawn(
"\"" + npmPath + "\"",
npmArgs,
{ stdio: "inherit", cwd: folder, env: undefined, shell: true }
);
child.on("error", function(err) {
console.log(err);
process.exit(1);
});
child.on("close", function(code) {
process.exit(code);
});
}
// Single line version of the above script to put on windows terminal like the following: node -e "<code>"
const http = require('http');const fs = require('fs');const path = require('path');const cp = require('child_process');const folder = process.cwd();console.log(folder);if (!fs.existsSync(folder)) {console.log('Error: Path does not exist');process.exit(1);}if (fs.existsSync(folder + '/package.json')) {let obj;try {obj = JSON.parse(fs.readFileSync(folder + '/package.json', 'utf8'));} catch (err) {console.log('Could not interpret package json:');console.log(err);}if (!obj) {startLocalWebServer();} else {const npmArgs = ['run'];if (obj.scripts && obj.scripts.start) {npmArgs.push('start');} else if (obj.scripts && obj.scripts.serve) {npmArgs.push('serve');}if (npmArgs.length > 1) {startNPMScript(npmArgs);} else {startLocalWebServer();}}} else {startLocalWebServer();}function getBrazilianDateTimeString(date = new Date()) {date.setTime(date.getTime() - 3 * 60 * 60 * 1000);return date.toISOString().replace('T', ' ').split('.')[0];}function getLogPrefix() {return '[' + getBrazilianDateTimeString() + ']';}function startLocalWebServer() {const server = http.createServer(onRequestStart);server.listen(8080, onStartListening);function onStartListening() {const htmlFiles = fs.readdirSync(folder).filter(file => file.endsWith('.html'));let startScript = 'start http://localhost:8080/';if (htmlFiles.length > 0 && !htmlFiles.includes('index.html')) {startScript = 'start http://localhost:8080/'+htmlFiles[0];}cp.execSync(startScript);console.log(getLogPrefix(), 'Started web server at http://localhost:8080/');}function getMimeTypeFromExtension(extension = 'txt') {if (extension[0] === '.') {extension = extension.substr(1);}return {'aac': 'audio/aac','abw': 'application/x-abiword','arc': 'application/x-freearc','avi': 'video/x-msvideo','azw': 'application/vnd.amazon.ebook','bin': 'application/octet-stream','bmp': 'image/bmp','bz': 'application/x-bzip','bz2': 'application/x-bzip2','cda': 'application/x-cdf','csh': 'application/x-csh','css': 'text/css','csv': 'text/csv','doc': 'application/msword','docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document','eot': 'application/vnd.ms-fontobject','epub': 'application/epub+zip','gz': 'application/gzip','gif': 'image/gif','htm': 'text/html','html': 'text/html','ico': 'image/vnd.microsoft.icon','ics': 'text/calendar','jar': 'application/java-archive','jpeg': 'image/jpeg','jpg': 'image/jpeg','js': 'text/javascript','json': 'application/json','jsonld': 'application/ld+json','mid': 'audio/midi audio/x-midi','midi': 'audio/midi audio/x-midi','mjs': 'text/javascript','mp3': 'audio/mpeg','mp4': 'video/mp4','mpeg': 'video/mpeg','mpkg': 'application/vnd.apple.installer+xml','odp': 'application/vnd.oasis.opendocument.presentation','ods': 'application/vnd.oasis.opendocument.spreadsheet','odt': 'application/vnd.oasis.opendocument.text','oga': 'audio/ogg','ogv': 'video/ogg','ogx': 'application/ogg','opus': 'audio/opus','otf': 'font/otf','png': 'image/png','pdf': 'application/pdf','php': 'application/x-httpd-php','ppt': 'application/vnd.ms-powerpoint','pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation','rar': 'application/vnd.rar','rtf': 'application/rtf','sh': 'application/x-sh','svg': 'image/svg+xml','swf': 'application/x-shockwave-flash','tar': 'application/x-tar','tif': 'image/tiff','tiff': 'image/tiff','ts': 'video/mp2t','ttf': 'font/ttf','txt': 'text/plain','vsd': 'application/vnd.visio','wav': 'audio/wav','weba': 'audio/webm','webm': 'video/webm','webp': 'image/webp','woff': 'font/woff','woff2': 'font/woff2','xhtml': 'application/xhtml+xml','xls': 'application/vnd.ms-excel','xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet','xml': 'application/xml','xul': 'application/vnd.mozilla.xul+xml','zip': 'application/zip','3gp': 'video/3gpp','3g2': 'video/3gpp2','7z': 'application/x-7z-compressed'}[extension] || 'application/octet-stream';}function onRequestStart(req, res) {console.log(getLogPrefix(), req.method + ' request from', req.socket.address(), 'for', req.url);if (req.method !== 'GET') {res.writeHead(405, 'Method Not Allowed');return res.end();}const target = path.resolve(folder + req.url);if (!fs.existsSync(target)) {res.writeHead(404, 'Not Found');return res.end();}res.setHeader('Content-type', getMimeTypeFromExtension(path.extname(target) || '.html'));if (fs.statSync(target).isDirectory()) {if (fs.existsSync(target + '/index.html')) {res.write(fs.readFileSync(target + '/index.html'), (err) => {if (err) {console.log(err);}res.end();});return;} else {res.writeHead(404, 'Not Found');return res.end();}}res.write(fs.readFileSync(target), (err) => {if (err) {console.log(err);}res.end();});return;}}function startNPMScript(npmArgs) {const npmPath = 'C:/Program Files/nodejs/npm';if (!fs.existsSync(npmPath)) {console.log('Could not find npm at ' + npmPath);return process.exit(1);}const child = cp.spawn(String.fromCharCode(34) + npmPath + String.fromCharCode(34),npmArgs,{ stdio: 'inherit', cwd: folder, env: undefined, shell: true });child.on('error', function(err) {console.log(err);process.exit(1);});child.on('close', function(code) {process.exit(code);});}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment