export function serveAsync()

in cli/server.ts [940:1269]


export function serveAsync(options: ServeOptions) {
    serveOptions = options;
    if (!serveOptions.port) serveOptions.port = 3232;
    if (!serveOptions.wsPort) serveOptions.wsPort = 3233;
    if (!serveOptions.hostname) serveOptions.hostname = "localhost";
    setupRootDir();
    const wsServerPromise = initSocketServer(serveOptions.wsPort, serveOptions.hostname);
    if (serveOptions.serial)
        initSerialMonitor();

    const server = http.createServer(async (req, res) => {
        const error = (code: number, msg: string = null) => {
            res.writeHead(code, { "Content-Type": "text/plain" })
            res.end(msg || "Error " + code)
        }

        const sendJson = (v: any) => {
            if (typeof v == "string") {
                res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf8' })
                res.end(v)
            } else {
                res.writeHead(200, { 'Content-Type': 'application/json; charset=utf8' })
                res.end(JSON.stringify(v))
            }
        }

        const sendHtml = (s: string, code = 200) => {
            res.writeHead(code, { 'Content-Type': 'text/html; charset=utf8' })
            res.end(s.replace(
                /(<img [^>]* src=")(?:\/docs|\.)\/static\/([^">]+)"/g,
                function (f, pref, addr) { return pref + '/static/' + addr + '"'; }
            ))
        }

        const sendFile = (filename: string) => {
            try {
                let stat = fs.statSync(filename);

                res.writeHead(200, {
                    'Content-Type': U.getMime(filename),
                    'Content-Length': stat.size
                });

                fs.createReadStream(filename).pipe(res);
            } catch (e) {
                error(404, "File missing: " + filename)
            }
        }

        let pathname = decodeURI(url.parse(req.url).pathname);
        const opts: pxt.Map<string | string[]> = querystring.parse(url.parse(req.url).query);
        const htmlParams: pxt.Map<string> = {};
        if (opts["lang"] || opts["forcelang"])
            htmlParams["locale"] = (opts["lang"] as string || opts["forcelang"] as string);

        if (pathname == "/") {
            res.writeHead(301, { location: '/index.html' })
            res.end()
            return
        }

        if (pathname == "/oauth-redirect") {
            res.writeHead(301, { location: '/oauth-redirect.html' })
            res.end()
            return
        }

        let elts = pathname.split("/").filter(s => !!s)
        if (elts.some(s => s[0] == ".")) {
            return error(400, "Bad path :-(\n")
        }

        if (elts[0] == "api") {
            if (elts[1] == "streams") {
                let trg = Cloud.apiRoot + req.url.slice(5)
                res.setHeader("Location", trg)
                error(302, "Redir: " + trg)
                return
            }

            if (elts[1] == "immreader") {
                let trg = Cloud.apiRoot + elts[1];
                res.setHeader("Location", trg)
                error(302, "Redir: " + trg)
                return
            }

            if (elts[1] == "store") {
                return await handleApiStoreRequestAsync(req, res, elts.slice(2));
            }

            if (/^\d\d\d[\d\-]*$/.test(elts[1]) && elts[2] == "js") {
                return compileScriptAsync(elts[1])
                    .then(data => {
                        res.writeHead(200, { 'Content-Type': 'application/javascript' })
                        res.end(data)
                    }, err => {
                        error(500)
                        console.log(err.stack)
                    })
            }

            if (!isAuthorizedLocalRequest(req)) {
                error(403);
                return null;
            }

            return handleApiAsync(req, res, elts)
                .then(sendJson, err => {
                    if (err.statusCode) {
                        error(err.statusCode, err.message || "");
                        console.log("Error " + err.statusCode);
                    }
                    else {
                        error(500)
                        console.log(err.stack)
                    }
                })
        }

        if (elts[0] == "icon") {
            const name = path.join(userProjectsDir, elts[1], "icon.jpeg");
            return existsAsync(name)
                .then(exists => exists ? sendFile(name) : error(404));
        }

        if (elts[0] == "assets") {
            if (/^[a-z0-9\-_]/.test(elts[1]) && !/[\/\\]/.test(elts[1]) && !/^[.]/.test(elts[2])) {
                let filename = path.join(userProjectsDir, elts[1], "assets", elts[2])
                if (nodeutil.fileExistsSync(filename)) {
                    return sendFile(filename)
                } else {
                    return error(404, "Asset not found")
                }
            } else {
                return error(400, "Invalid asset path")
            }
        }

        if (options.packaged) {
            let filename = path.resolve(path.join(packagedDir, pathname))
            if (nodeutil.fileExistsSync(filename)) {
                return sendFile(filename)
            } else {
                return error(404, "Packaged file not found")
            }
        }

        if (pathname.slice(0, pxt.appTarget.id.length + 2) == "/" + pxt.appTarget.id + "/") {
            res.writeHead(301, { location: req.url.slice(pxt.appTarget.id.length + 1) })
            res.end()
            return
        }

        let publicDir = path.join(nodeutil.pxtCoreDir, "webapp/public")

        if (pathname == "/--embed") {
            sendFile(path.join(publicDir, 'embed.js'));
            return
        }

        if (pathname == "/--run") {
            sendFile(path.join(publicDir, 'run.html'));
            return
        }

        if (pathname == "/--multi") {
            sendFile(path.join(publicDir, 'multi.html'));
            return
        }

        if (pathname == "/--asseteditor") {
            sendFile(path.join(publicDir, 'asseteditor.html'));
            return
        }

        if (pathname == "/--skillmap") {
            sendFile(path.join(publicDir, 'skillmap.html'));
            return
        }

        if (/\/-[-]*docs.*$/.test(pathname)) {
            sendFile(path.join(publicDir, 'docs.html'));
            return
        }

        if (pathname == "/--codeembed") {
            // http://localhost:3232/--codeembed#pub:20467-26471-70207-51013
            sendFile(path.join(publicDir, 'codeembed.html'));
            return
        }

        if (/^\/(\d\d\d\d[\d-]+)$/.test(pathname)) {
            scriptPageTestAsync(pathname.slice(1))
                .then(sendHtml)
            return
        }

        if (/^\/(pkg|package)\/.*$/.test(pathname)) {
            pkgPageTestAsync(pathname.replace(/^\/[^\/]+\//, ""))
                .then(sendHtml)
                .catch(() => error(404, "Packaged file not found"));
            return
        }

        if (elts[0] == "streams") {
            streamPageTestAsync(elts[0] + "/" + elts[1])
                .then(sendHtml)
            return
        }

        if (elts[0] == "certificates") {
            certificateTestAsync().then(sendHtml);
            return;
        }

        if (/\.js\.map$/.test(pathname)) {
            error(404, "map files disabled")
            return;
        }

        let dd = dirs
        let mm = /^\/(cdn|parts|sim|doccdn|blb)(\/.*)/.exec(pathname)
        if (mm) {
            pathname = mm[2]
        } else if (U.startsWith(pathname, "/docfiles/")) {
            pathname = pathname.slice(10)
            dd = docfilesdirs
        }
        for (let dir of dd) {
            let filename = path.resolve(path.join(dir, pathname))
            if (nodeutil.fileExistsSync(filename)) {
                if (/\.html$/.test(filename)) {
                    let html = expandHtml(fs.readFileSync(filename, "utf8"), htmlParams)
                    sendHtml(html)
                } else {
                    sendFile(filename)
                }
                return;
            }
        }

        if (/simulator\.html/.test(pathname)) {
            // Special handling for missing simulator: redirect to the live sim
            res.writeHead(302, { location: `https://trg-${pxt.appTarget.id}.userpxt.io/---simulator` });
            res.end();
            return;
        }

        // redirect
        let redirectFile = path.join(docsDir, pathname + "-ref.json");
        if (nodeutil.fileExistsSync(redirectFile)) {
            const redir = nodeutil.readJson(redirectFile);
            res.writeHead(301, { location: redir["redirect"] })
            res.end()
            return;
        }

        let webFile = path.join(docsDir, pathname)
        if (!nodeutil.fileExistsSync(webFile)) {
            if (nodeutil.fileExistsSync(webFile + ".html")) {
                webFile += ".html"
                pathname += ".html"
            } else {
                webFile = ""
            }
        }

        if (webFile) {
            if (/\.html$/.test(webFile)) {
                let html = expandHtml(fs.readFileSync(webFile, "utf8"), htmlParams)
                sendHtml(html)
            } else {
                sendFile(webFile)
            }
        } else {
            const m = /^\/(v\d+)(.*)/.exec(pathname);
            if (m) pathname = m[2];
            const lang = (opts["translate"] && ts.pxtc.Util.TRANSLATION_LOCALE)
                || opts["lang"] as string
                || opts["forcelang"] as string;
            readMdAsync(pathname, lang)
                .then(md => {
                    const mdopts = <pxt.docs.RenderOptions>{
                        template: expandDocFileTemplate("docs.html"),
                        markdown: md,
                        theme: pxt.appTarget.appTheme,
                        filepath: pathname,
                        TOC: resolveTOC(pathname),
                        pubinfo: {
                            locale: lang,
                            crowdinproject: pxt.appTarget.appTheme.crowdinProject
                        }
                    };
                    if (opts["translate"])
                        mdopts.pubinfo["incontexttranslations"] = "1";
                    const html = pxt.docs.renderMarkdown(mdopts)
                    sendHtml(html, U.startsWith(md, "# Not found") ? 404 : 200)
                });
        }
        return
    });

    // if user has a server.js file, require it
    const serverjs = path.resolve(path.join(root, 'built', 'server.js'))
    if (nodeutil.fileExistsSync(serverjs)) {
        console.log('loading ' + serverjs)
        require(serverjs);
    }

    const serverPromise = new Promise<void>((resolve, reject) => {
        server.on("error", reject);
        server.listen(serveOptions.port, serveOptions.hostname, () => resolve());
    });

    return Promise.all([wsServerPromise, serverPromise])
        .then(() => {
            const start = `http://${serveOptions.hostname}:${serveOptions.port}/#local_token=${options.localToken}&wsport=${serveOptions.wsPort}`;
            console.log(`---------------------------------------------`);
            console.log(``);
            console.log(`To launch the editor, open this URL:`);
            console.log(start);
            console.log(``);
            console.log(`---------------------------------------------`);

            if (options.autoStart) {
                nodeutil.openUrl(start, options.browser);
            }
        });
}