mirror of
https://github.com/bluxmit/alnoda-workspaces.git
synced 2024-09-16 01:37:08 +12:00
342 lines
11 KiB
JavaScript
Executable file
342 lines
11 KiB
JavaScript
Executable file
#!/usr/bin/env node
|
|
|
|
// Build Tools Module -- included by build.js.
|
|
// Copies, symlinks and compresses files into the right locations.
|
|
// Can also compact & bundle JS/CSS together for distribution.
|
|
// Copyright (c) 2015 Joseph Huckaby, MIT License.
|
|
|
|
var path = require('path');
|
|
var fs = require('fs');
|
|
var zlib = require('zlib');
|
|
var util = require('util');
|
|
var os = require('os');
|
|
var cp = require('child_process');
|
|
|
|
var mkdirp = require('mkdirp');
|
|
var async = require('async');
|
|
var glob = require('glob');
|
|
var UglifyJS = require("uglify-js");
|
|
var Tools = require('pixl-tools');
|
|
|
|
var fileStatSync = function(file) {
|
|
// no-throw version of fs.statSync
|
|
var stats = null;
|
|
try { stats = fs.statSync(file); }
|
|
catch (err) { return false; }
|
|
return stats;
|
|
};
|
|
|
|
var fileExistsSync = function(file) {
|
|
// replacement for fs.existsSync which is being removed
|
|
return !!fileStatSync(file);
|
|
};
|
|
|
|
var symlinkFile = exports.symlinkFile = function symlinkFile( old_file, new_file, callback ) {
|
|
// create symlink to file
|
|
// Note: 'new_file' is the object that will be created on the filesystem
|
|
// 'old_file' should already exist, and is the file being pointed to
|
|
if (new_file.match(/\/$/)) new_file += path.basename(old_file);
|
|
|
|
// if target exists and is not a symlink, skip this
|
|
try {
|
|
var stats = fs.lstatSync(new_file);
|
|
if (!stats.isSymbolicLink()) return callback();
|
|
}
|
|
catch (e) {;}
|
|
|
|
console.log( "Symlink: " + old_file + " --> " + new_file );
|
|
|
|
try { fs.unlinkSync( new_file ); }
|
|
catch (e) {;}
|
|
|
|
if (!fileExistsSync( path.dirname(new_file) )) {
|
|
mkdirp.sync( path.dirname(new_file) );
|
|
}
|
|
|
|
// fs.symlink takes a STRING (not a file path per se) as the old (existing) file,
|
|
// and it needs to be relative from new_file. So we need to resolve some things.
|
|
var sym_new = path.resolve( new_file );
|
|
var sym_old = path.relative( path.dirname(sym_new), path.resolve(old_file) );
|
|
|
|
fs.symlink( sym_old, sym_new, callback );
|
|
};
|
|
|
|
var copyFile = exports.copyFile = function copyFile( old_file, new_file, callback ) {
|
|
// copy file
|
|
if (new_file.match(/\/$/)) new_file += path.basename(old_file);
|
|
console.log( "Copy: " + old_file + " --> " + new_file );
|
|
|
|
try { fs.unlinkSync( new_file ); }
|
|
catch (e) {;}
|
|
|
|
if (!fileExistsSync( path.dirname(new_file) )) {
|
|
mkdirp.sync( path.dirname(new_file) );
|
|
}
|
|
|
|
var inp = fs.createReadStream(old_file);
|
|
var outp = fs.createWriteStream(new_file);
|
|
inp.on('end', callback );
|
|
inp.pipe( outp );
|
|
};
|
|
|
|
var copyFiles = exports.copyFiles = function copyFiles( src_spec, dest_dir, callback ) {
|
|
// copy multiple files to destination directory using filesystem globbing
|
|
dest_dir = dest_dir.replace(/\/$/, '');
|
|
|
|
glob(src_spec, {}, function (err, files) {
|
|
// got files
|
|
if (files && files.length) {
|
|
async.eachSeries( files, function(src_file, callback) {
|
|
// foreach file
|
|
var stats = fileStatSync(src_file);
|
|
if (stats && stats.isFile()) {
|
|
copyFile( src_file, dest_dir + '/', callback );
|
|
}
|
|
else callback();
|
|
}, callback );
|
|
} // got files
|
|
else {
|
|
callback( err || new Error("No files found matching: " + src_spec) );
|
|
}
|
|
} );
|
|
};
|
|
|
|
var compressFile = exports.compressFile = function compressFile( src_file, gz_file, callback ) {
|
|
// gzip compress file
|
|
console.log( "Compress: " + src_file + " --> " + gz_file );
|
|
|
|
if (fileExistsSync(gz_file)) {
|
|
fs.unlinkSync( gz_file );
|
|
}
|
|
|
|
var gzip = zlib.createGzip();
|
|
var inp = fs.createReadStream( src_file );
|
|
var outp = fs.createWriteStream( gz_file );
|
|
inp.on('end', callback );
|
|
inp.pipe(gzip).pipe(outp);
|
|
};
|
|
|
|
var copyCompress = exports.copyCompress = function copyCompress( old_file, new_file, callback ) {
|
|
// copy file and create gzip version as well
|
|
if (new_file.match(/\/$/)) new_file += path.basename(old_file);
|
|
|
|
copyFile( old_file, new_file, function(err) {
|
|
if (err) return callback(err);
|
|
|
|
// Make a compressed copy, so node-static will serve it up to browsers
|
|
compressFile( old_file, new_file + '.gz', callback );
|
|
} );
|
|
};
|
|
|
|
var symlinkCompress = exports.symlinkCompress = function symlinkCompress( old_file, new_file, callback ) {
|
|
// symlink file and create gzip version as well
|
|
if (new_file.match(/\/$/)) new_file += path.basename(old_file);
|
|
|
|
symlinkFile( old_file, new_file, function(err) {
|
|
if (err) return callback(err);
|
|
|
|
// Make a compressed copy, so node-static will serve it up to browsers
|
|
compressFile( old_file, new_file + '.gz', callback );
|
|
} );
|
|
};
|
|
|
|
var deleteFile = exports.deleteFile = function deleteFile( file, callback ) {
|
|
// delete file
|
|
console.log( "Delete: " + file );
|
|
|
|
if (fileExistsSync(file)) {
|
|
fs.unlink( file, callback );
|
|
}
|
|
else callback();
|
|
};
|
|
|
|
var deleteFiles = exports.deleteFiles = function deleteFiles( spec, callback ) {
|
|
// delete multiple files using filesystem globbing
|
|
glob(spec, {}, function (err, files) {
|
|
// got files
|
|
if (files && files.length) {
|
|
async.eachSeries( files, function(file, callback) {
|
|
// foreach file
|
|
deleteFile( file, callback );
|
|
}, callback );
|
|
} // got files
|
|
else {
|
|
callback( err );
|
|
}
|
|
} );
|
|
};
|
|
|
|
var chmodFiles = exports.chmodFiles = function chmodFiles( mode, spec, callback ) {
|
|
// chmod multiple files to specified mode using filesystem globbing
|
|
glob(spec, {}, function (err, files) {
|
|
// got files
|
|
if (files && files.length) {
|
|
async.eachSeries( files, function(file, callback) {
|
|
// foreach file
|
|
fs.chmod( file, mode, callback );
|
|
}, callback );
|
|
} // got files
|
|
else {
|
|
callback( err || new Error("No files found matching: " + spec) );
|
|
}
|
|
} );
|
|
};
|
|
|
|
var copyDir = exports.copyDir = function copyDir( src_dir, dest_dir, exclusive, callback ) {
|
|
// recursively copy dir and contents, optionally with exclusive mode
|
|
// symlinks are followed, and the target is copied instead
|
|
var src_spec = src_dir + '/*';
|
|
|
|
// exclusive means skip if dest exists (do not replace)
|
|
if (exclusive && fileExistsSync(dest_dir)) return callback();
|
|
|
|
mkdirp.sync( dest_dir );
|
|
|
|
glob(src_spec, {}, function (err, files) {
|
|
// got files
|
|
if (files && files.length) {
|
|
async.eachSeries( files, function(src_file, callback) {
|
|
// foreach file
|
|
var stats = fs.statSync(src_file);
|
|
|
|
if (stats.isFile()) {
|
|
copyFile( src_file, dest_dir + '/', callback );
|
|
}
|
|
else if (stats.isDirectory()) {
|
|
copyDir( src_file, dest_dir + '/' + path.basename(src_file), exclusive, callback );
|
|
}
|
|
}, callback );
|
|
} // got files
|
|
else {
|
|
callback( err );
|
|
}
|
|
} );
|
|
};
|
|
|
|
var bundleCompress = exports.bundleCompress = function bundleCompress( args, callback ) {
|
|
// compress bundle of files
|
|
var html_file = args.html_file;
|
|
var html_dir = path.dirname( html_file );
|
|
|
|
if (!fileExistsSync( path.dirname(args.dest_bundle) )) {
|
|
mkdirp.sync( path.dirname(args.dest_bundle) );
|
|
}
|
|
|
|
var raw_html = fs.readFileSync( html_file, 'utf8' );
|
|
var regexp = new RegExp( "\\<\\!\\-\\-\\s+BUILD\\:\\s*"+args.match_key+"_START\\s*\\-\\-\\>([^]+)\\<\\!\\-\\-\\s*BUILD\\:\\s+"+args.match_key+"_END\\s*\\-\\-\\>" );
|
|
|
|
if (raw_html.match(regexp)) {
|
|
var files_raw = RegExp.$1;
|
|
var files = [];
|
|
|
|
files_raw.replace( /\b(src|href)\=[\"\']([^\"\']+)[\"\']/ig, function(m_all, m_g1, m_g2) {
|
|
files.push( path.join(html_dir, m_g2) );
|
|
} );
|
|
|
|
if (files.length) {
|
|
console.log("Bundling files: ", files);
|
|
var raw_output = '';
|
|
|
|
// optionally add file header
|
|
if (args.header) {
|
|
raw_output += args.header.trim() + "\n";
|
|
}
|
|
|
|
if (args.uglify) {
|
|
console.log("Running UglifyJS...");
|
|
var result = UglifyJS.minify( files );
|
|
if (!result || !result.code) return callback( new Error("Failed to bundle script: Uglify failure") );
|
|
raw_output += result.code;
|
|
}
|
|
else {
|
|
for (var idx = 0, len = files.length; idx < len; idx++) {
|
|
var file = files[idx];
|
|
raw_output += fs.readFileSync( file, 'utf8' ) + "\n";
|
|
}
|
|
}
|
|
|
|
// optionally strip source map links
|
|
// /*# sourceMappingURL=materialdesignicons.min.css.map */
|
|
if (args.strip_source_maps) {
|
|
raw_output = raw_output.replace(/sourceMappingURL\=\S+/g, "");
|
|
}
|
|
|
|
// write out our bundle
|
|
console.log(" --> " + args.dest_bundle);
|
|
fs.writeFileSync(args.dest_bundle, raw_output);
|
|
|
|
// swap a ref link into a copy of the HTML
|
|
console.log(" --> " + html_file );
|
|
raw_html = raw_html.replace( regexp, args.dest_bundle_tag );
|
|
fs.writeFileSync(html_file, raw_html);
|
|
|
|
// now make a compressed version of the bundle
|
|
compressFile( args.dest_bundle, args.dest_bundle + '.gz', function(err) {
|
|
if (err) return callback(err);
|
|
|
|
// and compress the final HTML as well
|
|
compressFile( html_file, html_file + '.gz', callback );
|
|
});
|
|
|
|
} // found files
|
|
else {
|
|
callback( new Error("Could not locate any file references: " + args.src_html + ": " + args.match_key) );
|
|
}
|
|
}
|
|
else {
|
|
callback( new Error("Could not locate match in HTML source: " + args.src_html + ": " + args.match_key) );
|
|
}
|
|
};
|
|
|
|
var generateSecretKey = exports.generateSecretKey = function generateSecretKey( args, callback ) {
|
|
// generate random secret key for a specified JSON config file
|
|
// use regular expression to preserve natural file format
|
|
var file = args.file;
|
|
var key = args.key;
|
|
var regex = new RegExp('(\\"'+Tools.escapeRegExp(key)+'\\"\\s*\\:\\s*\\")(.*?)(\\")');
|
|
var secret_key = Tools.generateUniqueID(32);
|
|
|
|
fs.readFile(file, 'utf8', function(err, text) {
|
|
if (err) return callback(err);
|
|
if (!text.match(regex)) return callback( new Error("Could not locate key to replace: " + file + ": " + key) );
|
|
|
|
text = text.replace(regex, '$1' + secret_key + '$3');
|
|
fs.writeFile( file, text, callback );
|
|
});
|
|
};
|
|
|
|
var addToServerStartup = exports.addToServerStartup = function addToServerStartup( args, callback ) {
|
|
// add service to init.d
|
|
// (RedHat and Ubuntu only -- do nothing on OS X)
|
|
var src_file = args.src_file;
|
|
var dest_dir = args.dest_dir;
|
|
var service_name = args.service_name;
|
|
var dest_file = dest_dir + '/' + service_name;
|
|
|
|
// shell command that activates service on redhat or ubuntu
|
|
var cmd = 'chkconfig '+service_name+' on || update-rc.d '+service_name+' defaults';
|
|
|
|
// skip on os x, and if init dir is missing
|
|
if (os.platform() == 'darwin') return callback();
|
|
if (!fileExistsSync(dest_dir)) return callback( new Error("Cannot locate init.d directory: " + dest_dir) );
|
|
if (process.getuid() != 0) return callback( new Error("Must be root to add services to server startup system.") );
|
|
|
|
// copy file into place
|
|
copyFile( src_file, dest_file, function(err) {
|
|
if (err) return callback(err);
|
|
|
|
// must be executable
|
|
fs.chmod( dest_file, "775", function(err) {
|
|
if (err) return callback(err);
|
|
|
|
// exec shell command
|
|
cp.exec( cmd, callback );
|
|
} );
|
|
} );
|
|
};
|
|
|
|
var printMessage = exports.printMessage = function printMessage( args, callback ) {
|
|
// output a message to the console
|
|
// use process.stdout.write because console.log has been redirected to a file.
|
|
process.stdout.write( "\n" + args.lines.join("\n") + "\n\n" );
|
|
};
|