UNPKG

makecode-core

Version:

MakeCode (PXT) - web-cached build tool

857 lines (856 loc) 32.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getAssetEditorHTML = exports.getWebConfig = exports.getSimHTML = exports.shareCommand = exports.addCommand = exports.stackCommand = exports.searchCommand = exports.getTargetConfigAsync = exports.getAppTargetAsync = exports.listHardwareVariantsAsync = exports.initCommand = exports.installCommand = exports.buildCommandOnce = exports.resolveProject = exports.clearProjectCache = exports.cleanCommand = exports.downloadCommand = exports.applyGlobalOptions = void 0; const path = require("path"); const chalk = require("chalk"); const xmldom_1 = require("@xmldom/xmldom"); const mkc = require("./mkc"); const files = require("./files"); const downloader = require("./downloader"); const loader_1 = require("./loader"); const mkc_1 = require("./mkc"); const stackresolver_1 = require("./stackresolver"); const files_1 = require("./files"); const host_1 = require("./host"); const share_1 = require("./share"); async function downloadProjectAsync(id) { id = id.replace(/.*\//, ""); const url = mkc.cloudRoot + id + "/text"; const files = await downloader.httpGetJsonAsync(url); for (let fn of Object.keys(files)) { if (/\//.test(fn)) continue; await (0, host_1.host)().writeFileAsync(fn, files[fn]); } msg("downloaded."); } async function buildOnePrj(opts, prj) { try { const simpleOpts = { native: opts.native, computeUsedParts: opts.buildSimJsInfo, emitBreakpoints: opts.emitBreakpoints }; const res = await prj.buildAsync(simpleOpts); const msgToString = (diagnostic) => { const category = diagnostic.category == 1 ? chalk.red("error") : diagnostic.category == 2 ? chalk.yellowBright("warning") : "message"; return `${category} TS${diagnostic.code}: ${diagnostic.messageText}\n`; }; let output = ""; for (let diagnostic of res.diagnostics) { let pref = ""; if (diagnostic.fileName) pref = `${diagnostic.fileName}(${diagnostic.line + 1},${diagnostic.column + 1}): `; if (typeof diagnostic.messageText == "string") output += pref + msgToString(diagnostic); else { for (let chain = diagnostic.messageText; chain; chain = chain.next) { output += pref + msgToString(chain); } } } if (output) console.log(output.replace(/\n$/, "")); return res; } catch (e) { error("Exception: " + e.stack); return null; } } function info(msg) { console.log(chalk.blueBright(msg)); } function msg(msg) { console.log(chalk.green(msg)); } function error(msg) { console.error(chalk.red(msg)); } function applyGlobalOptions(opts) { if (opts.noColors) chalk.level = 0; else if (opts.colors && !chalk.level) chalk.level = 1; else if ((0, host_1.host)().getEnvironmentVariable("GITHUB_WORKFLOW")) chalk.level = 1; } exports.applyGlobalOptions = applyGlobalOptions; async function downloadCommand(URL, opts) { applyGlobalOptions(opts); await downloadProjectAsync(URL); } exports.downloadCommand = downloadCommand; async function cleanCommand(opts) { applyGlobalOptions(opts); for (const dir of ["built", "pxt_modules"]) { if (await (0, host_1.host)().existsAsync(dir)) { msg(`deleting ${dir} folder`); await (0, host_1.host)().rmdirAsync(dir, { recursive: true, force: true }); } } msg("run `mkc init` again to setup your project"); } exports.cleanCommand = cleanCommand; let prjCache = {}; async function clearProjectCache() { var _a, _b; for (const prjdir of Object.keys(prjCache)) { const prj = prjCache[prjdir]; (_b = (_a = prj.service) === null || _a === void 0 ? void 0 : _a.dispose) === null || _b === void 0 ? void 0 : _b.call(_a); } prjCache = {}; } exports.clearProjectCache = clearProjectCache; function getCachedProject(prjdir) { if (!prjCache[prjdir]) { prjCache[prjdir] = new mkc.Project(prjdir); } return prjCache[prjdir]; } async function resolveProject(opts, quiet = false) { var _a; const prjdir = await files.findProjectDirAsync(); if (!prjdir) { error(`could not find "pxt.json" file`); (0, host_1.host)().exitWithStatus(1); } if (!opts.configPath) { const cfgFolder = await files.findParentDirWithAsync(prjdir, "mkc.json"); if (cfgFolder) opts.configPath = path.join(cfgFolder, "mkc.json"); } log(`using project: ${prjdir}/pxt.json`); const prj = getCachedProject(prjdir); if (opts.configPath) { log(`using config: ${opts.configPath}`); prj.mkcConfig = await readCfgAsync(opts.configPath, quiet); } await prj.loadEditorAsync(!!opts.update); let version = "???"; try { const appTarget = await prj.service.languageService.getAppTargetAsync(); version = (_a = appTarget === null || appTarget === void 0 ? void 0 : appTarget.versions) === null || _a === void 0 ? void 0 : _a.target; } catch (_b) { } log(`using editor: ${prj.mkcConfig.targetWebsite} v${version}`); if (opts.debug) await prj.service.languageService.enableDebugAsync(); if (opts.compileFlags) { await prj.service.languageService.setCompileSwitchesAsync(opts.compileFlags); } prj.writePxtModules = !!opts.pxtModules; if (opts.linkPxtModules) { prj.writePxtModules = true; prj.linkPxtModules = true; } else if (opts.symlinkPxtModules) { prj.writePxtModules = true; prj.symlinkPxtModules = true; } return prj; function log(msg) { if (!quiet) info(msg); } } exports.resolveProject = resolveProject; async function buildCommandOnce(opts) { const prj = await resolveProject(opts); await prj.service.languageService.enableExperimentalHardwareAsync(); const hwVariants = await prj.service.getHardwareVariantsAsync(); const appTarget = await prj.service.languageService.getAppTargetAsync(); const targetId = appTarget.id; let moreHw = []; const outputs = []; if (opts.buildSimJsInfo) { opts.javaScript = true; } if (opts.hw) { const hws = opts.hw.split(/[\s,;]+/); selectHW(hws[0]); moreHw = hws.slice(1); } if (!opts.javaScript || opts.hw) opts.native = true; else opts.native = false; if (opts.native && hwVariants.length) { await prj.guessHwVariantAsync(); infoHW(); } outputs.push(prj.outputPrefix); const compileRes = await buildOnePrj(opts, prj); const firmwareName = compileRes.success && ["binary.uf2", "binary.hex", "binary.elf"].filter(f => !!compileRes.outfiles[f])[0]; if (compileRes.success && opts.deploy) { if (!firmwareName) { // something went wrong here error(`firmware missing from built files (${Object.keys(compileRes.outfiles).join(", ")})`); } else { const compileInfo = appTarget.compile; const drives = await (0, host_1.host)().getDeployDrivesAsync(compileInfo); if (drives.length == 0) { msg("cannot find any drives to deploy to"); } else { const firmware = compileRes.outfiles[firmwareName]; const encoding = firmwareName == "binary.hex" ? "utf8" : "base64"; msg(`copying ${firmwareName} to ` + drives.join(", ")); const writeHexFile = (drivename) => { return (0, host_1.host)().writeFileAsync(path.join(drivename, firmwareName), firmware, encoding) .then(() => info(" wrote to " + drivename)) .catch(() => error(` failed writing to ${drivename}`)); }; for (const p of drives.map(writeHexFile)) await p; } } } let success = compileRes.success; if (success && opts.monoRepo) { const dirs = await (0, files_1.monoRepoConfigsAsync)("."); info(`mono-repo: building ${dirs.length} projects`); for (const fullpxtjson of dirs) { if (fullpxtjson.startsWith("pxt_modules")) continue; const fulldir = path.dirname(fullpxtjson); info(`build ${fulldir}`); const prj0 = await prj.mkChildProjectAsync(fulldir); const cfg = await prj0.readPxtConfig(); if (cfg.supportedTargets && cfg.supportedTargets.indexOf(targetId) < 0) { info(`skipping due to supportedTargets`); continue; } const res = await buildOnePrj(opts, prj0); if (!res.success) success = false; } } else if (success && moreHw.length) { for (const hw of moreHw) { selectHW(hw); infoHW(); outputs.push(prj.outputPrefix); await buildOnePrj(opts, prj); } const uf2s = []; for (const folder of outputs) { try { uf2s.push(await (0, host_1.host)().readFileAsync(path.join(folder, "binary.uf2"))); } catch (_a) { } } if (uf2s.length > 1) { const total = concatUint8Arrays(uf2s); const fn = "built/combined.uf2"; info(`combining ${uf2s.length} UF2 files into ${fn} (${Math.round(total.length / 1024)}kB)`); await (0, host_1.host)().writeFileAsync(fn, total); } } if (compileRes && outputs.length && firmwareName) { compileRes.binaryPath = outputs[0] + "/" + firmwareName; } if (compileRes && opts.buildSimJsInfo) { compileRes.simJsInfo = await prj.buildSimJsInfoAsync(compileRes); compileRes.simJsInfo.parts = compileRes.usedParts; } if (success) { msg("Build OK"); if (opts.watch) return compileRes; else (0, host_1.host)().exitWithStatus(0); } else { error("Build failed"); if (opts.watch) return compileRes; else (0, host_1.host)().exitWithStatus(1); } return null; function hwid(cfg) { return cfg.name.replace(/hw---/, ""); } function selectHW(hw0) { const hw = hw0.toLowerCase(); const selected = hwVariants.filter(cfg => { return (cfg.name.toLowerCase() == hw || hwid(cfg).toLowerCase() == hw || cfg.card.name.toLowerCase() == hw); }); if (!selected.length) { error(`No such HW id: ${hw0}`); msg(`Available hw:`); for (let cfg of hwVariants) { msg(`${hwid(cfg)}, ${cfg.card.name} - ${cfg.card.description}`); } (0, host_1.host)().exitWithStatus(1); } prj.hwVariant = hwid(selected[0]); } function infoHW() { info(`using hwVariant: ${prj.mainPkg.mkcConfig.hwVariant} (target ${targetId})`); if (!opts.alwaysBuilt) prj.outputPrefix = "built/" + prj.mainPkg.mkcConfig.hwVariant; } } exports.buildCommandOnce = buildCommandOnce; async function installCommand(opts) { applyGlobalOptions(opts); if (!await (0, host_1.host)().existsAsync("pxt.json")) { error("missing pxt.json"); (0, host_1.host)().exitWithStatus(1); } opts.pxtModules = true; const prj = await resolveProject(opts); prj.mainPkg = null; if (opts.monoRepo) { const dirs = await (0, files_1.monoRepoConfigsAsync)("."); info(`mono-repo: building ${dirs.length} projects`); for (const fullpxtjson of dirs) { if (fullpxtjson.startsWith("pxt_modules")) continue; const fulldir = path.dirname(fullpxtjson); info(`install ${fulldir}`); const prj0 = await prj.mkChildProjectAsync(fulldir); await prj0.maybeWritePxtModulesAsync(); } } else { await prj.maybeWritePxtModulesAsync(); } } exports.installCommand = installCommand; async function initCommand(template, deps, opts) { applyGlobalOptions(opts); if (!await (0, host_1.host)().existsAsync("pxt.json")) { if (opts.importUrl) { await downloadProjectAsync(opts.importUrl); } else { if (!template) { error("missing template"); (0, host_1.host)().exitWithStatus(1); } const target = loader_1.descriptors.find(t => t.id === template); if (!target) { error(`template not found`); (0, host_1.host)().exitWithStatus(1); } msg(`initializing project for ${target.name}`); msg("saving main.ts"); await (0, host_1.host)().writeFileAsync("main.ts", "// add code here", "utf8"); msg("saving pxt.json"); await (0, host_1.host)().writeFileAsync("pxt.json", JSON.stringify({ name: "my-project", version: "0.0.0", files: ["main.ts"], supportedTargets: [target.targetId], dependencies: target.dependencies || (target.corepkg && { [target.corepkg]: "*" }) || {}, testDependencies: target.testDependencies || {}, }, null, 4)); await (0, host_1.host)().writeFileAsync("mkc.json", JSON.stringify({ targetWebsite: target.website, links: {}, }, null, 4), "utf8"); } } else { if (template || opts.importUrl) { error("directory is not empty, cannot apply template"); (0, host_1.host)().exitWithStatus(1); } } const vscodeSettings = ".vscode/settings.json"; if (opts.vscodeProject && !await (0, host_1.host)().existsAsync(vscodeSettings)) { if (!await (0, host_1.host)().existsAsync(".vscode")) await (0, host_1.host)().mkdirAsync(".vscode"); await (0, host_1.host)().writeFileAsync(vscodeSettings, JSON.stringify({ "editor.formatOnType": true, "files.autoSave": "afterDelay", "files.watcherExclude": { "**/.git/objects/**": true, "**/built/**": true, "**/node_modules/**": true, "**/yotta_modules/**": true, "**/yotta_targets": true, "**/pxt_modules/**": true, "**/.pxt/**": true }, "files.associations": { "*.blocks": "html", "*.jres": "json" }, "search.exclude": { "**/built": true, "**/node_modules": true, "**/yotta_modules": true, "**/yotta_targets": true, "**/pxt_modules": true, "**/.pxt": true }, "files.exclude": { "**/pxt_modules": true, "**/.pxt": true, "**/mkc.json": true } }, null, 4)); } const vscodeExtensions = ".vscode/extensions.json"; if (opts.vscodeProject && !await (0, host_1.host)().existsAsync(vscodeExtensions)) { if (!await (0, host_1.host)().existsAsync(".vscode")) await (0, host_1.host)().mkdirAsync(".vscode"); await (0, host_1.host)().writeFileAsync(vscodeExtensions, JSON.stringify({ recommendations: [ "ms-edu.pxt-vscode-web" ] }, null, 4)); } const gitignore = ".gitignore"; if (opts.gitIgnore && !await (0, host_1.host)().existsAsync(gitignore)) { msg(`saving ${gitignore}`); await (0, host_1.host)().writeFileAsync(gitignore, `# MakeCode built node_modules yotta_modules yotta_targets pxt_modules .pxt _site *.db *.tgz .header.json .simstate.json`); } if (!await (0, host_1.host)().existsAsync("tsconfig.json")) { msg("saving tsconfig.json"); await (0, host_1.host)().writeFileAsync("tsconfig.json", JSON.stringify({ compilerOptions: { target: "ES5", noImplicitAny: true, outDir: "built", rootDir: ".", }, include: ["**/*.ts"], exclude: ["built/**", "pxt_modules/**/*test.ts"], }, null, 4), "utf8"); } const prettierrc = ".prettierrc"; if (!await (0, host_1.host)().existsAsync(prettierrc)) { msg(`saving ${prettierrc}`); await (0, host_1.host)().writeFileAsync(prettierrc, JSON.stringify({ arrowParens: "avoid", semi: false, tabWidth: 4, })); } const gh = ".github/workflows/makecode.yml"; if (!await (0, host_1.host)().existsAsync(gh)) { if (!await (0, host_1.host)().existsAsync(".github")) await (0, host_1.host)().mkdirAsync(".github"); if (!await (0, host_1.host)().existsAsync(".github/workflows")) await (0, host_1.host)().mkdirAsync(".github/workflows"); msg(`saving ${gh}`); await (0, host_1.host)().writeFileAsync(gh, `name: MakeCode Build on: push: workflow_dispatch: jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 with: submodules: recursive - run: npx makecode `); } opts.pxtModules = true; const prj = await resolveProject(opts); if (!await (0, host_1.host)().existsAsync("mkc.json")) { msg("saving mkc.json"); await (0, host_1.host)().writeFileAsync("mkc.json", mkc.stringifyConfig(prj.mainPkg.mkcConfig), "utf8"); } for (const dep of deps) await addDependency(prj, dep, undefined); prj.mainPkg = null; prj.writePxtModules = true; await prj.maybeWritePxtModulesAsync(); msg(`project ready, run "makecode -d" to build and deploy`); } exports.initCommand = initCommand; async function listHardwareVariantsAsync(opts) { const prj = await resolveProject(opts); await prj.service.languageService.enableExperimentalHardwareAsync(); return await prj.service.getHardwareVariantsAsync(); } exports.listHardwareVariantsAsync = listHardwareVariantsAsync; async function getAppTargetAsync(opts) { const prj = await resolveProject(opts); return await prj.service.languageService.getAppTargetAsync(); } exports.getAppTargetAsync = getAppTargetAsync; async function getTargetConfigAsync(opts) { const prj = await resolveProject(opts); return await prj.service.languageService.getTargetConfigAsync(); } exports.getTargetConfigAsync = getTargetConfigAsync; async function jacdacMakeCodeExtensions() { let data = []; try { const r = await (0, host_1.host)().requestAsync({ url: "https://raw.githubusercontent.com/microsoft/jacdac/main/services/makecode-extensions.json" }); data = JSON.parse(r.text); } catch (e) { } return data; } function join(...parts) { return parts.filter(p => !!p).join("/"); } // parse https://github.com/[company]/[project](/filepath)(#tag) function parseRepoId(repo) { if (!repo) return undefined; // clean out whitespaces repo = repo.trim(); // trim trailing / repo = repo.replace(/\/$/, ""); // convert github pages into github repo const mgh = /^https:\/\/([^./#]+)\.github\.io\/([^/#]+)\/?$/i.exec(repo); if (mgh) repo = `github:${mgh[1]}/${mgh[2]}`; repo = repo.replace(/^github:/i, ""); repo = repo.replace(/^https:\/\/github\.com\//i, ""); repo = repo.replace(/\.git\b/i, ""); const m = /^([^#\/:]+)\/([^#\/:]+)(\/([^#]+))?(#([^\/:]*))?$/.exec(repo); if (!m) return undefined; const owner = m[1]; const project = m[2]; let fileName = m[4]; const tag = m[6]; const treeM = fileName && /^tree\/([^\/]+\/)/.exec(fileName); if (treeM) { // https://github.com/pelikhan/mono-demo/tree/master/demo2 fileName = fileName.slice(treeM[0].length); // branch info? } return { owner, project, slug: join(owner, project), fullName: join(owner, project, fileName), tag, fileName, }; } async function fetchExtension(slug) { const url = `https://cdn.makecode.com/api/gh/${slug}`; const req = await (0, host_1.host)().requestAsync({ url }); if (req.statusCode !== 200) { error(`resolution of ${slug} failed (${req.statusCode})`); (0, host_1.host)().exitWithStatus(1); } const script = JSON.parse(req.text); return script; } async function searchCommand(query, opts) { applyGlobalOptions(opts); query = query.trim().toLowerCase(); msg(`searching for ${query}`); const prj = await resolveProject(opts); const targetid = prj.editor.targetJson.id; const res = await (0, host_1.host)().requestAsync({ url: `${mkc_1.cloudRoot}ghsearch/${targetid}/${targetid}?q=${encodeURIComponent(query)}` }); if (res.statusCode !== 200) { error(`search request failed`); (0, host_1.host)().exitWithStatus(1); } const payload = JSON.parse(res.text); const { items } = payload; items === null || items === void 0 ? void 0 : items.forEach(({ full_name, description, owner }) => { msg(` ${full_name}`); info(` https://github.com/${full_name}`); if (description) info(` ${description}`); }); if (/jacdac/i.test(query)) { const q = query.replace(/jacdac-*/i, ""); const exts = await jacdacMakeCodeExtensions(); for (const ext of exts.filter(ext => ext.client.name.indexOf(q) > -1 || ext.service.indexOf(q) > -1)) { msg(` ${ext.client.name}`); info(` https://${ext.client.repo}`); } } } exports.searchCommand = searchCommand; async function stackCommand(opts) { const srcmap = JSON.parse(await (0, host_1.host)().readFileAsync("built/binary.srcmap", "utf8")); console.log((0, stackresolver_1.expandStackTrace)(srcmap, await (0, host_1.host)().readFileAsync(0, "utf8"))); } exports.stackCommand = stackCommand; async function addCommand(pkg, name, opts) { applyGlobalOptions(opts); opts.pxtModules = true; msg(`adding ${pkg}`); const prj = await resolveProject(opts); await addDependency(prj, pkg, name); prj.mainPkg = null; await prj.maybeWritePxtModulesAsync(); } exports.addCommand = addCommand; async function addDependency(prj, pkg, name) { var _a, _b; pkg = pkg.toLowerCase().trim(); if (pkg === "jacdac") pkg = "https://github.com/microsoft/pxt-jacdac"; else if (/^jacdac-/.test(pkg)) { const exts = await jacdacMakeCodeExtensions(); const ext = exts.find(ext => ext.client.name === pkg); if (ext) { info(`found jacdac ${ext.client.repo}`); pkg = ext.client.repo; } } const rid = parseRepoId(pkg); const pxtJson = await prj.readPxtConfig(); if (rid) { const d = await fetchExtension(rid.slug); const dname = name || join(rid.project, rid.fileName).replace(/^pxt-/, "").replace("/", "-"); pxtJson.dependencies[dname] = `github:${rid.fullName}#${d.version ? `v${d.version}` : d.defaultBranch}`; info(`adding dependency ${dname}=${pxtJson.dependencies[dname]}`); } else { const appTarget = await prj.service.languageService.getAppTargetAsync(); const bundledPkgs = (_b = (_a = appTarget === null || appTarget === void 0 ? void 0 : appTarget.bundleddirs) === null || _a === void 0 ? void 0 : _a.map((dir) => { var _a; return (_a = /^libs\/(.+)/.exec(dir)) === null || _a === void 0 ? void 0 : _a[1]; })) === null || _b === void 0 ? void 0 : _b.filter((dir) => !!dir); const builtInPkg = bundledPkgs === null || bundledPkgs === void 0 ? void 0 : bundledPkgs.find(dir => dir === pkg); if (!builtInPkg) { const possiblyMeant = bundledPkgs === null || bundledPkgs === void 0 ? void 0 : bundledPkgs.filter(el => (el === null || el === void 0 ? void 0 : el.toLowerCase().indexOf(pkg)) !== -1); if (possiblyMeant === null || possiblyMeant === void 0 ? void 0 : possiblyMeant.length) { error(`Did you mean ${possiblyMeant === null || possiblyMeant === void 0 ? void 0 : possiblyMeant.join(", ")}?`); } else { error("unknown package, try https://github.com/.../... for github extensions"); } (0, host_1.host)().exitWithStatus(1); } const collidingHwVariant = Object.keys(pxtJson.dependencies) .find(dep => dep.toLowerCase().replace(/---.+$/, "") === pkg.replace(/---.+$/, "") && pxtJson.dependencies[dep] === "*"); if (collidingHwVariant) { delete pxtJson.dependencies[collidingHwVariant]; } pxtJson.dependencies[builtInPkg] = "*"; info(`adding builtin dependency ${builtInPkg}=*`); } await (0, host_1.host)().writeFileAsync("pxt.json", JSON.stringify(pxtJson, null, 4), "utf8"); } function isKV(v) { return !!v && typeof v === "object" && !Array.isArray(v); } function jsonMergeFrom(trg, src) { if (!src) return; Object.keys(src).forEach(k => { if (isKV(trg[k]) && isKV(src[k])) jsonMergeFrom(trg[k], src[k]); else if (Array.isArray(trg[k]) && Array.isArray(src[k])) trg[k] = trg[k].concat(src[k]); else trg[k] = src[k]; }); } async function readCfgAsync(cfgpath, quiet = false) { const files = []; return readCfgRec(cfgpath); async function readCfgRec(cfgpath) { if (files.indexOf(cfgpath) >= 0) { error(`Config file loop: ${files.join(" -> ")} -> ${cfgpath}`); (0, host_1.host)().exitWithStatus(1); } const cfg = await cfgFile(cfgpath); const currCfg = {}; files.push(cfgpath); for (const fn of cfg.include || []) { const resolved = path.resolve(path.dirname(cfgpath), fn); if (!quiet) info(` include: ${resolved}`); jsonMergeFrom(currCfg, readCfgRec(resolved)); } jsonMergeFrom(currCfg, cfg); delete currCfg.include; files.pop(); return currCfg; async function cfgFile(cfgpath) { let cfg; try { cfg = JSON.parse(await (0, host_1.host)().readFileAsync(cfgpath, "utf8")); } catch (e) { error(`Can't read config file: '${cfgpath}'; ` + e.message); (0, host_1.host)().exitWithStatus(1); } const lnk = cfg.links; if (lnk) { const mkcFolder = path.resolve(".", path.dirname(cfgpath)); for (const k of Object.keys(lnk)) { lnk[k] = path.resolve(mkcFolder, lnk[k]); } } return cfg; } } } function concatUint8Arrays(bufs) { let size = 0; for (const buf of bufs) { size += buf.length; } const res = new Uint8Array(size); let offset = 0; for (const buf of bufs) { res.set(buf, offset); offset += buf.length; } return res; } async function shareCommand(opts) { const shareLink = await (0, share_1.shareProjectAsync)(opts); if (shareLink) { info(`Success! Project shared to ${shareLink}`); } else { error("Unable to share project"); } } exports.shareCommand = shareCommand; async function getSimHTML(opts) { return getExpandedHTML(opts, "cachedSimulatorKey", "sim"); } exports.getSimHTML = getSimHTML; async function getWebConfig(opts) { applyGlobalOptions(opts); const project = await resolveProject(opts); return project.editor.webConfig; } exports.getWebConfig = getWebConfig; async function getAssetEditorHTML(opts) { return getExpandedHTML(opts, "cachedAssetEditorKey", "asseteditor"); } exports.getAssetEditorHTML = getAssetEditorHTML; async function getExpandedHTML(opts, downloadInfoKey, pageName) { applyGlobalOptions(opts); const project = await resolveProject(opts); const cache = await project.getCacheAsync(); const infoKey = project.editor.website + "-info"; const rawDownloadInfo = await cache.getAsync(infoKey); const parsed = (rawDownloadInfo ? JSON.parse((0, host_1.host)().bufferToString(rawDownloadInfo)) : {}); if (parsed[downloadInfoKey]) { const existing = await cache.getAsync(parsed[downloadInfoKey]); if (existing) { return (0, host_1.host)().bufferToString(existing); } } const key = `${project.editor.website}-${pageName}.html`; const expanded = await getExpandedPageFromCache(key, cache, project.editor.website, project.editor.cdnUrl); const expandedKey = `${project.editor.website}-${pageName}Expanded.html`; await cache.setAsync(expandedKey, (0, host_1.host)().stringToBuffer(expanded)); // save that this page has been generated. this key is cleared when the cache is updated parsed[downloadInfoKey] = expandedKey; await cache.setAsync(infoKey, (0, host_1.host)().stringToBuffer(JSON.stringify(parsed))); return expanded; } async function getExpandedPageFromCache(cacheKey, cache, website, cdnUrl) { let pageHTML = (0, host_1.host)().bufferToString(await cache.getAsync(cacheKey)); const imageUrls = []; pageHTML = pageHTML.replace(/https:\/\/[\w\/\.\-]+/g, f => { if (f.startsWith(cdnUrl) && f.endsWith(".png")) { imageUrls.push(f); } return f; }); for (const url of imageUrls) { const resp = await downloader.requestAsync({ url }); if (resp.statusCode !== 200 || !resp.buffer) continue; const encoded = await (0, host_1.host)().base64EncodeBufferAsync(resp.buffer); const dataUri = `data:image/png;base64,${encoded}`; pageHTML = pageHTML.replace(url, dataUri); } const dom = new xmldom_1.DOMParser().parseFromString(pageHTML, "text/html"); const scripts = []; const toRemove = []; const processElement = async (element, srcAttribute, fileExtension) => { if (!element.hasAttribute(srcAttribute)) return undefined; const srcPath = element.getAttribute(srcAttribute); if (!srcPath.endsWith(fileExtension)) return undefined; let filename = srcPath; if (srcPath.startsWith(website)) { filename = srcPath.slice(website.length); } if (filename.indexOf("/") !== -1) { filename = filename.split("/").pop(); } else { filename = filename.split("-").pop(); } const key = website + "-" + filename; const contents = await cache.getAsync(key); if (!contents) return undefined; let contentString = (0, host_1.host)().bufferToString(contents); return contentString.trim(); }; for (const element of dom.getElementsByTagName("script")) { const contentString = await processElement(element, "src", ".js"); if (contentString) { scripts.push(encodeURIComponent(contentString)); } else { scripts.push(encodeURIComponent(element.textContent)); } toRemove.push(element); } for (const element of dom.getElementsByTagName("link")) { const contentString = await processElement(element, "href", ".css"); if (contentString) { const newStyle = dom.createElement("style"); newStyle.textContent = `\n${contentString}\n`; element.parentElement.insertBefore(newStyle, element); toRemove.push(element); } } for (const element of toRemove) { element.parentElement.removeChild(element); } const metaScript = ` const allScripts = [\`${scripts.join("`,`")}\`]; for (const scriptEntry of allScripts) { const el = document.createElement("script"); el.textContent = decodeURIComponent(scriptEntry); document.head.appendChild(el); } `; const scriptElement = dom.createElement("script"); scriptElement.textContent = metaScript; dom.getElementsByTagName("head").item(0).appendChild(scriptElement); return new xmldom_1.XMLSerializer().serializeToString(dom); } //# sourceMappingURL=commands.js.map