UNPKG

@liascript/devserver

Version:
360 lines (359 loc) 19.3 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (g && (g = 0, op[0] && (_ = 0)), _) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; Object.defineProperty(exports, "__esModule", { value: true }); exports.init = init; exports.start = start; exports.stop = stop; exports.gotoLine = gotoLine; var express = require("express"); var fs = require("fs"); var path = require("path"); var cors = require('cors'); var handlebars = require('express-handlebars'); var ip = require('ip'); var open = require('open'); var bodyParser = require('body-parser'); var reload = require('reload'); var chokidar = require('chokidar'); var app = express(); app.use(bodyParser.json()); var dirname = ''; var node_modules; var liascriptPath = ''; var reloadInstance = null; var watcher = null; var clients = []; var gotoScript = "<script>\nif (!window.LIA) {\n window.LIA = {}\n}\n\nvar filename__ = document.location.search.replace(\"?\"+document.location.origin, \"\")\n\nwindow.LIA.lineGoto = function(linenumber) {\n fetch(\"/lineGoto\", {\n method: \"POST\",\n headers: {'Content-Type': 'application/json'}, \n body: JSON.stringify({\n \"linenumber\": linenumber,\n \"filename\": filename__\n })\n }).then(res => {\n console.log(\"Goto line\", linenumber);\n });\n}\n\nconst events = new EventSource('/gotoLine');\nevents.onmessage = (event) => {\n try {\n const data = JSON.parse(event.data);\n if (data.filename == filename__) {\n console.log(\"goto line:\", data.linenumber);\n window.LIA.gotoLine(data.linenumber)\n }\n } catch (e) {\n console.warn(\"gotoLine failed\")\n }\n};\n</script>"; var serverPointer; init(__dirname); function init(serverPath, nodeModulesPath) { dirname = serverPath || path.join(__dirname, '..'); node_modules = nodeModulesPath || path.join(dirname, 'node_modules'); liascriptPath = path.resolve(path.join(node_modules, '@liascript/editor/dist')); } function start(port, hostname, input, responsiveVoice, liveReload, openInBrowser, testOnline, gotoCallback) { return __awaiter(this, void 0, void 0, function () { var project, stats, watchPath, err_1, localURL, server; return __generator(this, function (_a) { switch (_a.label) { case 0: port = port || 3000; hostname = hostname || 'localhost'; openInBrowser = openInBrowser || false; input = input || '.'; liveReload = liveReload || false; testOnline = testOnline || false; project = { path: input, readme: undefined, }; if (input) { stats = fs.lstatSync(input); // Is it a directory? if (stats.isDirectory()) { project.path = input; } else if (stats.isFile()) { project.path = path.dirname(input); project.readme = path.basename(input); } } app.set('view engine', 'hbs'); app.engine('hbs', handlebars({ layoutsDir: path.resolve(path.join(dirname, 'views/layouts')), defaultLayout: 'main', extname: 'hbs', })); app.set('views', path.resolve(path.join(dirname, 'views'))); if (!liveReload) return [3 /*break*/, 4]; watchPath = path.join(project.path, project.readme || ''); console.log("\u2728 watching for changes on: \"".concat(watchPath, "\"")); _a.label = 1; case 1: _a.trys.push([1, 3, , 4]); return [4 /*yield*/, reload(app) // Watch the file for changes using chokidar (more reliable than fs.watch) ]; case 2: reloadInstance = _a.sent(); // Watch the file for changes using chokidar (more reliable than fs.watch) watcher = chokidar.watch(watchPath, { ignoreInitial: true, awaitWriteFinish: { stabilityThreshold: 100, pollInterval: 50, }, }); watcher.on('change', function (filePath) { console.log('📝 file changed, reloading...'); reloadInstance.reload(); }); watcher.on('error', function (error) { console.error('Watcher error:', error); }); return [3 /*break*/, 4]; case 3: err_1 = _a.sent(); console.error('Reload could not start:', err_1); return [3 /*break*/, 4]; case 4: app.get('/', function (req, res) { res.redirect('/home'); }); app.get('/gotoLine', eventsHandler); app.get('/home*', function (req, res) { var currentPath = project.path + '/' + req.params[0]; var stats = fs.lstatSync(currentPath); // Is it a directory? if (stats.isDirectory()) { var files = fs.readdirSync(currentPath).filter(function (e) { return e[0] !== '.'; }); var basePath = '/home'; var pathNames = req.params[0].split('/').filter(function (e) { return e !== ''; }); var paths = []; for (var i = 0; i < pathNames.length; i++) { basePath += '/' + pathNames[i]; paths.push({ name: pathNames[i], href: basePath }); } res.render('main', { layout: 'index', path: paths, file: files .map(function (file) { return { name: file, href: "http://".concat(hostname, ":").concat(port, "/home").concat(req.params[0], "/").concat(file), isDirectory: fs.lstatSync(currentPath + '/' + file).isDirectory(), }; }) .sort(function (a, b) { if (a.isDirectory && !b.isDirectory) { return -1; } else if (!a.isDirectory && b.isDirectory) { return 1; } else { if (a.name.toLocaleLowerCase() < b.name.toLocaleLowerCase()) { return -1; } else { return 1; } } return 0; }), }); } else if (stats.isFile()) { if (req.params[0].toLocaleLowerCase().endsWith('.md')) { if (testOnline) { res.redirect("https://LiaScript.github.io/course/?http://".concat(hostname, ":").concat(port, "/").concat(req.params[0])); } else { res.redirect("/liascript/index.html?http://".concat(hostname, ":").concat(port, "/").concat(req.params[0])); } } else { res.sendFile(req.params[0], { root: project.path }); } } else { res.send('ups, something went wrong'); } }); app.get('/liascript/', function (req, res) { res.redirect('/liascript/index.html'); }); app.get('/liascript/index.html', function (req, res) { // ------------------------------------ if (liveReload && responsiveVoice) { fs.readFile(liascriptPath + '/index.html', 'utf8', function (err, data) { if (err || !data) { res.status(500).send('index.html not found or could not be read'); return; } res.send(data.replace('</head>', "<script type='text/javascript' src='/reload/reload.js'></script>\n <script type='text/javascript' src='https://code.responsivevoice.org/responsivevoice.js?key=".concat(responsiveVoice, "'></script>\n ").concat(gotoScript, "\n </head>"))); }); } // ------------------------------------ else if (liveReload) { fs.readFile(liascriptPath + '/index.html', 'utf8', function (err, data) { if (err || !data) { res.status(500).send('index.html not found or could not be read'); return; } res.send(data.replace('</head>', "<script type='text/javascript' src='/reload/reload.js'></script>\n ".concat(gotoScript, "\n </head>"))); }); } // ------------------------------------ else if (responsiveVoice) { fs.readFile(liascriptPath + '/index.html', 'utf8', function (err, data) { if (err || !data) { res.status(500).send('index.html not found or could not be read'); return; } res.send(data.replace('</head>', "<script type='text/javascript' src='https://code.responsivevoice.org/responsivevoice.js?key=".concat(responsiveVoice, "'></script>\n ").concat(gotoScript, "\n </head>"))); }); } // ------------------------------------ else { fs.readFile(liascriptPath + '/index.html', 'utf8', function (err, data) { if (err || !data) { res.status(500).send('index.html not found or could not be read'); return; } res.send(data.replace('</head>', "".concat(gotoScript, "</head>"))); }); } }); // load everything from the liascript folder app.get('/liascript/*', function (req, res) { res.sendFile(req.params[0], { root: liascriptPath }, function (err) { if (err) { // Extract the file path by removing the '/liascript/' prefix var projectPath = req.params[0]; console.log("File not found in liascriptPath, trying project.path: ".concat(projectPath), project.path); res.sendFile(projectPath, { root: project.path }); } }); }); // ignore this one app.get('/sw.js', function (req, res) { }); app.get('/favicon.ico', function (req, res) { }); // react to click-events app.post('/lineGoto', function (req, res) { if (gotoCallback) { try { var linenumber = req.body.linenumber; var filename = req.body.filename; gotoCallback(linenumber, filename); } catch (e) { console.warn("lineGoto event with wrong datatype, you have to provide {'linenumber': int, 'filename': string}"); } } return res.json({}); }); // everything else comes from the current project folder app.use('/*', cors(), function (req, res, next) { // Skip reload and socket.io paths - let reload package handle them if (req.originalUrl.startsWith('/reload/') || req.originalUrl.startsWith('/socket.io')) { return next(); } res.sendFile(req.originalUrl, { root: project.path }); }); localURL = 'http://' + hostname + ':' + port; if (project.path && project.readme) { localURL += '/liascript/index.html?http://' + hostname + ':' + port + '/' + project.readme; } if (testOnline && project.readme) { localURL = 'https://LiaScript.github.io/course/?http://' + hostname + ':' + port + '/' + project.readme; } server = app.listen(port); server.on('error', function (e) { throw e; }); if (openInBrowser) { open(localURL); } console.log('📡 starting server'); console.log(" - local: ".concat(localURL)); console.log(" - on your network: ".concat(localURL.replace(hostname, ip.address()))); serverPointer = server; return [2 /*return*/]; } }); }); } function stop() { if (serverPointer) { serverPointer.close(); } if (watcher) { watcher.close(); watcher = null; } if (reloadInstance) { reloadInstance = null; } } function gotoLine(linenumber, filename) { clients.forEach(function (client) { return client.response.write("data: ".concat(JSON.stringify({ linenumber: linenumber, filename: filename, }), "\n\n")); }); } function eventsHandler(request, response, next) { var headers = { 'Content-Type': 'text/event-stream', Connection: 'keep-alive', 'Cache-Control': 'no-cache', }; response.writeHead(200, headers); var data = "data: \n\n"; response.write(data); var clientId = Date.now(); var newClient = { id: clientId, response: response, }; clients.push(newClient); request.on('close', function () { //console.log(`${clientId} Connection closed`) clients = clients.filter(function (client) { return client.id !== clientId; }); }); }