ares-ide
Version:
A browser-based code editor and UI designer for Enyo 2 projects
388 lines (338 loc) • 11.9 kB
JavaScript
/* jshint node: true */
/**
# deploy.js - portable deployment script
This portable Node.js script minifies your application, its libraries,
and the Enyo framework it is using. The resulting application
is suitable for production usage, either hosted on a Web server or
embedded into a PhoneGap container.
The script is intended to be run from the application's root directory
(unlike previous, deprecated incarnations of the scripts `deploy.sh` and
`deploy.bat` that shipped within the `bootplate` application), where it
expects to find a file called `package.js` (unless the user specifies an
alternate location using the `-p` flag).
This script comes along with Enyo. It automatically uses and embeds the
Enyo version it is shipped with, unless the user specifies another
Enyo version using the `-e enyo_dir` flag.
This script uses the application manifest `deploy.json` if found. Here
is the content of a typical application manifest:
```json
{
"enyo": "./enyo",
"packagejs": "./package.js",
"assets": ["./icon.png", "./index.html", "./assets"],
"libs": ["./lib/onyx", "./lib/layout"]
}
```
When the application does not have a root-level `deploy.json` manifest
file, this script expects to find the following files and folders in the
application root directory. Each of them is copied verbatim into the
output production/deployment folder (the one optionally given using
the `-o` flag).
* `index.html`, the application startup page
* `icon.png`, the application icon
* `assets/`holds the static application assets, such as images,
videos, etc.
When your application has library dependencies (for example,
`lib/mylib`), this script uses the library manifest
`lib/mylib/deploy.json` (if present) or the old-fashioned deployment
scripts `lib/mylib/deploy.sh` (on Linux and Mac OSX) or
`lib/mylib/deploy.bat` (on Windows). If neither exists, then the
entire library is copied (except for the `.git`, `target` and `build`
directories).
Here is the content of a typical library manifest:
```json
{
"source": ".",
"assets": ["assets", "images", "README.txt", "LICENSE-2.0.txt"],
"libs": []
}
```
**NOTE:** When you change the default value of the build folder name
(using `--build` or `-b`) you must adapt the content of your
`index.html` accordingly.
**NOTE:** The test build folder (`./build` by default) and the output
folder (`PWD/deploy` by default) are recursively deleted each time this
command is run, so do not store any source code in either location.
*/
// Load dependencies
var nopt = require("nopt"),
path = require('path'),
fs = require('fs'),
shell = require('shelljs');
var stat, script;
// Send message to parent node process, if any
process.on('uncaughtException', function (err) {
var errMsg = err.toString() + err.stack;
console.error(errMsg);
if (process.send) {
// only available if parent process is node
process.send({error: errMsg});
}
process.exit(1);
});
// receive error messages from child node processes
process.on('message', function(msg) {
console.dir(msg);
if (msg.error && msg.error.stack) {
console.error(msg.error.stack);
}
if (process.send) {
process.send(msg);
}
});
// Parse arguments
var node = process.argv[0],
deploy = process.argv[1],
less = true, // LESS compilation, turned on by default
verbose = false,
beautify = false,
noexec = false;
function printUsage() {
// format generated using node-optimist...
console.log('\n' +
'Usage: ' + node + ' ' + deploy + ' [-c][-e enyo_dir][-l lib_dir][-b build_dir][-o out_dir][-p package_js][-s source_dir][-f map_from -t map_to ...]\n' +
'\n' +
'Options:\n' +
' -v verbose operation [boolean] [default: ' + verbose + ']\n' +
' -b build directory sub-folder [default: "./build"]\n' +
' -c do not run the LESS compiler [boolean] [default: ' + less + ']\n' +
' -e enyo framework sub-folder [default: "./enyo"]\n' +
' -l libs sub-folder [default: "./lib"]\n' +
' -o alternate output directory [default: "PWD/deploy/APPNAME"]\n' +
' -p main package.js file (relative) [default: "./package.js"]\n' +
' -s source code root directory [default: "PWD"]\n' +
' -B pretty-print (beautify) JS output [default: "' + beautify + '"]\n' +
' -f remote source mapping: from local path\n' +
' -t remote source mapping: to remote path\n' +
' -E|--noexec disallow execution of application-provided scripts [default: false]\n' +
' -T|--test generate a build directory in the source tree for testing [default: false]\n' +
'\n');
}
var opt = nopt(/*knownOpts*/ {
"build": String, // relative path
"less": Boolean,
"enyo": String, // relative path
"lib": String, // relative path
"out": path, // absolute path
"packagejs": String, // relative path
"source": path, // absolute path
"verbose": Boolean,
"help": Boolean,
"beautify": Boolean,
"noexec": Boolean,
"test": Boolean,
"mapfrom": [String, Array],
"mapto": [String, Array]
}, /*shortHands*/ {
"b": "--build",
"c": "--no-less",
"e": "--enyo",
"l": "--lib",
"o": "--out",
"p": "--packagejs",
"s": "--source",
"v": "--verbose",
"h": "--help",
"B": "--beautify",
"f": "--mapfrom",
"t": "--mapto",
"E": "--noexec",
"T": "--test",
"?": "--help"
}, process.argv /*args*/, 2 /*slice*/);
var log = function() {};
if (opt.verbose) {
log = console.log;
}
log("opt:", opt);
if (opt.help) {
printUsage();
process.exit(1);
}
if (!opt.source && opt.packagejs) {
// backward compatibility: use top-level package.js folder as
// source folder if provided & if no source folder is provided
opt.source = path.dirname(opt.packagejs);
opt.packagejs = path.basename(opt.packagejs);
}
opt.source = opt.source || process.cwd();
opt.app = opt.app || path.basename(opt.source);
opt.out = opt.out ? path.resolve(process.cwd(), opt.out) : path.join(process.cwd(), 'deploy', opt.app);
// deploy.js works only when running on top of the source tree...
process.chdir(opt.source);
// application default values may come from the manifest file: deploy.json
var manifest;
try {
manifest = JSON.parse(fs.readFileSync("deploy.json"));
} catch(e) {
manifest = {
_default: true
};
}
opt.packagejs = opt.packagejs || manifest.packagejs || "package.js";
opt.packagejs = path.relative(process.cwd(), opt.packagejs);
// top-level project folder, relative to top-level package.js
var rootFolder = path.relative(path.dirname(opt.packagejs), ".");
opt.build = opt.build || manifest.build || "build";
opt.enyo = opt.enyo || manifest.enyo || "enyo"; // from top-level folder
log("opt:", opt);
less = (opt.less !== false) && less;
beautify = opt.beautify;
noexec = opt.noexec;
verbose = opt.verbose;
if ((opt.mapfrom || opt.maptop) && (!opt.mapfrom || !opt.mapto || (opt.mapfrom.length != opt.mapto.length))) {
log("mapfrom:", opt.mapfrom);
log("mapto:", opt.mapto);
console.error("Error: The number of 'mapfrom' and 'mapto' arguments must match.");
process.exit(1);
}
var minifier = path.resolve(__dirname, 'minifier', 'minify.js');
log("Using: opt.source=" + opt.source);
log("Using: opt.out=" + opt.out);
log("Using: opt.build=" + opt.build);
log("Using: opt.enyo=" + opt.enyo);
log("Using: opt.packagejs=" + opt.packagejs);
log("Using: opt.test=" + opt.test);
log("Using: less=" + less);
log("Using: beautify=" + beautify);
log("Using: noexec=" + noexec);
// utils
function run(args) {
var command = '"' + args.join('" "') + '"';
var report;
log("% ", command);
report = shell.exec(command, { silent: !verbose });
if (report.code !== 0) {
throw new Error("Fail: '" + command + "'\n" + report.output);
}
}
// Prepare target directories
log("% rm -rf " + opt.out);
shell.rm('-rf', opt.out);
log("% mkdir -p " + opt.out + "/" + opt.build);
shell.mkdir('-p', path.join(opt.out, opt.build));
log("% rm -rf " + opt.build);
shell.rm('-rf', opt.build);
// Build / Minify
var args, i;
if (!opt.mapfrom || opt.mapfrom.indexOf("enyo") < 0) {
console.log("Minify-ing Enyo...");
args = [node, minifier,
'-no-alias',
'-enyo', opt.enyo,
'-destdir', opt.out,
'-output', path.join(opt.build, 'enyo'),
(beautify ? '-beautify' : '-no-beautify'),
path.join(opt.enyo, 'minify', 'package.js')];
if (opt.mapfrom) {
for (i=0; i<opt.mapfrom.length; i++) {
args.push("-f", opt.mapfrom[i], "-t", opt.mapto[i]);
}
}
run(args);
} else {
console.log("Skipping Enyo minification (will be mapped to " + opt.mapto[opt.mapfrom.indexOf("enyo")] + ").");
}
console.log("Minify-ing the application...");
args = [node, minifier,
// ENYODIR, from the the top-level package.js
'-enyo', path.join(rootFolder, opt.enyo),
'-destdir', opt.out,
'-output', path.join(opt.build, 'app'),
(less ? '-less' : '-no-less'),
(beautify ? '-beautify' : '-no-beautify'),
opt.packagejs];
if (opt.mapfrom) {
for (i=0; i<opt.mapfrom.length; i++) {
args.push("-f", opt.mapfrom[i], "-t", opt.mapto[i]);
}
}
if (opt.lib) {
// LIBPATH, from the top-level package.js
args.push("-lib", path.join(rootFolder, opt.lib));
}
run(args);
// Deploy / Copy
if(!manifest._default) {
/* Pick the list of files & folders to copy from the application manifest */
deployDir(".");
} else {
/* Use legacy built-in list of files & folders to copy */
shell.mkdir('-p', path.join(opt.out, 'lib'));
shell.cp('index.html', opt.out);
shell.cp('icon.png', opt.out);
if(shell.test('-d', 'assets')) {
shell.cp('-r', 'assets', opt.out);
}
if(shell.test('-d', 'lib')) {
shell.ls('lib').forEach(function(lib) {
var libDir = path.join("lib", lib);
if (opt.mapfrom && opt.mapfrom.indexOf(libDir) >= 0) {
// Don't deploy libraries that are loaded from elsewhere
console.log("Skipping:", libDir);
} else {
deployDir(libDir);
}
});
}
}
if (opt.test) {
log("Test build dir");
log("% cp -r " + path.join(opt.out, opt.build) + " .");
shell.cp('-r', path.join(opt.out, opt.build), ".");
}
function deployDir(subDir) {
log("Deploying '" + subDir + "'...");
try {
var manifest = JSON.parse(fs.readFileSync(path.join(subDir, "deploy.json")));
if (Array.isArray(manifest.assets)) {
manifest.assets.forEach(function(asset) {
var dstAssetDir = path.dirname(path.join(opt.out, subDir, asset));
log("% mkdir -p " + dstAssetDir);
shell.mkdir('-p', dstAssetDir);
log("% cp -r " + path.join(subDir, asset) + "...");
shell.cp('-r', path.join(subDir, asset), dstAssetDir);
});
}
if (Array.isArray(manifest.libs)) {
manifest.libs.forEach(function(libDir) {
deployDir(path.join(subDir, libDir));
});
}
} catch(e) {
// backward compatibility: run deploy.sh or deploy.bat
try {
if (noexec) {
console.warn("noexec: Skip execution of client-provided code");
throw new Error("noexec: Skip execution of client-provided code");
}
script = path.join(subDir, 'deploy.' + (process.platform === 'win32' ? 'bat' : 'sh'));
stat = fs.statSync(script);
if (!stat.isFile()) {
throw new Error("*** Not a file: '" + script + "'");
}
var command = [script, path.join(opt.out, subDir)];
if (process.platform !== 'win32') {
command.unshift("sh");
}
run(command, path.join(subDir));
} catch(e2) {
// no deploy.(js|bat|sh): copy everything...
var dd = path.dirname(path.join(opt.out, subDir));
log("% mkdir -p " + dd);
shell.mkdir('-p', dd);
var sd = subDir;
log("% cp -r " + sd + " " + dd);
shell.cp('-r', sd, dd);
// ...then remove ".git" & others, if any
log("% rm -rf " + dd + "/(.git|target|build|deploy)");
shell.rm('-rf', path.join(opt.out, subDir, '.git'));
shell.rm('-rf', path.join(opt.out, subDir, 'target'));
shell.rm('-rf', path.join(opt.out, subDir, 'build'));
shell.rm('-rf', path.join(opt.out, subDir, 'deploy'));
}
}
}
console.log("Success: the deployable application is available in: ", opt.out);
process.exit(0);