'use strict'; var osTmpDir = require('os-tmpdir'); var path = require('path'); var childProcess = require('child_process'); var promisify = require('util.promisify'); var nodeFs = require('fs'); var mkdirp = require('mkdirp'); var promiseRipple = require('./promise-ripple'); var promiseSeries = require('./promise-series'); var _a = require('./eval-errors'), stackSplit = _a.stackSplit, stackJoin = _a.stackJoin, acornError = _a.acornError, getCleanLines = _a.getCleanLines; var buildEvalable = require('./module-rewrite').buildEvalable; var getHash = require('./md-nodes').getHash; var _b = require('./shell-eval'), parsePromptBlock = _b.parsePromptBlock, runPromptCommand = _b.runPromptCommand, checkPromptCommand = _b.checkPromptCommand; var temp = path.join(osTmpDir(), 'evalmd'); var fs = { mkdirsAsync: promisify(mkdirp), writeFileAsync: promisify(nodeFs.writeFile), unlinkAsync: promisify(nodeFs.unlink), }; /** @import { AssembleData, ConcatNode, Logger, MdNode, Package } from './types' */ /** * @param {string} filePath * @param {readonly MdNode[]} nodes */ function evalError(filePath, nodes) { return /** @param {unknown} e */ function (e) { if (!(e instanceof Error) || !e.stack) { return e; } var stack = stackSplit(e.stack); var absFilePath = path.resolve(filePath); var cleanLines = getCleanLines(stack.lines, nodes, absFilePath, false); if (cleanLines.length !== 0) { stack.lines = cleanLines; } stack.frame = getCleanLines(stack.frame, nodes, absFilePath, true); stack.frame.shift(); if (stack.frame[stack.frame.length - 1] === '') { stack.frame.pop(); } return stackJoin(stack); }; } /** @param {string} file */ function evalFileAsync(file) { return new Promise(function (resolve, reject) { var command = [process.execPath, file].join(' '); childProcess.exec(command, function (error, stdout, stderr) { if (stdout) { process.stdout.write(stdout); } if (error) { return reject(error); } return resolve({ stdout: stdout, stderr: stderr, }); }); }); } /** * @param {unknown} error * @param {((e: unknown) => unknown) | undefined} [stackWrapper] */ function getCleanErr(error, stackWrapper) { if (stackWrapper) { return stackWrapper(error); } if (error instanceof Error && error.stack) { return error.stack; } return error; } /** * @param {unknown} error * @param {((e: unknown) => unknown) | undefined} stackWrapper * @param {boolean} nonstop * @param {Logger} logger */ function nonstopErr(error, stackWrapper, nonstop, logger) { var cleanErr = getCleanErr(error, stackWrapper); if (nonstop) { logger.err(cleanErr); return error; } throw cleanErr; } /** * @template {MdNode | ConcatNode} T * @param {T} node * @param {readonly MdNode[]} nodes * @param {number} markdownLinesLength * @param {Package | false} pkg * @param {string} prepend * @param {boolean} nonstop * @param {string} filePath * @param {Logger} logger * @param {boolean} sloppy * @returns {Promise} */ function evaluate(node, nodes, markdownLinesLength, pkg, prepend, nonstop, filePath, logger, sloppy) { return promiseRipple(node, { notice: function (node) { var ids = Array.isArray(node) ? node.map(function (n) { return n.id; }) : [node.id]; var word = (ids.length > 1) ? 'blocks' : 'block'; logger.info(filePath, [ 'running', word, ids.join(', '), ].join(' ')); }, evalCode: function (node) { var parse = (Array.isArray(node) ? (node[0] && node[0].parse) : node.parse) || false; var stackWrapper = acornError(nodes, filePath); try { return buildEvalable(node, nodes, markdownLinesLength, pkg, prepend, { parse: parse, sloppy: sloppy }); } catch (error) { return nonstopErr(error, stackWrapper, nonstop, logger); } }, fileName: function (node) { var fileEvalHash = typeof node.fileEval === 'string' ? getHash(node.fileEval) : getHash(filePath + node.id); Object.assign(node, { fileEvalHash: fileEvalHash, fileEvalHashPath: path.join(temp, "".concat(fileEvalHash, ".js")), }); return node; }, fileCreated: function (node) { if (!node.evalCode || node.evalCode instanceof Error || !node.fileEvalHashPath) { return false; } var evalCode = node.evalCode; var fileEvalHashPath = node.fileEvalHashPath; var dirs = path.dirname(fileEvalHashPath); return fs.mkdirsAsync(dirs) .then(function () { return fs.writeFileAsync(fileEvalHashPath, evalCode.preserveAlter) .then(function () { return true; }); }); }, evalResult: function (node) { if (!node.fileCreated || !node.fileEvalHashPath) { return false; } var stackWrapper = evalError(filePath, nodes); return evalFileAsync(node.fileEvalHashPath) .catch(function (error) { return nonstopErr(error, stackWrapper, nonstop, logger); }); }, fileRemove: function (node) { if (!node.fileCreated || !node.fileEvalHashPath) { return false; } return fs.unlinkAsync(node.fileEvalHashPath); }, }); } /** * @param {ConcatNode} nodes * @param {number} markdownLinesLength * @param {Package | false} pkg * @param {string} prepend * @param {boolean} nonstop * @param {string} filePath * @param {boolean} blockScope * @param {Logger} logger * @param {boolean} sloppy */ function evaluateScope(nodes, markdownLinesLength, pkg, prepend, nonstop, filePath, blockScope, logger, sloppy) { if (blockScope) { return promiseSeries(nodes, function (node, _index, nodes) { return evaluate(node, nodes, markdownLinesLength, pkg, prepend, nonstop, filePath, logger, sloppy); }); } return evaluate(nodes, nodes, markdownLinesLength, pkg, prepend, nonstop, filePath, logger, sloppy) .then(function (node) { return [node]; }); } /** * @template {MdNode | ConcatNode} T * @param {T} node * @param {readonly MdNode[]} nodes * @param {number} markdownLinesLength * @param {Package | false} pkg * @param {string} prepend * @param {boolean} nonstop * @param {string} filePath * @param {string | boolean} output * @param {string | boolean} delimeter * @param {Logger} logger * @param {boolean} sloppy * @returns {Promise} */ function outputCode(node, nodes, markdownLinesLength, pkg, prepend, nonstop, filePath, output, delimeter, logger, sloppy) { return promiseRipple(node, { notice: function (node) { var ids = Array.isArray(node) ? node.map(function (n) { return n.id; }) : [node.id]; var word = (ids.length > 1) ? 'blocks' : 'block'; logger.info(filePath, [ 'outputting', word, ids.join(', '), ].join(' ')); }, evalCode: function (node) { var parse = (Array.isArray(node) ? (node[0] && node[0].parse) : node.parse) || false; var stackWrapper = acornError(nodes, filePath); try { return buildEvalable(node, nodes, markdownLinesLength, pkg, prepend, { parse: parse, sloppy: sloppy }); } catch (error) { return nonstopErr(error, stackWrapper, nonstop, logger); } }, output: function (node) { if (!node.evalCode || node.evalCode instanceof Error) { return false; } if (output === true) { output = 'preserve'; } if (output === 'preserve' || output === 'concat' || output === 'preserveAlter' || output === 'concatAlter') { process.stdout.write(node.evalCode[output]); } delimeter = (delimeter === true) ? '//EVALMD-STDOUT-FILE-DELIMETER' : delimeter; if (delimeter) { process.stdout.write(delimeter); } return true; }, }); } /** * @param {ConcatNode} nodes * @param {number} markdownLinesLength * @param {Package | false} pkg * @param {string} prepend * @param {boolean} nonstop * @param {string} filePath * @param {boolean} blockScope * @param {string | boolean} output * @param {string | boolean} delimeter * @param {Logger} logger * @param {boolean} sloppy */ function outputScope(nodes, markdownLinesLength, pkg, prepend, nonstop, filePath, blockScope, output, delimeter, logger, sloppy) { if (blockScope) { return promiseSeries(nodes, function (node, _index, nodes) { return outputCode(node, nodes, markdownLinesLength, pkg, prepend, nonstop, filePath, output, delimeter, logger, sloppy); }); } return outputCode(nodes, nodes, markdownLinesLength, pkg, prepend, nonstop, filePath, output, delimeter, logger, sloppy) .then(function (node) { return [node]; }); } /** * @param {readonly MdNode[]} nodes * @param {string} filePath * @param {boolean} nonstop * @param {Logger} logger */ function evaluateShell(nodes, filePath, nonstop, logger) { return promiseSeries(nodes, function (node) { logger.info(filePath, [ 'running', 'block', node.id, ].join(' ')); var commands = parsePromptBlock(node.content); return promiseSeries(commands, function (item) { return runPromptCommand(item.command).then(function (result) { var error = checkPromptCommand(item, result); if (error) { if (!nonstop) { throw error; } logger.err(error); node.evalResult = error; } return result; }); }) .then(function () { if (!node.evalResult) { node.evalResult = true; } return node; }); }); } /** * @param {string} kind * @param {readonly MdNode[]} nodes * @param {string} filePath * @param {boolean} nonstop * @param {Logger} logger */ function evaluateKind(kind, nodes, filePath, nonstop, logger) { if (kind === 'sh') { return evaluateShell(nodes, filePath, nonstop, logger); } return Promise.resolve([]); } /** * @param {AssembleData} data * @param {Package | false} pkg * @param {string} prepend * @param {boolean} nonstop * @param {string} filePath * @param {Logger} logger * @param {boolean} sloppy */ function evaluateAllKinds(data, pkg, prepend, nonstop, filePath, logger, sloppy) { /** @type {(() => unknown)[]} */ var runners = []; var evalNodes = data.evalNodes || []; var markdownLinesLength = (data.markdownLines || []).length; var kindFences = data.kindFences || {}; if (evalNodes.length) { runners.push(function () { return evaluateScope(evalNodes, markdownLinesLength, pkg, prepend, nonstop, filePath, Boolean(data.blockScope), logger, sloppy); }); } Object.keys(kindFences).forEach(function (kind) { if (kindFences[kind].length) { runners.push(function () { return evaluateKind(kind, kindFences[kind], filePath, nonstop, logger); }); } }); if (!runners.length) { logger.info('no blocks to eval'); return Promise.resolve(false); } /** @type {unknown[]} */ var results = []; return runners.reduce(function (chain, runner) { return chain.then(function () { return Promise.resolve(runner()).then(function (nodes) { results = results.concat(nodes); }); }); }, Promise.resolve()).then(function () { return results; }); } module.exports = { evalError: evalError, evalFileAsync: evalFileAsync, getCleanErr: getCleanErr, nonstopErr: nonstopErr, evaluate: evaluate, evaluateScope: evaluateScope, outputCode: outputCode, outputScope: outputScope, evaluateShell: evaluateShell, evaluateKind: evaluateKind, evaluateAllKinds: evaluateAllKinds, };