function NwBuilder(options) { var defaults = { files: null, appName: false, appVersion: false, platforms: ['osx32', 'osx64', 'win32', 'win64'], currentPlatform: detectCurrentPlatform(), version: 'latest', buildDir: './build', cacheDir: './cache', downloadUrl: 'https://dl.nwjs.io/', manifestUrl: 'https://nwjs.io/versions.json', flavor: 'sdk', buildType: 'default', forceDownload: false, macCredits: false, macIcns: false, macZip: null, zip: null, zipOptions: null, macPlist: false, winIco: null, argv: process.argv.slice(2) }; // Intercept the platforms and check for the legacy platforms of 'osx' and 'win' and // replace with 'osx32', 'osx64', and 'win32', 'win64' respectively. if(typeof options.platforms != 'undefined'){ if(options.platforms.indexOf('osx') >= 0){ options.platforms.splice(options.platforms.indexOf('osx'), 1, 'osx32', 'osx64'); } if(options.platforms.indexOf('win') >= 0){ options.platforms.splice(options.platforms.indexOf('win'), 1, 'win32', 'win64'); } if(options.platforms.indexOf('linux') >= 0){ options.platforms.splice(options.platforms.indexOf('linux'), 1, 'linux32', 'linux64'); } } // Assign options this.options = _.defaults(options, defaults); // Some Option checking if(!this.options.files) { throw new Error('Please specify some files'); } if (this.options.platforms.length === 0) throw new Error('No platform to build!'); // verify all the platforms specifed by the user are supported // this + previous check assures as we have only buildable platforms specified this.options.platforms.forEach(function(platform) { if (!(platform in platforms)) throw new Error('Unknown platform ' + platform); }); this._platforms = _.cloneDeep(platforms); // clear all unused platforms for (var name in this._platforms) { if (this.options.platforms.indexOf(name) === -1) delete this._platforms[name]; } }
n/a
function EventEmitter() { EventEmitter.init.call(this); }
n/a
checkCache = function (cachepath, files) { var missing; // if the version is >=0.12.3, then we don't which files we want from the archives, so just check that the folder // exists and has at least 3 files in it. if(files.length === 1 && files[0] === '*'){ return fs.existsSync(cachepath) && fs.readdirSync(cachepath).length >= 3; } files.forEach(function(file) { if (missing) { return; } if (!fs.existsSync(path.join(cachepath, file))) { missing = true; } }); return !missing; }
...
NwBuilder.prototype.isPlatformCached = function (platformName, platform, version, flavor) {
this.setPlatformCacheDirectory(platformName, platform, version, flavor);
if (this.options.forceDownload) {
return false;
}
this.preparePlatformFiles(platformName, platform, version);
return Downloader.checkCache(platform.cache, platform.files);
};
// returns a Boolean; true if the desired platform is supported
NwBuilder.prototype.preparePlatformFiles = function(platformName, platform, version) {
// return if platform.files is already prepared
if(Object.keys(platform.files)[0] !== Object.keys(platforms[platformName].files)[0]){
return true;
...
clearProgressbar = function () { bar && bar.terminate(); bar = null; }
...
} else {
self.emit('log', 'Using cache for: ' + name);
}
});
return Promise.all(downloads)
.then(function(data) {
Downloader.clearProgressbar();
return data;
});
};
NwBuilder.prototype.buildGypModules = function () {
// @todo
// If we trigger a rebuild we have to copy
...
downloadAndUnpack = function (cachepath, url) { var extention = path.extname(url), done = Promise.defer(), self = this, rq = request(url), len, stream; function format(statusCode) { return statusCode + ': ' + require('http').STATUS_CODES[statusCode]; } rq.proxy = true; rq.on('error', function(err) { bar && bar.terminate(); done.reject(err); }); rq.on('response', function (res) { len = parseInt(res.headers['content-length'], 10); if (res.statusCode !== 200) { done.reject({ statusCode: res.statusCode, msg: 'Recieved status code ' + format(res.statusCode) }); } else if (len) { if (!bar) { bar = new progress(' downloading [:bar] :percent :etas', { complete: '=', incomplete: '-', width: 20, total: len }); } else { bar.total += len; } } }); rq.on('data', function(chunk) { len && bar && bar.tick(chunk.length); }); if (extention === '.zip') { stream = temp.createWriteStream(); stream.on('close', function() { if(done.promise.isRejected()) return; self.extractZip(stream.path, cachepath).then(self.stripRootFolder).then(function(files) { done.resolve(files); }); }); rq.pipe(stream); } if (extention === '.gz') { rq.on('response', function(res) { if(res.statusCode !== 200) return; self.extractTar(res, cachepath).then(self.stripRootFolder).then(function(files) { done.resolve(files); }); }); } return done.promise; }
...
}
fs.mkdirpSync(platform.cache);
self.emit('log', 'Create cache folder in ' + path.resolve(self.options.cacheDir, self._version.version + '
;-' + self._version.flavor));
if(!self.isPlatformCached(name, platform, self._version.version, self._version.flavor)) {
downloads.push(
Downloader.downloadAndUnpack(platform.cache, platform.url)
.catch(function(err){
if(err.statusCode === 404){
self.emit('log', 'ERROR: The version '+self._version.version+ ' (' + self._version
.flavor + ') does not have a corresponding build posted at ' + self.options.downloadUrl + '. Please choose a version
from that list.');
} else {
self.emit('log', err.msg);
}
...
extractTar = function (tarstream, destination) { var done = Promise.defer(), gunzip = zlib.createGunzip(), files = []; tarstream .pipe(gunzip) .on('error', function(err){ done.reject(err); }) .pipe(tar.extract(destination, { umask: (isWin ? false : 0), map: function(header) { files.push({path: path.basename(header.name)}); return header; } })) .on('finish', function() { done.resolve({files:files, destination:destination}); }); return done.promise; }
...
rq.pipe(stream);
}
if (extention === '.gz') {
rq.on('response', function(res) {
if(res.statusCode !== 200) return;
self.extractTar(res, cachepath).then(self.stripRootFolder).then(function(
files) {
done.resolve(files);
});
});
}
return done.promise;
},
...
extractZip = function (zipfile, destination) { var files = [], done = Promise.defer(); new DecompressZip(zipfile) .on('error', function(err){ done.reject(err); }) .on('extract', function(log) { // Setup chmodSync to fix permissions files.forEach(function(file) { fs.chmodSync(path.join(destination, file.path), file.mode); }); done.resolve({files:files, destination:destination}); }) .extract({ path: destination, filter: function(entry) { files.push({ path: entry.path, mode: entry.mode.toString(8) }); return true; } }); return done.promise; }
...
});
if (extention === '.zip') {
stream = temp.createWriteStream();
stream.on('close', function() {
if(done.promise.isRejected()) return;
self.extractZip(stream.path, cachepath).then(self.stripRootFolder).then(function
(files) {
done.resolve(files);
});
});
rq.pipe(stream);
}
...
stripRootFolder = function (extracted){ var done = Promise.defer(), files = extracted.files, destination = extracted.destination, rootFiles = fs.readdirSync(destination), fromDir = path.join(destination, rootFiles.length === 1 ? rootFiles[0] : ''); // strip out root folder if it exists if(rootFiles.length === 1 && fs.statSync(fromDir).isDirectory() ){ // strip folder from files for (var i = 0; i < files.length; i++) { var file = files[i]; file.path = path.relative(rootFiles[0], file.path); if(file.path === '') { files.splice(i, 1); i--; } } // move stripped folder to destination ncp(fromDir, destination, function (err) { if (err) done.reject(); else rimraf(fromDir, function(){ done.resolve(files); }); }); } else { done.resolve(files); } return done.promise; }
n/a
closerPathDepth = function (path1, path2) { if (!path2) { return path1; } var d1 = this.pathDepth(path1), d2 = this.pathDepth(path2); return d1 < d2 ? path1 : path2; }
...
return new Promise(function(resolve, reject) {
if(!matches.length) return reject('No files matching');
matches.forEach(function(file) {
var internalFileName = path.normalize(file);
if (internalFileName.match('package.json')) {
jsonfile = self.closerPathDepth(internalFileName, jsonfile);
package_path = path.normalize(jsonfile.split('package.json')[0] || './');
}
if(!fs.lstatSync(internalFileName).isDirectory()) {
srcFiles.push(internalFileName);
}
});
...
copyFile = function (src, dest, _event, options) { return new Promise(function(resolve, reject) { options = options || {}; var stats = fs.lstatSync(src); fs.copy(src, dest, options, function (err) { if(err) return reject(err); var retryCount = 0; var existsCallback = function(exists){ if(exists){ fs.chmod(dest, stats.mode, function(err){ // ignore error if (err) { _event.emit('log', 'chmod ' + stats.mode + ' on ' + dest + ' failed after copying, ignoring'); } resolve(); }); } else if (retryCount++ < 2) { // This is antipattern!!! // Callback should be called when the copy is finished!!!! setTimeout(function(){ fs.exists(dest, existsCallback); }, 1000); } else { reject(new Error("Copied file (" + dest + ") doesn't exist in destination after copying")); } }; fs.exists(dest, existsCallback); }); }); }
...
return !/nwjs\.app\/Contents\/Resources\/[^.]+\.lproj(\/|$)/.test(filepath);
};
}
// rename executable to app name
destFile = self.options.appName + path.extname(destFile);
}
copiedFiles.push(Utils.copyFile(file, path.join(platform.releasePath, destFile
), self, options));
platform.files.push(destFile);
});
return;
}
// legacy
...
editPlist = function (plistInput, plistOutput, options) { options = options || {}; // Make sure all required properties are set [ 'CFBundleName', 'CFBundleDisplayName', 'CFBundleVersion', 'CFBundleShortVersionString' ].forEach(function(prop) { if(!options.hasOwnProperty(prop)) { throw new Error('Missing macPlist property \'' + prop + '\''); } }); // Bundle identifier based on package name if(options.CFBundleIdentifier === undefined) { options.CFBundleIdentifier = 'com.nw-builder.' + options.CFBundleName.toLowerCase().replace(/[^a-z\-]/g,''); } // Read the input file return readFile(plistInput, 'utf8') // Parse it .then(plist.parse) // Then overwrite the properties with custom values .then(function(info) { // Keep backwards compatibility and handle aliases Object.keys(options).forEach(function(key) { var value = options[key]; switch(key) { case 'mac_bundle_id': info.CFBundleIdentifier = value; break; case 'mac_document_types': info.CFBundleDocumentTypes = value.map(function(type) { return { CFBundleTypeName: type.name, CFBundleTypeExtensions: type.extensions, CFBundleTypeRole: type.role, LSIsAppleDefaultForType: type.isDefault }; }); break; default: info[key] = value; } }); // Remove some unwanted properties if(!(options.hasOwnProperty('mac_document_types') || options.hasOwnProperty('CFBundleDocumentTypes'))) { info.CFBundleDocumentTypes = []; } if(!options.hasOwnProperty('UTExportedTypeDeclarations')) info.UTExportedTypeDeclarations = []; // Write output file return writeFile(plistOutput, plist.build(info)); }); }
...
name: self.options.appName,
version: self.options.appVersion,
copyright: self._appPkg.copyright
},
self.options.macPlist
);
allDone.push(Utils.editPlist(PlistPath, PlistPath, plistOptions));
}
});
return Promise.all(allDone);
};
NwBuilder.prototype.handleWinApp = function () {
...
generateZipFile = function (files, _event, platformSpecificManifest, zipOptions) { var destStream = temp.createWriteStream(), archive = archiver('zip', zipOptions || {}); return new Promise(function(resolve, reject) { // Resolve on close destStream.on('close', function () { resolve(destStream.path); }); // Reject on Error archive.on('error', reject); // Add the files var filesBulk = []; files.forEach(function(file){ if(file.dest === 'package.json' && platformSpecificManifest){ archive.append(platformSpecificManifest, {name: 'package.json'}); } else { filesBulk.push({ src: file.src, data: { name: path.basename(file.dest) }, expand: true, flatten: true, dest: path.dirname(file.dest) }); } }); archive.bulk(filesBulk); // Some logs archive.on('entry', function (file) { _event.emit('log', 'Zipping ' + file.name); }); // Pipe the stream archive.pipe(destStream); archive.finalize(); }); }
...
return;
}
// create (or don't create) a ZIP for multiple platforms
new Promise(function(resolve, reject) {
if(numberOfPlatformsWithoutOverrides > 1){
Utils.generateZipFile(self._files, self, null, zipOptions).then(function (zip
) {
resolve(zip);
}, reject);
}
else {
resolve();
}
})
...
getFileList = function (fileglob) { var self = this, jsonfile, destFiles = [], srcFiles = [], package_path, matches = Glob(fileglob); return new Promise(function(resolve, reject) { if(!matches.length) return reject('No files matching'); matches.forEach(function(file) { var internalFileName = path.normalize(file); if (internalFileName.match('package.json')) { jsonfile = self.closerPathDepth(internalFileName, jsonfile); package_path = path.normalize(jsonfile.split('package.json')[0] || './'); } if(!fs.lstatSync(internalFileName).isDirectory()) { srcFiles.push(internalFileName); } }); if (!jsonfile) { return reject('Could not find a package.json in your src folder'); } srcFiles.forEach(function(file) { destFiles.push({ src: file, dest: file.replace(package_path, '') }); }); resolve({ files: destFiles, json: jsonfile }); }); }
...
return hasCallback ? true : done.promise;
};
NwBuilder.prototype.checkFiles = function () {
var self = this;
return Utils.getFileList(this.options.files).then(function (data) {
self._appPkg = data.json;
self._files = data.files;
return Utils.getPackageInfo(self._appPkg).then(function (appPkg) {
self._appPkg = appPkg;
...
getPackageInfo = function (path) { return new Promise(function(resolve, reject) { fs.readFile(path, function (err, data) { if (err) return reject(err); try { var appPkg = JSON.parse(data); } catch(e) { reject("Invalid package.json: " + e + "\nMake sure the file is encoded as utf-8"); return; } if (!appPkg.name || !appPkg.version) { reject("Please make sure that your project's package.json includes a version and a name value"); } else { resolve(appPkg); } }); }); }
...
var self = this;
return Utils.getFileList(this.options.files).then(function (data) {
self._appPkg = data.json;
self._files = data.files;
return Utils.getPackageInfo(self._appPkg).then(function (appPkg) {
self._appPkg = appPkg;
if(!self.options.appName || !self.options.appVersion) {
self.options.appName = (self.options.appName ? self.options.appName : appPkg.name);
self.options.appVersion = (self.options.appVersion ? self.options.appVersion : appPkg.version);
}
});
...
getPlistOptions = function (parsedParams, custom) { var obj = {}; if(parsedParams.name) { obj.CFBundleName = parsedParams.name; obj.CFBundleDisplayName = parsedParams.name; } if(parsedParams.version) { obj.CFBundleVersion = parsedParams.version; obj.CFBundleShortVersionString = 'Version ' + parsedParams.version; } if(parsedParams.copyright) { obj.NSHumanReadableCopyright = parsedParams.copyright; } return _.merge(obj, custom); }
...
var PlistPath = path.resolve(platform.releasePath, self.options.appName+'.app', 'Contents', 'Info.plist
');
// If the macPlist is a string we just copy the file
if(typeof self.options.macPlist === 'string') {
allDone.push(Utils.copyFile(self.options.macPlist, PlistPath, self));
} else {
// Setup the Plist
var plistOptions = Utils.getPlistOptions(
{
name: self.options.appName,
version: self.options.appVersion,
copyright: self._appPkg.copyright
},
self.options.macPlist
);
...
mergeFiles = function (app, zipfile, chmod) { // we need to pipe the app into the zipfile and chmod it return new Promise(function(resolve, reject) { var zipStream = fs.createReadStream(zipfile), writeStream = fs.createWriteStream(app, {flags:'a'}); zipStream.on('error', reject); writeStream.on('error', reject); writeStream.on('finish', function () { if(chmod) { fs.chmodSync(app, chmod); } resolve(); }); zipStream.pipe(writeStream); }); }
...
path.resolve(self.getResourcesDirectoryPath(platform), 'app.nw'),
self
));
} else {
var executableToMergeWith = self._version.isLegacy ? _.first(platform.files) : self.getExecutableName(name);
// We cat the app.nw file into the .exe / nw
copyPromises.push(Utils.mergeFiles(
path.resolve(platform.releasePath, executableToMergeWith),
self.getZipFile(name),
platform.chmod
));
}
});
...
pathDepth = function (absolutePath) { return absolutePath.split(path.sep).length; }
...
json: jsonfile
});
});
},
closerPathDepth: function(path1, path2) {
if (!path2) { return path1; }
var d1 = this.pathDepth(path1),
d2 = this.pathDepth(path2);
return d1 < d2 ? path1 : path2;
},
pathDepth: function(absolutePath) {
return absolutePath.split(path.sep).length;
},
...
getLatestVersion = function (downloadUrl, manifestUrl, flavor) { return getManifest(manifestUrl) .then(function(manifest) { return { desiredVersion: manifest.stable.replace('v', ''), downloadUrl: downloadUrl, manifestUrl: manifestUrl, flavor: flavor } }) .then(this.getVersion); }
...
};
NwBuilder.prototype.resolveLatestVersion = function () {
var self = this;
if(self.options.version !== 'latest') return Promise.resolve();
return NwVersions.getLatestVersion(self.options.downloadUrl, self.options.manifestUrl
, self.options.flavor).then(function(latestVersion){
self.emit('log', 'Latest Version: v' + latestVersion.version);
self.options.version = latestVersion.version;
return latestVersion;
});
};
NwBuilder.prototype.checkVersion = function () {
...
getVersion = function (args){ return (isLegacyVersion(args.desiredVersion) ? getLegacyVersions : getVersionsFromManifest)(args.downloadUrl, args.manifestUrl ) .then(function(versions) { var version = versions.findIndex(function(version){ return version.version === args.desiredVersion; }); return version >= 0 ? Promise.resolve(versions[version]) : Promise.reject('Version ' + args.desiredVersion + ' not found.'); }); }
...
if(!semver.valid(version)){
return Promise.reject('The version ' + version + ' is not valid.');
}
var getVersionFromManifest = function(){
return NwVersions.getVersion({
desiredVersion: version,
downloadUrl: self.options.downloadUrl,
manifestUrl: self.options.manifestUrl,
flavor: flavor
});
};
var getVersion;
...
getVersions = function (downloadUrl, manifestUrl, flavor){ return Promise.all([ getVersionsFromManifest(downloadUrl, manifestUrl, flavor), getLegacyVersions(downloadUrl, flavor) ]) .then(function(versionLists){ return versionLists[0].concat(versionLists[1]); }); }
n/a