From 6d965c0926c7a72b9e64c370416cd7302255abc8 Mon Sep 17 00:00:00 2001 From: Brian Ng Date: Sat, 29 Jul 2017 21:26:28 -0500 Subject: [PATCH] Make babel-node a standalone package (#6023) * Make babel-node a standalone package * New package `babel-node` previously `babel-cli/bin/babel-node` * updates --- .gitignore | 1 + packages/babel-node/.npmignore | 3 + packages/babel-node/README.md | 17 ++ packages/babel-node/bin/babel-node.js | 3 + packages/babel-node/package.json | 34 +++ packages/babel-node/src/_babel-node.js | 186 +++++++++++++++ packages/babel-node/src/babel-node.js | 98 ++++++++ .../fixtures/babel-node/--eval/options.json | 4 + .../babel-node/--extensions/in-files/foo.bar | 1 + .../babel-node/--extensions/options.json | 4 + .../fixtures/babel-node/--print/options.json | 4 + .../babel-node/arguments/in-files/bar.js | 1 + .../babel-node/arguments/options.json | 4 + .../directory/in-files/foo/index.js | 2 + .../babel-node/directory/options.json | 3 + .../fixtures/babel-node/directory/stdout.txt | 1 + .../babel-node/filename/in-files/bar.js | 2 + .../fixtures/babel-node/filename/options.json | 3 + .../fixtures/babel-node/filename/stdout.txt | 1 + .../babel-node/no-strict/options.json | 4 + .../babel-node/require/in-files/bar2.js | 2 + .../babel-node/require/in-files/foo2.js | 5 + .../require/in-files/not_node_modules.jsx | 10 + .../fixtures/babel-node/require/options.json | 3 + .../fixtures/babel-node/require/stdout.txt | 2 + .../v8Flag-dashed-with-param/options.json | 4 + .../babel-node/v8Flag-dashed/options.json | 4 + .../options.json | 4 + .../v8Flag-underscored/options.json | 4 + packages/babel-node/test/index.js | 213 ++++++++++++++++++ 30 files changed, 627 insertions(+) create mode 100644 packages/babel-node/.npmignore create mode 100644 packages/babel-node/README.md create mode 100755 packages/babel-node/bin/babel-node.js create mode 100644 packages/babel-node/package.json create mode 100644 packages/babel-node/src/_babel-node.js create mode 100755 packages/babel-node/src/babel-node.js create mode 100644 packages/babel-node/test/fixtures/babel-node/--eval/options.json create mode 100644 packages/babel-node/test/fixtures/babel-node/--extensions/in-files/foo.bar create mode 100644 packages/babel-node/test/fixtures/babel-node/--extensions/options.json create mode 100644 packages/babel-node/test/fixtures/babel-node/--print/options.json create mode 100644 packages/babel-node/test/fixtures/babel-node/arguments/in-files/bar.js create mode 100644 packages/babel-node/test/fixtures/babel-node/arguments/options.json create mode 100644 packages/babel-node/test/fixtures/babel-node/directory/in-files/foo/index.js create mode 100644 packages/babel-node/test/fixtures/babel-node/directory/options.json create mode 100644 packages/babel-node/test/fixtures/babel-node/directory/stdout.txt create mode 100644 packages/babel-node/test/fixtures/babel-node/filename/in-files/bar.js create mode 100644 packages/babel-node/test/fixtures/babel-node/filename/options.json create mode 100644 packages/babel-node/test/fixtures/babel-node/filename/stdout.txt create mode 100644 packages/babel-node/test/fixtures/babel-node/no-strict/options.json create mode 100644 packages/babel-node/test/fixtures/babel-node/require/in-files/bar2.js create mode 100644 packages/babel-node/test/fixtures/babel-node/require/in-files/foo2.js create mode 100644 packages/babel-node/test/fixtures/babel-node/require/in-files/not_node_modules.jsx create mode 100644 packages/babel-node/test/fixtures/babel-node/require/options.json create mode 100644 packages/babel-node/test/fixtures/babel-node/require/stdout.txt create mode 100644 packages/babel-node/test/fixtures/babel-node/v8Flag-dashed-with-param/options.json create mode 100644 packages/babel-node/test/fixtures/babel-node/v8Flag-dashed/options.json create mode 100644 packages/babel-node/test/fixtures/babel-node/v8Flag-underscored-with-param/options.json create mode 100644 packages/babel-node/test/fixtures/babel-node/v8Flag-underscored/options.json create mode 100644 packages/babel-node/test/index.js diff --git a/.gitignore b/.gitignore index 841ad562a3..f0052bad05 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ package-lock.json !/packages/babel-runtime/helpers/es6/toArray.js /packages/babel-register/test/.babel /packages/babel-cli/test/tmp +/packages/babel-node/test/tmp /packages/*/lib .nyc_output /babel.sublime-workspace diff --git a/packages/babel-node/.npmignore b/packages/babel-node/.npmignore new file mode 100644 index 0000000000..47cdd2c655 --- /dev/null +++ b/packages/babel-node/.npmignore @@ -0,0 +1,3 @@ +src +test +node_modules diff --git a/packages/babel-node/README.md b/packages/babel-node/README.md new file mode 100644 index 0000000000..e5d93a6ba2 --- /dev/null +++ b/packages/babel-node/README.md @@ -0,0 +1,17 @@ +# babel-node + +> A Babel-powered Node.js CLI + +babel-node is a CLI that works exactly the same as the Node.js CLI, with the added benefit of compiling with Babel presets and plugins before running it. + +## Install + +```sh +npm install --save-dev babel-node +``` + +## Usage + +```sh +babel-node [options] [ -e script | script.js ] [arguments] +``` diff --git a/packages/babel-node/bin/babel-node.js b/packages/babel-node/bin/babel-node.js new file mode 100755 index 0000000000..c20e9befb8 --- /dev/null +++ b/packages/babel-node/bin/babel-node.js @@ -0,0 +1,3 @@ +#!/usr/bin/env node + +require("../lib/babel-node"); diff --git a/packages/babel-node/package.json b/packages/babel-node/package.json new file mode 100644 index 0000000000..59b27ee6be --- /dev/null +++ b/packages/babel-node/package.json @@ -0,0 +1,34 @@ +{ + "name": "babel-node", + "version": "7.0.0-alpha.17", + "description": "Babel command line", + "author": "Sebastian McKenzie ", + "homepage": "https://babeljs.io/", + "license": "MIT", + "repository": "https://github.com/babel/babel/tree/master/packages/babel-node", + "keywords": [ + "6to5", + "babel", + "es6", + "transpile", + "transpiler", + "babel-cli", + "compiler" + ], + "dependencies": { + "babel-core": "7.0.0-alpha.17", + "babel-register": "7.0.0-alpha.17", + "babel-polyfill": "7.0.0-alpha.17", + "commander": "^2.8.1", + "fs-readdir-recursive": "^1.0.0", + "lodash": "^4.2.0", + "output-file-sync": "^2.0.0", + "v8flags": "^3.0.0" + }, + "devDependencies": { + "babel-helper-fixtures": "7.0.0-alpha.17" + }, + "bin": { + "babel-node": "./bin/babel-node.js" + } +} diff --git a/packages/babel-node/src/_babel-node.js b/packages/babel-node/src/_babel-node.js new file mode 100644 index 0000000000..09093d63cd --- /dev/null +++ b/packages/babel-node/src/_babel-node.js @@ -0,0 +1,186 @@ +import commander from "commander"; +import Module from "module"; +import { inspect } from "util"; +import path from "path"; +import repl from "repl"; +import * as babel from "babel-core"; +import vm from "vm"; +import "babel-polyfill"; +import register from "babel-register"; + +import pkg from "../package.json"; + +const program = new commander.Command("babel-node"); + +function collect(value, previousValue): Array { + // If the user passed the option with no value, like "babel-node file.js --presets", do nothing. + if (typeof value !== "string") return previousValue; + + const values = value.split(","); + + return previousValue ? previousValue.concat(values) : values; +} + +/* eslint-disable max-len */ +program.option("-e, --eval [script]", "Evaluate script"); +program.option("-p, --print [code]", "Evaluate script and print result"); +program.option( + "-o, --only [globs]", + "A comma-separated list of glob patterns to compile", + collect, +); +program.option( + "-i, --ignore [globs]", + "A comma-separated list of glob patterns to skip compiling", + collect, +); +program.option( + "-x, --extensions [extensions]", + "List of extensions to hook into [.es6,.js,.es,.jsx,.mjs]", + collect, +); +program.option("-w, --plugins [string]", "", collect); +program.option("-b, --presets [string]", "", collect); +/* eslint-enable max-len */ + +program.version(pkg.version); +program.usage("[options] [ -e script | script.js ] [arguments]"); +program.parse(process.argv); + +register({ + extensions: program.extensions, + ignore: program.ignore, + only: program.only, + plugins: program.plugins, + presets: program.presets, +}); + +const replPlugin = ({ types: t }) => ({ + visitor: { + ModuleDeclaration(path) { + throw path.buildCodeFrameError("Modules aren't supported in the REPL"); + }, + + VariableDeclaration(path) { + if (path.node.kind !== "var") { + throw path.buildCodeFrameError( + "Only `var` variables are supported in the REPL", + ); + } + }, + + Program(path) { + if (path.get("body").some(child => child.isExpressionStatement())) return; + + // If the executed code doesn't evaluate to a value, + // prevent implicit strict mode from printing 'use strict'. + path.pushContainer( + "body", + t.expressionStatement(t.identifier("undefined")), + ); + }, + }, +}); + +const _eval = function(code, filename) { + code = code.trim(); + if (!code) return undefined; + + code = babel.transform(code, { + filename: filename, + presets: program.presets, + plugins: (program.plugins || []).concat([replPlugin]), + }).code; + + return vm.runInThisContext(code, { + filename: filename, + }); +}; + +if (program.eval || program.print) { + let code = program.eval; + if (!code || code === true) code = program.print; + + global.__filename = "[eval]"; + global.__dirname = process.cwd(); + + const module = new Module(global.__filename); + module.filename = global.__filename; + module.paths = Module._nodeModulePaths(global.__dirname); + + global.exports = module.exports; + global.module = module; + global.require = module.require.bind(module); + + const result = _eval(code, global.__filename); + if (program.print) { + const output = typeof result === "string" ? result : inspect(result); + process.stdout.write(output + "\n"); + } +} else { + if (program.args.length) { + // slice all arguments up to the first filename since they're babel args that we handle + let args = process.argv.slice(2); + + let i = 0; + let ignoreNext = false; + args.some(function(arg, i2) { + if (ignoreNext) { + ignoreNext = false; + return; + } + + if (arg[0] === "-") { + const parsedArg = program[arg.slice(2)]; + if (parsedArg && parsedArg !== true) { + ignoreNext = true; + } + } else { + i = i2; + return true; + } + }); + args = args.slice(i); + + // make the filename absolute + const filename = args[0]; + if (!path.isAbsolute(filename)) { + args[0] = path.join(process.cwd(), filename); + } + + // add back on node and concat the sliced args + process.argv = ["node"].concat(args); + process.execArgv.unshift(__filename); + + Module.runMain(); + } else { + replStart(); + } +} + +function replStart() { + repl.start({ + prompt: "> ", + input: process.stdin, + output: process.stdout, + eval: replEval, + useGlobal: true, + }); +} + +function replEval(code, context, filename, callback) { + let err; + let result; + + try { + if (code[0] === "(" && code[code.length - 1] === ")") { + code = code.slice(1, -1); // remove "(" and ")" + } + + result = _eval(code, filename); + } catch (e) { + err = e; + } + + callback(err, result); +} diff --git a/packages/babel-node/src/babel-node.js b/packages/babel-node/src/babel-node.js new file mode 100755 index 0000000000..cc1ed88c32 --- /dev/null +++ b/packages/babel-node/src/babel-node.js @@ -0,0 +1,98 @@ +/** + * This tiny wrapper file checks for known node flags and appends them + * when found, before invoking the "real" _babel-node(1) executable. + */ + +import getV8Flags from "v8flags"; +import path from "path"; + +let args = [path.join(__dirname, "_babel-node")]; + +let babelArgs = process.argv.slice(2); +let userArgs; + +// separate node arguments from script arguments +const argSeparator = babelArgs.indexOf("--"); +if (argSeparator > -1) { + userArgs = babelArgs.slice(argSeparator); // including the -- + babelArgs = babelArgs.slice(0, argSeparator); +} + +/** + * Replace dashes with underscores in the v8Flag name + * Also ensure that if the arg contains a value (e.g. --arg=true) + * that only the flag is returned. + */ +function getNormalizedV8Flag(arg) { + const matches = arg.match(/--(.+)/); + + if (matches) { + return `--${matches[1].replace(/-/g, "_")}`; + } + + return arg; +} + +getV8Flags(function(err, v8Flags) { + babelArgs.forEach(function(arg) { + const flag = arg.split("=")[0]; + + switch (flag) { + case "-d": + args.unshift("--debug"); + break; + + case "debug": + case "--debug": + case "--debug-brk": + case "--inspect": + args.unshift(arg); + break; + + case "-gc": + args.unshift("--expose-gc"); + break; + + case "--nolazy": + args.unshift(flag); + break; + + default: + if ( + v8Flags.indexOf(getNormalizedV8Flag(flag)) >= 0 || + arg.indexOf("--trace") === 0 + ) { + args.unshift(arg); + } else { + args.push(arg); + } + break; + } + }); + + // append arguments passed after -- + if (argSeparator > -1) { + args = args.concat(userArgs); + } + + try { + const kexec = require("kexec"); + kexec(process.argv[0], args); + } catch (err) { + if (err.code !== "MODULE_NOT_FOUND") throw err; + + const child_process = require("child_process"); + const proc = child_process.spawn(process.argv[0], args, { + stdio: "inherit", + }); + proc.on("exit", function(code, signal) { + process.on("exit", function() { + if (signal) { + process.kill(process.pid, signal); + } else { + process.exit(code); + } + }); + }); + } +}); diff --git a/packages/babel-node/test/fixtures/babel-node/--eval/options.json b/packages/babel-node/test/fixtures/babel-node/--eval/options.json new file mode 100644 index 0000000000..09d27772df --- /dev/null +++ b/packages/babel-node/test/fixtures/babel-node/--eval/options.json @@ -0,0 +1,4 @@ +{ + "args": ["--eval", "console.log([1, 2, 3].map(x => x * x));"], + "stdout": "[ 1, 4, 9 ]" +} diff --git a/packages/babel-node/test/fixtures/babel-node/--extensions/in-files/foo.bar b/packages/babel-node/test/fixtures/babel-node/--extensions/in-files/foo.bar new file mode 100644 index 0000000000..9d023628ac --- /dev/null +++ b/packages/babel-node/test/fixtures/babel-node/--extensions/in-files/foo.bar @@ -0,0 +1 @@ +console.log([1, 2, 3].map(x => x * x)); diff --git a/packages/babel-node/test/fixtures/babel-node/--extensions/options.json b/packages/babel-node/test/fixtures/babel-node/--extensions/options.json new file mode 100644 index 0000000000..c64999d8e2 --- /dev/null +++ b/packages/babel-node/test/fixtures/babel-node/--extensions/options.json @@ -0,0 +1,4 @@ +{ + "args": ["foo", "--extensions", ".bar"], + "stdout": "[ 1, 4, 9 ]" +} diff --git a/packages/babel-node/test/fixtures/babel-node/--print/options.json b/packages/babel-node/test/fixtures/babel-node/--print/options.json new file mode 100644 index 0000000000..df235fd4d2 --- /dev/null +++ b/packages/babel-node/test/fixtures/babel-node/--print/options.json @@ -0,0 +1,4 @@ +{ + "args": ["--print", "--eval", "([1, 2, 3].map(x => x * x))"], + "stdout": "[ 1, 4, 9 ]" +} diff --git a/packages/babel-node/test/fixtures/babel-node/arguments/in-files/bar.js b/packages/babel-node/test/fixtures/babel-node/arguments/in-files/bar.js new file mode 100644 index 0000000000..13257ec5e8 --- /dev/null +++ b/packages/babel-node/test/fixtures/babel-node/arguments/in-files/bar.js @@ -0,0 +1 @@ +console.log(process.argv[2]); diff --git a/packages/babel-node/test/fixtures/babel-node/arguments/options.json b/packages/babel-node/test/fixtures/babel-node/arguments/options.json new file mode 100644 index 0000000000..5aa934d555 --- /dev/null +++ b/packages/babel-node/test/fixtures/babel-node/arguments/options.json @@ -0,0 +1,4 @@ +{ + "args": ["bar", "foo"], + "stdout": "foo" +} diff --git a/packages/babel-node/test/fixtures/babel-node/directory/in-files/foo/index.js b/packages/babel-node/test/fixtures/babel-node/directory/in-files/foo/index.js new file mode 100644 index 0000000000..6cbc6fbcfb --- /dev/null +++ b/packages/babel-node/test/fixtures/babel-node/directory/in-files/foo/index.js @@ -0,0 +1,2 @@ +var foo = () => console.log("foo"); +foo(); diff --git a/packages/babel-node/test/fixtures/babel-node/directory/options.json b/packages/babel-node/test/fixtures/babel-node/directory/options.json new file mode 100644 index 0000000000..36b991ad38 --- /dev/null +++ b/packages/babel-node/test/fixtures/babel-node/directory/options.json @@ -0,0 +1,3 @@ +{ + "args": ["foo"] +} diff --git a/packages/babel-node/test/fixtures/babel-node/directory/stdout.txt b/packages/babel-node/test/fixtures/babel-node/directory/stdout.txt new file mode 100644 index 0000000000..257cc5642c --- /dev/null +++ b/packages/babel-node/test/fixtures/babel-node/directory/stdout.txt @@ -0,0 +1 @@ +foo diff --git a/packages/babel-node/test/fixtures/babel-node/filename/in-files/bar.js b/packages/babel-node/test/fixtures/babel-node/filename/in-files/bar.js new file mode 100644 index 0000000000..6cbc6fbcfb --- /dev/null +++ b/packages/babel-node/test/fixtures/babel-node/filename/in-files/bar.js @@ -0,0 +1,2 @@ +var foo = () => console.log("foo"); +foo(); diff --git a/packages/babel-node/test/fixtures/babel-node/filename/options.json b/packages/babel-node/test/fixtures/babel-node/filename/options.json new file mode 100644 index 0000000000..d9bcfc2f57 --- /dev/null +++ b/packages/babel-node/test/fixtures/babel-node/filename/options.json @@ -0,0 +1,3 @@ +{ + "args": ["bar"] +} diff --git a/packages/babel-node/test/fixtures/babel-node/filename/stdout.txt b/packages/babel-node/test/fixtures/babel-node/filename/stdout.txt new file mode 100644 index 0000000000..257cc5642c --- /dev/null +++ b/packages/babel-node/test/fixtures/babel-node/filename/stdout.txt @@ -0,0 +1 @@ +foo diff --git a/packages/babel-node/test/fixtures/babel-node/no-strict/options.json b/packages/babel-node/test/fixtures/babel-node/no-strict/options.json new file mode 100644 index 0000000000..63fc4b34f4 --- /dev/null +++ b/packages/babel-node/test/fixtures/babel-node/no-strict/options.json @@ -0,0 +1,4 @@ +{ + "args": ["--eval","--print", "var a = 1;"], + "stdout": "undefined" +} diff --git a/packages/babel-node/test/fixtures/babel-node/require/in-files/bar2.js b/packages/babel-node/test/fixtures/babel-node/require/in-files/bar2.js new file mode 100644 index 0000000000..5e0f4fd877 --- /dev/null +++ b/packages/babel-node/test/fixtures/babel-node/require/in-files/bar2.js @@ -0,0 +1,2 @@ +var bar = () => console.log("bar"); +bar(); diff --git a/packages/babel-node/test/fixtures/babel-node/require/in-files/foo2.js b/packages/babel-node/test/fixtures/babel-node/require/in-files/foo2.js new file mode 100644 index 0000000000..71db6cd5b2 --- /dev/null +++ b/packages/babel-node/test/fixtures/babel-node/require/in-files/foo2.js @@ -0,0 +1,5 @@ +import "./bar2"; +import "./not_node_modules"; + +var foo = () => console.log("foo"); +foo(); diff --git a/packages/babel-node/test/fixtures/babel-node/require/in-files/not_node_modules.jsx b/packages/babel-node/test/fixtures/babel-node/require/in-files/not_node_modules.jsx new file mode 100644 index 0000000000..f14bae58e1 --- /dev/null +++ b/packages/babel-node/test/fixtures/babel-node/require/in-files/not_node_modules.jsx @@ -0,0 +1,10 @@ +/* +The purpose of this file is to test that the node_modules check in the require +hook doesn't mistakenly exclude something like "not_node_modules". To pass, this +file merely needs to be transpiled. The transpiled code won't, and doesn't need +to, execute without error. It won't execute because React will be undefined. +*/ +try { + ; +} +catch (e) {} diff --git a/packages/babel-node/test/fixtures/babel-node/require/options.json b/packages/babel-node/test/fixtures/babel-node/require/options.json new file mode 100644 index 0000000000..988cc017dc --- /dev/null +++ b/packages/babel-node/test/fixtures/babel-node/require/options.json @@ -0,0 +1,3 @@ +{ + "args": ["foo2"] +} diff --git a/packages/babel-node/test/fixtures/babel-node/require/stdout.txt b/packages/babel-node/test/fixtures/babel-node/require/stdout.txt new file mode 100644 index 0000000000..128976523b --- /dev/null +++ b/packages/babel-node/test/fixtures/babel-node/require/stdout.txt @@ -0,0 +1,2 @@ +bar +foo diff --git a/packages/babel-node/test/fixtures/babel-node/v8Flag-dashed-with-param/options.json b/packages/babel-node/test/fixtures/babel-node/v8Flag-dashed-with-param/options.json new file mode 100644 index 0000000000..a9cabbc10c --- /dev/null +++ b/packages/babel-node/test/fixtures/babel-node/v8Flag-dashed-with-param/options.json @@ -0,0 +1,4 @@ +{ + "args": ["--expose-gc-as=garbageCollector", "--eval", "console.log(typeof global.garbageCollector)"], + "stdout": "function" +} diff --git a/packages/babel-node/test/fixtures/babel-node/v8Flag-dashed/options.json b/packages/babel-node/test/fixtures/babel-node/v8Flag-dashed/options.json new file mode 100644 index 0000000000..82ff74d156 --- /dev/null +++ b/packages/babel-node/test/fixtures/babel-node/v8Flag-dashed/options.json @@ -0,0 +1,4 @@ +{ + "args": ["--expose-gc", "--eval", "console.log(typeof global.gc)"], + "stdout": "function" +} diff --git a/packages/babel-node/test/fixtures/babel-node/v8Flag-underscored-with-param/options.json b/packages/babel-node/test/fixtures/babel-node/v8Flag-underscored-with-param/options.json new file mode 100644 index 0000000000..a8b0e91738 --- /dev/null +++ b/packages/babel-node/test/fixtures/babel-node/v8Flag-underscored-with-param/options.json @@ -0,0 +1,4 @@ +{ + "args": ["--expose_gc_as=garbageCollector", "--eval", "console.log(typeof global.garbageCollector)"], + "stdout": "function" +} diff --git a/packages/babel-node/test/fixtures/babel-node/v8Flag-underscored/options.json b/packages/babel-node/test/fixtures/babel-node/v8Flag-underscored/options.json new file mode 100644 index 0000000000..c279b0e13d --- /dev/null +++ b/packages/babel-node/test/fixtures/babel-node/v8Flag-underscored/options.json @@ -0,0 +1,4 @@ +{ + "args": ["--expose_gc", "--eval", "console.log(typeof global.gc)"], + "stdout": "function" +} diff --git a/packages/babel-node/test/index.js b/packages/babel-node/test/index.js new file mode 100644 index 0000000000..47ed8b1a3a --- /dev/null +++ b/packages/babel-node/test/index.js @@ -0,0 +1,213 @@ +const includes = require("lodash/includes"); +const readdir = require("fs-readdir-recursive"); +const helper = require("babel-helper-fixtures"); +const assert = require("assert"); +const rimraf = require("rimraf"); +const outputFileSync = require("output-file-sync"); +const child = require("child_process"); +const merge = require("lodash/merge"); +const path = require("path"); +const chai = require("chai"); +const fs = require("fs"); + +const fixtureLoc = path.join(__dirname, "fixtures"); +const tmpLoc = path.join(__dirname, "tmp"); + +const fileFilter = function(x) { + return x !== ".DS_Store"; +}; + +const presetLocs = [ + path.join(__dirname, "../../babel-preset-es2015"), + path.join(__dirname, "../../babel-preset-react"), +].join(","); + +const pluginLocs = [ + path.join(__dirname, "/../../babel-plugin-transform-strict-mode"), + path.join(__dirname, "/../../babel-plugin-transform-es2015-modules-commonjs"), +].join(","); + +const readDir = function(loc, filter) { + const files = {}; + if (fs.existsSync(loc)) { + readdir(loc, filter).forEach(function(filename) { + files[filename] = helper.readFile(path.join(loc, filename)); + }); + } + return files; +}; + +const saveInFiles = function(files) { + // Place an empty .babelrc in each test so tests won't unexpectedly get to repo-level config. + outputFileSync(".babelrc", "{}"); + + Object.keys(files).forEach(function(filename) { + const content = files[filename]; + outputFileSync(filename, content); + }); +}; + +const assertTest = function(stdout, stderr, opts) { + const expectStderr = opts.stderr.trim(); + stderr = stderr.trim(); + + if (opts.stderr) { + if (opts.stderrContains) { + assert.ok( + includes(stderr, expectStderr), + "stderr " + + JSON.stringify(stderr) + + " didn't contain " + + JSON.stringify(expectStderr), + ); + } else { + chai.expect(stderr).to.equal(expectStderr, "stderr didn't match"); + } + } else if (stderr) { + throw new Error("stderr:\n" + stderr); + } + + const expectStdout = opts.stdout.trim(); + stdout = stdout.trim(); + stdout = stdout.replace(/\\/g, "/"); + + if (opts.stdout) { + if (opts.stdoutContains) { + assert.ok( + includes(stdout, expectStdout), + "stdout " + + JSON.stringify(stdout) + + " didn't contain " + + JSON.stringify(expectStdout), + ); + } else { + chai.expect(stdout).to.equal(expectStdout, "stdout didn't match"); + } + } else if (stdout) { + throw new Error("stdout:\n" + stdout); + } + + if (opts.outFiles) { + const actualFiles = readDir(path.join(tmpLoc)); + + Object.keys(actualFiles).forEach(function(filename) { + if (!opts.inFiles.hasOwnProperty(filename)) { + const expect = opts.outFiles[filename]; + const actual = actualFiles[filename]; + + chai.expect(expect, "Output is missing: " + filename).to.not.be + .undefined; + + if (expect) { + chai + .expect(actual) + .to.equal(expect, "Compiled output does not match: " + filename); + } + } + }); + + Object.keys(opts.outFiles).forEach(function(filename) { + chai + .expect(actualFiles, "Extraneous file in output: " + filename) + .to.contain.key(filename); + }); + } +}; + +const buildTest = function(binName, testName, opts) { + const binLoc = path.join(__dirname, "../lib", binName); + + return function(callback) { + clear(); + saveInFiles(opts.inFiles); + + let args = [binLoc]; + + args.push("--presets", presetLocs, "--plugins", pluginLocs); + args.push("--only", "../../../../packages/*/test"); + + args = args.concat(opts.args); + + const spawn = child.spawn(process.execPath, args); + + let stderr = ""; + let stdout = ""; + + spawn.stderr.on("data", function(chunk) { + stderr += chunk; + }); + + spawn.stdout.on("data", function(chunk) { + stdout += chunk; + }); + + spawn.on("close", function() { + let err; + + try { + assertTest(stdout, stderr, opts); + } catch (e) { + err = e; + } + + if (err) { + err.message = + args.map(arg => `"${arg}"`).join(" ") + ": " + err.message; + } + + callback(err); + }); + + if (opts.stdin) { + spawn.stdin.write(opts.stdin); + spawn.stdin.end(); + } + }; +}; + +const clear = function() { + process.chdir(__dirname); + if (fs.existsSync(tmpLoc)) rimraf.sync(tmpLoc); + fs.mkdirSync(tmpLoc); + process.chdir(tmpLoc); +}; + +fs.readdirSync(fixtureLoc).forEach(function(binName) { + if (binName[0] === ".") return; + + const suiteLoc = path.join(fixtureLoc, binName); + describe("bin/" + binName, function() { + fs.readdirSync(suiteLoc).forEach(function(testName) { + if (testName[0] === ".") return; + + const testLoc = path.join(suiteLoc, testName); + + const opts = { + args: [], + }; + + const optionsLoc = path.join(testLoc, "options.json"); + if (fs.existsSync(optionsLoc)) merge(opts, require(optionsLoc)); + + ["stdout", "stdin", "stderr"].forEach(function(key) { + const loc = path.join(testLoc, key + ".txt"); + if (fs.existsSync(loc)) { + opts[key] = helper.readFile(loc); + } else { + opts[key] = opts[key] || ""; + } + }); + + opts.outFiles = readDir(path.join(testLoc, "out-files"), fileFilter); + opts.inFiles = readDir(path.join(testLoc, "in-files"), fileFilter); + + const babelrcLoc = path.join(testLoc, ".babelrc"); + if (fs.existsSync(babelrcLoc)) { + // copy .babelrc file to tmp directory + opts.inFiles[".babelrc"] = helper.readFile(babelrcLoc); + } + + it(testName, buildTest(binName, testName, opts)); + }); + }); +});