#!/bin/sh ':' //; exec "$(command -v nodejs || command -v node)" "$0" "$@" var zlib = require('zlib'); var path = require('path'); // external dependencies var fs = require('fs-extra'); var tar = require('tar'); var fstream = require('fstream'); var getopt = require('node-getopt'); /* * Packing a project. * The efljs package has a similar format to debian packages. It is a * tar package containing two files: * * meta.txt: Metadata information about this package. * data.tar.gz: Gzipped data, with the project tree ready to be decompressed * and run by the package launcher. * * During the build, a out/ directory is created in the project root to * store the package and temporary files. */ // Creates a stub .project file and packs it. function pack_single(sourcepath, options) { if (options.verbose) console.log("Creating project file for single file app", sourcepath); var dir_name = path.dirname(fs.realpathSync(sourcepath)); var filename = path.basename(sourcepath); var projectRegex = /^(.*).js$/g; var project_name = projectRegex.exec(filename)[1]; if (!validade_project_name(project_name)) { console.error("Invalid project name. Must start with a letter."); process.exit(0); } var project_filename = path.join(dir_name, project_name + ".project"); var fd = fs.openSync(project_filename, 'w'); var jsonData = {}; jsonData["Name"] = project_name; jsonData["Entry"] = filename; jsonData["Sources"] = [[filename, '.']]; jsonData["Version"] = "0.1"; fs.writeSync(fd, JSON.stringify(jsonData, null, 2)); fs.closeSync(fd); pack_project(project_filename, options); } function generate_build_info(configuration, project_file, options) { build_info = {}; // project == project_dir // /out == build_dir // /data == data_dir // /name-version == package_dir build_info.package_id = configuration.Name + "-" + configuration.Version; build_info.project_dir = path.dirname(project_file); build_info.build_dir = path.join(build_info.project_dir, "out"); build_info.data_dir = path.join(build_info.build_dir, "data"); build_info.package_dir = path.join(build_info.build_dir, build_info.package_id); build_info.data_file = path.join(build_info.package_dir, "data.tar.gz"); build_info.package_file = path.join(build_info.build_dir, build_info.package_id + ".epk") build_info.metadata_file = path.join(build_info.package_dir, "meta.json"); if (options.verbose) { console.log("Project id: ", build_info.package_id); console.log("Project source dir: ", build_info.project_dir); console.log("Project build dir: ", build_info.build_dir); console.log("Project data dir:", build_info.data_dir); console.log("Project package dir:", build_info.package_dir); } return build_info; } // Project names must start with a letter and contain only // letters, digits and underscores. function validade_project_name(name) { return (/^[a-zA-Z][\w-]*$/).test(name) } function pack_project(project_file, options) { if (options.verbose) console.log("Packing project from project file ", project_file); var configuration = JSON.parse(fs.readFileSync(project_file)); if (!validade_project_name(configuration.Name)) { console.error("Invalid project name. Must start with a letter."); process.exit(0); } var build_info = generate_build_info(configuration, project_file, options); try { fs.mkdirSync(build_info.build_dir); fs.mkdirSync(build_info.data_dir); fs.mkdirSync(build_info.package_dir); } catch (e) { console.warn("Warning: Project output directories not empty."); } create_metadata_file(configuration, build_info, options); // If not explicitly named on configuration, add the entire directory if (!('Sources' in configuration)) { generate_source_list(configuration, build_info.project_dir, options); } create_project_tree(configuration.Sources, build_info, options); pack_data_dir(build_info, options); } function create_project_tree(sources, build_info, options) { for (var i = sources.length - 1; i >= 0; i--) { if (options.verbose) console.log("Adding file ", sources[i], "to package."); var source_file = path.join(build_info.project_dir, sources[i][0]); var destination_dir = path.join(build_info.data_dir, sources[i][1]); var destination_filename = path.basename(source_file); var destination_file = path.join(destination_dir, destination_filename); fs.copySync(source_file, destination_file); }; } function generate_source_list(configuration, project_dir, options) { console.log("Generating source list for project dir", build_info.project_dir); var dir_entries = fs.readdirSync(project_dir); var sources = []; dir_entries.forEach(function(entry){ if (entry == "out") return; sources.push([entry, "."]); }); configuration.Sources = sources; } function create_metadata_file(configuration, build_info, options) { if (options.verbose) console.log("Creating metadata file", build_info.metadata_file); var metadata = {}; metadata.Name = configuration.Name; metadata.Entry = configuration.Entry; metadata.Version = configuration.Version; var output = fs.createWriteStream(build_info.metadata_file); output.write(JSON.stringify(metadata, null, 2)); output.close(); } function pack_data_dir(build_info, options) { if (options.verbose) console.log("Packing data..."); pack_directory(build_info.data_dir, build_info.data_file, true, true, function(){ if (options.verbose) console.log("Packed data"); pack_final_package(build_info, options); }); } function pack_final_package(build_info, options) { if (options.verbose) console.log("Creating package ", build_info.package_file); pack_directory(build_info.package_dir, build_info.package_file, false, false, function(){ if (options.verbose) console.log("Created project package."); }); } function pack_directory(source_dir, target_file, strip_base_dir, should_gzip, callback) { var output = fs.createWriteStream(target_file); var packer = tar.Pack({fromBase: strip_base_dir == true}); if (callback != undefined) output.on('close', callback); var reader = fstream.Reader({path: source_dir, type: "Directory"}); var destStr = reader.pipe(packer); if(should_gzip) destStr = destStr.pipe(zlib.createGzip()); destStr.pipe(output); } function main() { var options = getopt.create([ ['v', 'verbose', 'Explain what is being done'], ['h', 'help', 'Display this help'] ]).bindHelp().parseSystem(); filename = options.argv[0]; if (typeof filename === 'undefined') { console.error('Must provide a valid js or project file.'); process.exit(1); } if (endsWith(filename, ".js")) { pack_single(filename, options.options); } else if (endsWith(filename, ".project")) { pack_project(filename, options.options); } } main(); //// Helper functions function endsWith(str, suffix) { return str.indexOf(suffix, str.length - suffix.length) !== -1; }