UNPKG

10.6 kBJavaScriptView Raw
1"use strict";
2var fs = require("fs");
3var path = require("path");
4var child_process = require("child_process");
5var babel = require("babel-core");
6var resolve = require("resolve");
7var constants = require("./constants");
8/**
9 * Makes a node-style asynchronous function return an promise.
10 * Pass the function and then the rest of arguments.
11 * Example: `promisify(fs.remove, "build").then(() => ...)`
12*/
13function promisify(f) {
14 var args = Array.from(arguments).slice(1);
15 return new Promise(function (resolve, reject) {
16 args.push(function (err, data) {
17 if (err) {
18 reject(err);
19 }
20 else {
21 resolve(data);
22 }
23 });
24 f.apply(null, args);
25 });
26}
27exports.promisify = promisify;
28/** Prints a new line with the message on process.stderr */
29function stderrLog(err) {
30 var msg = typeof err === "string"
31 ? err
32 : err.message + (err.stack ? "\n" + err.stack : "");
33 if (typeof process === "object")
34 process.stderr.write(msg + "\n");
35 else
36 console.log(msg);
37}
38exports.stderrLog = stderrLog;
39/** Prints a new line with the message on process.stdout */
40function stdoutLog(s) {
41 if (typeof process === "object") {
42 process.stdout.write(s + "\n");
43 }
44 else {
45 console.log(s);
46 }
47}
48exports.stdoutLog = stdoutLog;
49/** Finish the process according to the environment */
50function finish(code, continuation) {
51 var err = code === 0 ? null : "FABLE EXIT CODE: " + code;
52 if (typeof continuation === "object") {
53 if (err && typeof continuation.reject === "function") {
54 continuation.reject(err);
55 return;
56 }
57 else if (typeof continuation.resolve === "function") {
58 continuation.resolve(null);
59 return;
60 }
61 }
62 if (typeof process === "object") {
63 process.exit(code);
64 }
65 else if (err) {
66 throw err;
67 }
68}
69exports.finish = finish;
70function splitByWhitespace(str) {
71 function stripQuotes(str, start, end) {
72 return str[start] === '"' && str[end - 1] === '"'
73 ? str.substring(start + 1, end - 1)
74 : str.substring(start, end);
75 }
76 var reg = /\s+(?=([^"]*"[^"]*")*[^"]*$)/g;
77 reg.lastIndex = 0;
78 var tmp, tmp2, results = [], lastIndex = 0;
79 while ((tmp = reg.exec(str)) !== null) {
80 results.push(stripQuotes(str, lastIndex, tmp.index));
81 lastIndex = tmp.index + tmp[0].length;
82 }
83 results.push(stripQuotes(str, lastIndex, str.length));
84 return results;
85}
86exports.splitByWhitespace = splitByWhitespace;
87function runCommandPrivate(workingDir, command, continuation) {
88 var cmd, args;
89 process.stdout.write(workingDir + "> " + command + "\n");
90 // If there's no continuation, it means the process will run in parallel (postbuild-once).
91 // If we use `cmd /C` on Windows we won't be able to kill the cmd child process later.
92 // See http://stackoverflow.com/a/32814686 (unfortutanely the solutions didn't seem to apply here)
93 if (process.platform === "win32" && continuation) {
94 cmd = "cmd";
95 args = splitByWhitespace(command);
96 args.splice(0, 0, "/C");
97 }
98 else {
99 args = splitByWhitespace(command);
100 cmd = args[0];
101 args = args.slice(1);
102 }
103 var proc = child_process.spawn(cmd, args, { cwd: workingDir });
104 proc.on('exit', function (code) {
105 if (continuation) {
106 code === 0 ? continuation.resolve(code) : continuation.reject(code);
107 }
108 });
109 proc.stderr.on('data', function (data) {
110 stderrLog(data.toString());
111 });
112 proc.stdout.on("data", function (data) {
113 stdoutLog(data.toString());
114 });
115 return proc;
116}
117/** Runs a command and returns a Promise, requires child_process */
118function runCommand(workingDir, command) {
119 return new Promise(function (resolve, reject) {
120 runCommandPrivate(workingDir, command, { resolve: resolve, reject: reject });
121 });
122}
123exports.runCommand = runCommand;
124/** Starts a process to run the command and returns it, requires child_process */
125function runCommandInParallel(workingDir, command) {
126 return runCommandPrivate(workingDir, command);
127}
128exports.runCommandInParallel = runCommandInParallel;
129/**
130 * Returns an array with tuples of plugin paths and config objects (requires 'resolve' package)
131 * @param plugins Can be a string, array of tuples (id + config) or an object (key-value pairs)
132 * @param basedir Directory from where to resolve the plugins
133 * @param prefix Will be attached to plugin names if missing (e.g. 'babel-plugin-')
134*/
135function resolvePlugins(plugins, basedir, prefix) {
136 if (plugins == null) {
137 return [];
138 }
139 else if (typeof plugins === "object") {
140 if (!Array.isArray(plugins)) {
141 plugins = Object.getOwnPropertyNames(plugins).map(function (k) { return [k, plugins[k]]; });
142 }
143 }
144 else {
145 plugins = [plugins];
146 }
147 return plugins.map(function (plugin) {
148 var config = {};
149 if (Array.isArray(plugin)) {
150 config = plugin[1];
151 plugin = plugin[0];
152 }
153 plugin = prefix && !plugin.startsWith(prefix) ? prefix + plugin : plugin;
154 return [resolve.sync(plugin, { basedir: basedir }), config];
155 });
156}
157exports.resolvePlugins = resolvePlugins;
158/**
159 * Checks if the file is an F# project (.fsproj) or script (.fsx)
160 */
161function isFSharpProject(filePath) {
162 return typeof filePath === "string"
163 && constants.FSHARP_PROJECT_EXTENSIONS.indexOf(path.extname(filePath).toLowerCase()) >= 0;
164}
165exports.isFSharpProject = isFSharpProject;
166/**
167 * Checks if the file is an F# module (.fs), script (.fsx) or project (.fsproj)
168 */
169function isFSharpFile(filePath) {
170 return typeof filePath === "string"
171 && constants.FSHARP_FILE_EXTENSIONS.indexOf(path.extname(filePath).toLowerCase()) >= 0;
172}
173exports.isFSharpFile = isFSharpFile;
174/**
175 * Apparently path.isAbsolute is not very reliable
176 * so this uses `path.resolve(x) === x`
177*/
178function isFullPath(filePath) {
179 return path.resolve(filePath) === filePath;
180}
181exports.isFullPath = isFullPath;
182/**
183 * If path2 is absolute, returns it instead of joining
184*/
185function pathJoin(path1, path2) {
186 if (!path2) {
187 return path1;
188 }
189 ;
190 return isFullPath(path2) ? path2 : path.join(path1, path2);
191}
192exports.pathJoin = pathJoin;
193/**
194 * Calculates the common parent directory of an array of file paths
195 * @param {string[]} filePaths Array of resolved file paths.
196*/
197function getCommonBaseDir(filePaths) {
198 function getCommonPrefix(xs) {
199 function f(prefix, xs) {
200 if (xs.length === 0) {
201 return prefix;
202 }
203 else {
204 var x = xs[0], i = 0;
205 while (i < prefix.length && i < x.length && x[i] === prefix[i]) {
206 i = i + 1;
207 }
208 return f(prefix.slice(0, i), xs.slice(1));
209 }
210 }
211 return xs.length === 0 ? [] : f(xs[0], xs.slice(1));
212 }
213 var normalized = filePaths.map(function (filePath) { return path.dirname(filePath).replace(/\\/g, '/').split('/'); });
214 return getCommonPrefix(normalized).join('/');
215}
216exports.getCommonBaseDir = getCommonBaseDir;
217/**
218 * Converts a Babel AST to JS code.
219 */
220function babelify(babelAst, opts) {
221 var outDir = pathJoin(opts.workingDir, opts.outDir);
222 var babelOpts = {
223 babelrc: opts.babelrc || false,
224 filename: babelAst.fileName,
225 // sourceRoot: outDir,
226 presets: opts.babel.presets,
227 plugins: opts.babel.plugins,
228 };
229 var fsCode = null;
230 // The F# code is only necessary when generating source maps
231 if (opts.sourceMaps && babelAst.originalFileName) {
232 try {
233 fsCode = fs.readFileSync(babelAst.originalFileName).toString();
234 babelOpts.sourceMaps = opts.sourceMaps,
235 babelOpts.sourceMapTarget = path.basename(babelAst.fileName),
236 babelOpts.sourceFileName = path.relative(path.dirname(babelAst.fileName), babelAst.originalFileName.replace(/\\/g, '/'));
237 }
238 catch (err) {
239 }
240 }
241 var parsed = babel.transformFromAst(babelAst, fsCode, babelOpts);
242 var res = [{
243 isEntry: babelAst.isEntry,
244 fileName: babelAst.fileName,
245 code: parsed.code,
246 map: parsed.map
247 }];
248 // Compile JS includes
249 if (Array.isArray(babelAst.jsIncludes))
250 babelAst.jsIncludes.forEach(function (js) {
251 parsed = babel.transformFileSync(js.sourcePath, babelOpts);
252 res.push({
253 isEntry: false,
254 fileName: pathJoin(outDir, "js_includes/" + js.name) + ".js",
255 code: parsed.code,
256 map: parsed.map
257 });
258 });
259 var timestamp = " at " + (new Date()).toLocaleTimeString();
260 for (var i = 0; i < res.length; i++) {
261 stdoutLog("Compiled " + path.relative(outDir, res[i].fileName) + timestamp);
262 }
263 return res;
264}
265exports.babelify = babelify;
266/** Create directory if it doesn't exist */
267function ensureDirExists(dir, cont) {
268 if (fs.existsSync(dir)) {
269 if (typeof cont === "function") {
270 cont();
271 }
272 }
273 else {
274 ensureDirExists(path.dirname(dir), function () {
275 if (!fs.existsSync(dir)) {
276 fs.mkdirSync(dir);
277 }
278 if (typeof cont === "function") {
279 cont();
280 }
281 });
282 }
283}
284exports.ensureDirExists = ensureDirExists;
285function writeFile(fileName, code, map) {
286 ensureDirExists(path.dirname(fileName));
287 fs.writeFileSync(fileName, code);
288 if (map) {
289 fs.appendFileSync(fileName, "\n//# sourceMappingURL=" + path.basename(fileName) + ".map");
290 fs.writeFileSync(fileName + ".map", JSON.stringify(map));
291 }
292}
293exports.writeFile = writeFile;
294/** Converts Babel AST to JS code and writes to disc */
295function babelifyToFile(babelAst, opts) {
296 // Use strict equality so it evals to false when opts.sourceMaps === "inline"
297 babelify(babelAst, opts).forEach(function (parsed) {
298 return writeFile(parsed.fileName, parsed.code, opts.sourceMaps === true ? parsed.map : null);
299 });
300}
301exports.babelifyToFile = babelifyToFile;