UNPKG

fable-compiler-netcore

Version:
302 lines (301 loc) 10.6 kB
"use strict"; var fs = require("fs"); var path = require("path"); var child_process = require("child_process"); var babel = require("babel-core"); var resolve = require("resolve"); var constants = require("./constants"); /** * Makes a node-style asynchronous function return an promise. * Pass the function and then the rest of arguments. * Example: `promisify(fs.remove, "build").then(() => ...)` */ function promisify(f) { var args = Array.from(arguments).slice(1); return new Promise(function (resolve, reject) { args.push(function (err, data) { if (err) { reject(err); } else { resolve(data); } }); f.apply(null, args); }); } exports.promisify = promisify; /** Prints a new line with the message on process.stderr */ function stderrLog(err) { var msg = typeof err === "string" ? err : err.message + (err.stack ? "\n" + err.stack : ""); if (typeof process === "object") process.stderr.write(msg + "\n"); else console.log(msg); } exports.stderrLog = stderrLog; /** Prints a new line with the message on process.stdout */ function stdoutLog(s) { if (typeof process === "object") { process.stdout.write(s + "\n"); } else { console.log(s); } } exports.stdoutLog = stdoutLog; /** Finish the process according to the environment */ function finish(code, continuation) { var err = code === 0 ? null : "FABLE EXIT CODE: " + code; if (typeof continuation === "object") { if (err && typeof continuation.reject === "function") { continuation.reject(err); return; } else if (typeof continuation.resolve === "function") { continuation.resolve(null); return; } } if (typeof process === "object") { process.exit(code); } else if (err) { throw err; } } exports.finish = finish; function splitByWhitespace(str) { function stripQuotes(str, start, end) { return str[start] === '"' && str[end - 1] === '"' ? str.substring(start + 1, end - 1) : str.substring(start, end); } var reg = /\s+(?=([^"]*"[^"]*")*[^"]*$)/g; reg.lastIndex = 0; var tmp, tmp2, results = [], lastIndex = 0; while ((tmp = reg.exec(str)) !== null) { results.push(stripQuotes(str, lastIndex, tmp.index)); lastIndex = tmp.index + tmp[0].length; } results.push(stripQuotes(str, lastIndex, str.length)); return results; } exports.splitByWhitespace = splitByWhitespace; function runCommandPrivate(workingDir, command, continuation) { var cmd, args; process.stdout.write(workingDir + "> " + command + "\n"); // If there's no continuation, it means the process will run in parallel (postbuild-once). // If we use `cmd /C` on Windows we won't be able to kill the cmd child process later. // See http://stackoverflow.com/a/32814686 (unfortutanely the solutions didn't seem to apply here) if (process.platform === "win32" && continuation) { cmd = "cmd"; args = splitByWhitespace(command); args.splice(0, 0, "/C"); } else { args = splitByWhitespace(command); cmd = args[0]; args = args.slice(1); } var proc = child_process.spawn(cmd, args, { cwd: workingDir }); proc.on('exit', function (code) { if (continuation) { code === 0 ? continuation.resolve(code) : continuation.reject(code); } }); proc.stderr.on('data', function (data) { stderrLog(data.toString()); }); proc.stdout.on("data", function (data) { stdoutLog(data.toString()); }); return proc; } /** Runs a command and returns a Promise, requires child_process */ function runCommand(workingDir, command) { return new Promise(function (resolve, reject) { runCommandPrivate(workingDir, command, { resolve: resolve, reject: reject }); }); } exports.runCommand = runCommand; /** Starts a process to run the command and returns it, requires child_process */ function runCommandInParallel(workingDir, command) { return runCommandPrivate(workingDir, command); } exports.runCommandInParallel = runCommandInParallel; /** * Returns an array with tuples of plugin paths and config objects (requires 'resolve' package) * @param plugins Can be a string, array of tuples (id + config) or an object (key-value pairs) * @param basedir Directory from where to resolve the plugins * @param prefix Will be attached to plugin names if missing (e.g. 'babel-plugin-') */ function resolvePlugins(plugins, basedir, prefix) { if (plugins == null) { return []; } else if (typeof plugins === "object") { if (!Array.isArray(plugins)) { plugins = Object.getOwnPropertyNames(plugins).map(function (k) { return [k, plugins[k]]; }); } } else { plugins = [plugins]; } return plugins.map(function (plugin) { var config = {}; if (Array.isArray(plugin)) { config = plugin[1]; plugin = plugin[0]; } plugin = prefix && !plugin.startsWith(prefix) ? prefix + plugin : plugin; return [resolve.sync(plugin, { basedir: basedir }), config]; }); } exports.resolvePlugins = resolvePlugins; /** * Checks if the file is an F# project (.fsproj) or script (.fsx) */ function isFSharpProject(filePath) { return typeof filePath === "string" && constants.FSHARP_PROJECT_EXTENSIONS.indexOf(path.extname(filePath).toLowerCase()) >= 0; } exports.isFSharpProject = isFSharpProject; /** * Checks if the file is an F# module (.fs), script (.fsx) or project (.fsproj) */ function isFSharpFile(filePath) { return typeof filePath === "string" && constants.FSHARP_FILE_EXTENSIONS.indexOf(path.extname(filePath).toLowerCase()) >= 0; } exports.isFSharpFile = isFSharpFile; /** * Apparently path.isAbsolute is not very reliable * so this uses `path.resolve(x) === x` */ function isFullPath(filePath) { return path.resolve(filePath) === filePath; } exports.isFullPath = isFullPath; /** * If path2 is absolute, returns it instead of joining */ function pathJoin(path1, path2) { if (!path2) { return path1; } ; return isFullPath(path2) ? path2 : path.join(path1, path2); } exports.pathJoin = pathJoin; /** * Calculates the common parent directory of an array of file paths * @param {string[]} filePaths Array of resolved file paths. */ function getCommonBaseDir(filePaths) { function getCommonPrefix(xs) { function f(prefix, xs) { if (xs.length === 0) { return prefix; } else { var x = xs[0], i = 0; while (i < prefix.length && i < x.length && x[i] === prefix[i]) { i = i + 1; } return f(prefix.slice(0, i), xs.slice(1)); } } return xs.length === 0 ? [] : f(xs[0], xs.slice(1)); } var normalized = filePaths.map(function (filePath) { return path.dirname(filePath).replace(/\\/g, '/').split('/'); }); return getCommonPrefix(normalized).join('/'); } exports.getCommonBaseDir = getCommonBaseDir; /** * Converts a Babel AST to JS code. */ function babelify(babelAst, opts) { var outDir = pathJoin(opts.workingDir, opts.outDir); var babelOpts = { babelrc: opts.babelrc || false, filename: babelAst.fileName, // sourceRoot: outDir, presets: opts.babel.presets, plugins: opts.babel.plugins, }; var fsCode = null; // The F# code is only necessary when generating source maps if (opts.sourceMaps && babelAst.originalFileName) { try { fsCode = fs.readFileSync(babelAst.originalFileName).toString(); babelOpts.sourceMaps = opts.sourceMaps, babelOpts.sourceMapTarget = path.basename(babelAst.fileName), babelOpts.sourceFileName = path.relative(path.dirname(babelAst.fileName), babelAst.originalFileName.replace(/\\/g, '/')); } catch (err) { } } var parsed = babel.transformFromAst(babelAst, fsCode, babelOpts); var res = [{ isEntry: babelAst.isEntry, fileName: babelAst.fileName, code: parsed.code, map: parsed.map }]; // Compile JS includes if (Array.isArray(babelAst.jsIncludes)) babelAst.jsIncludes.forEach(function (js) { parsed = babel.transformFileSync(js.sourcePath, babelOpts); res.push({ isEntry: false, fileName: pathJoin(outDir, "js_includes/" + js.name) + ".js", code: parsed.code, map: parsed.map }); }); var timestamp = " at " + (new Date()).toLocaleTimeString(); for (var i = 0; i < res.length; i++) { stdoutLog("Compiled " + path.relative(outDir, res[i].fileName) + timestamp); } return res; } exports.babelify = babelify; /** Create directory if it doesn't exist */ function ensureDirExists(dir, cont) { if (fs.existsSync(dir)) { if (typeof cont === "function") { cont(); } } else { ensureDirExists(path.dirname(dir), function () { if (!fs.existsSync(dir)) { fs.mkdirSync(dir); } if (typeof cont === "function") { cont(); } }); } } exports.ensureDirExists = ensureDirExists; function writeFile(fileName, code, map) { ensureDirExists(path.dirname(fileName)); fs.writeFileSync(fileName, code); if (map) { fs.appendFileSync(fileName, "\n//# sourceMappingURL=" + path.basename(fileName) + ".map"); fs.writeFileSync(fileName + ".map", JSON.stringify(map)); } } exports.writeFile = writeFile; /** Converts Babel AST to JS code and writes to disc */ function babelifyToFile(babelAst, opts) { // Use strict equality so it evals to false when opts.sourceMaps === "inline" babelify(babelAst, opts).forEach(function (parsed) { return writeFile(parsed.fileName, parsed.code, opts.sourceMaps === true ? parsed.map : null); }); } exports.babelifyToFile = babelifyToFile;