package.js | |
---|---|
/*
* package.js: Utilities for working with package.json files.
*
* (C) 2010, Nodejitsu Inc.
*
*/
var util = require('util'),
path = require('path'),
fs = require('fs'),
spawn = require('child_process').spawn,
async = require('async'),
analyzer = require('require-analyzer'),
npm = require('npm'),
npmout = require('npm/lib/utils/output'),
npmtar = require('npm/lib/utils/tar'),
semver = require('semver'),
winston = require('winston'),
jitsu = require('jitsu');
var package = exports; | |
Monkey patch | npmout.write = function () {
var args = Array.prototype.slice.call(arguments),
callback;
args.forEach(function (arg) {
if (typeof arg === 'function') {
callback = arg;
}
});
callback();
}; |
function get (dir, callback)@dir {string} Directory to get the package.json from@callback {function} Continuation to respond to when completeAttempts to read the package.json from the specified | package.get = function (dir, options, callback) {
if (!callback) {
callback = options;
options = {};
}
package.read(dir, function (err, pkg) {
if (err) {
return package.create(dir, callback);
}
package.validate(pkg, dir, options, function (err, updated) {
return err ? callback(err) : callback(null, updated);
});
});
}; |
function read (dir, callback)@dir {string} Directory to read the package.json from@callback {function} Continuation to pass control to when completeAttempts to read the package.json file out of the specified directory. | package.read = function (dir, callback) {
var file = path.join(dir, 'package.json');
fs.readFile(file, function (err, data) {
if (err) {
return callback(err);
}
data = data.toString();
if (!data.length) {
return callback(new Error('package.json is empty'));
}
try {
callback(null, JSON.parse(data.toString()));
}
catch (ex) {
callback(new Error('Invalid package.json file'));
}
});
}; |
function tryRead (dir, callback, success)@dir {string} Directory to try to read the package.json from.@callback {function} Continuation to respond to on error.@success {function} Continuation to respond to on success.Attempts to read the package.json file from the specified | package.tryRead = function (dir, callback, success) {
package.read(dir, function (err, pkg) {
return err ? callback(new Error('No package.json in ' + dir), true) : success(pkg);
});
}; |
function createPackage (dir, callback)@dir {string} Directory to create the package.json in@callback {function} Continuation to respond to when completeWalks the user through creating a simple package.json in the specified
| package.create = function (dir, callback) {
var help = [
'',
'A package.json stores meta-data about your application',
'In order to continue we\'ll need to gather some information about your app',
'',
'Press ^C at any time to quit.',
'to select a default value, press ENTER'
];
winston.warn('There in no valid package.json file in ' + dir.grey);
winston.warn('Creating package.json at ' + (dir + '/package.json').grey);
help.forEach(function (line) {
winston.help(line);
});
jitsu.prompt.get(package.properties(dir), function (err, result) {
if (err) { |
TODO: Something here... | }
var pkg = {
name: result.name,
subdomain: result.subdomain,
scripts: {
start: result['scripts.start']
},
version: result.version
};
package.analyzeDependencies(pkg, dir, function (err, addedDeps, updates) {
if (err) {
return callback(err);
}
package.write(addedDeps, dir, true, callback);
});
});
}; |
function validate (pkg, dir, callback)@pkg {Object} Parsed package.json to validate@dir {string} Directory containing the package.json file@callback {function} Continuation to respond to when completeValidates the specified | package.validate = function (pkg, dir, options, callback) {
if (!callback) {
callback = options;
options = {};
}
var properties = package.properties(dir),
missing = [];
function checkProperty (desc, next) {
var nested = desc.name.split('.'),
value = pkg[nested[0]];
if (nested.length > 1 && value) {
value = value[nested[1]];
}
if (!value || (missing.validator && !missing.validator.test(value))) {
missing.push(desc);
}
next();
}
async.forEach(properties, checkProperty, function () {
if (missing.length > 0) {
var help, names = missing.map(function (prop) {
return ' ' + (prop.message || prop.name).grey;
});
help = [
'',
'Your package.json file is missing required fields:',
'',
names.join(', '),
'',
'Prompting user for required fields.',
'Press ^C at any time to quit.',
''
];
help.forEach(function (line) {
winston.warn(line);
});
return jitsu.prompt.addProperties(pkg, missing, function (err, updated) {
return err ? callback(err) : tryAnalyze(updated);
});
}
function tryAnalyze (target) {
package.analyzeDependencies(target, dir, function (err, addedDeps, updates) {
if (err) {
return callback(err);
}
return updates ? package.write(addedDeps, dir, true, callback) : callback(null, addedDeps);
});
}
var analyze = jitsu.config.get('resolveDependencies');
return analyze !== undefined && !analyze
? callback(null, [])
: tryAnalyze(pkg);
});
}; |
function writePackage (pkg, dir, callback)@pkg {Object} Data for the package.json@dir {string} Directory to write the package.json in@callback {function} Continuation to respond to when completePrompts the user about writing the new package.json file. If the user
OKs the operation, attempts to write to file. Otherwise restarts the
create operation in the specified | package.write = function (pkg, dir, create, callback) {
if (!callback) {
callback = create;
create = null;
}
winston.warn('About to write ' + path.join(dir, 'package.json').magenta);
jitsu.log.putObject(pkg, 2);
jitsu.prompt.get({
name: 'answer',
message: 'Is this ' + 'ok?'.green.bold,
default: 'yes'
}, function (err, result) {
if (result.answer !== 'yes' && result.answer !== 'y') {
return create ? package.create(dir, callback) : callback(new Error('Save package.json cancelled.'));
}
fs.writeFile(path.join(dir, 'package.json'), JSON.stringify(pkg, null, 2), function (err) {
return err ? callback(err) : callback(null, pkg, dir);
})
});
}; |
function checkDependencies (pkg, dir, callback)@pkg {Object} Parsed package.json to check dependencies for.@dir {string} Directory containing the package.json file.@callback {function} Continuation to respond to when complete.Analyzes the dependencies in | package.analyzeDependencies = function (pkg, dir, callback) {
winston.info('Analyzing your application dependencies in ' + pkg.scripts.start.magenta);
analyzer.analyze({ target: path.join(process.cwd(), pkg.scripts.start), timeout: 1000 }, function (err, pkgs) { |
Create a hash of | var updates, versions = analyzer.extractVersions(pkgs);
if (package.newDependencies(pkg.dependencies, versions)) { |
If there are new dependencies, indicate this to the user. | winston.info('Found new dependencies. They will be added automatically');
|
Extract, merge, and display the updates found by | updates = analyzer.updates(pkg.dependencies, versions);
updates = analyzer.merge({}, updates.added, updates.updated);
jitsu.log.putObject(updates); |
Update the package.json dependencies | pkg.dependencies = analyzer.merge({}, pkg.dependencies || {}, updates);
}
callback(null, pkg, updates);
});
}; |
function createPackage (dir, callback)@dir {string} Directory to create the package *.tgz file from@callback {function} Continuation to pass control to when completeCreates a *.tgz package file from the specified directory | package.createTarball = function (dir, version, callback) {
if (!callback) {
callback = version;
version = null;
}
package.read(dir, function (err, pkg) {
if (err) {
return callback(err);
}
if (dir.slice(-1) === '/') {
dir = dir.slice(0, -1);
}
var name = [jitsu.config.get('username'), pkg.name, version || pkg.version].join('-') + '.tgz',
tarball = path.join(jitsu.config.get('tmproot'), name);
npm.load({ exit: false }, function () {
npmtar.pack(tarball, dir, pkg, true, function (err) {
return err ? callback(err) : callback(null, pkg, tarball);
});
});
});
}; |
function updateTarball (version, pkg, existing, callback)@version {string} Optional Version to use for the updated tarball@pkg {Object} Current package.json file on disk@existing {Object} Remote package.json stored at Nodejitsu@callback {function} Continuation to respond to when complete. | package.updateTarball = function (version, pkg, existing, firstSnapshot, callback) {
if (!callback) {
callback = firstSnapshot;
firstSnapshot = false;
}
function executeCreate (err) {
if (err) {
return callback(err, true);
}
version = version || pkg.version;
jitsu.package.createTarball(process.cwd(), version, function (err, ign, filename) {
if (err) {
return callback(err, true)
}
winston.warn('Creating new snapshot for version ' + pkg.version.magenta);
winston.silly('Filename: ' + filename);
jitsu.snapshots.create(pkg.name, version, filename, function (err, snapshots) {
winston.info('Done creating snapshot ' + version.magenta);
return err ? callback(err) : callback(null, version, pkg);
});
});
}
var old = false;
if (!firstSnapshot) {
winston.silly('Existing version: ' + existing.version.magenta);
winston.silly('Local version: ' + pkg.version.magenta);
var old = semver.gte(existing.version, pkg.version);
if (old) { |
If the existing version is greater than the version in the package.json file on disk, update it and then write back to disk. | winston.warn('Your package.json version will be incremented for you automatically.');
pkg.version = semver.inc(existing.version, 'build');
}
}
return old
? package.write(pkg, process.cwd(), executeCreate)
: executeCreate();
}; |
function newDependencies (current, updated)@current {Object} Set of current dependencies@updated {Object} Set of updated dependenciesReturns a value indicating if there are any new dependencies
in | package.newDependencies = function (current, updated) {
var updates = analyzer.updates(current, updated);
return Object.keys(updates.added).length > 0 || Object.keys(updates.updated).length > 0;
}; |
function properties (dir)@dir {string} Directory in which the package.json properties are being usedReturns a new set of properties to be consumed by | package.properties = function (dir) {
return [
{
name: 'name',
message: 'App name',
validator: /[\w|\-]+/,
default: path.basename(dir)
},
{
name: 'subdomain',
validator: /[\w|\-|\_]+/,
help: [
'',
'The ' + 'subdomain '.grey + 'is where your application will reside.',
'Your application will then become accessible at: http://' + 'yourdomain'.grey + '.nodejitsu.com',
''
],
default: path.basename(dir)
},
{
name: 'scripts.start',
message: 'scripts.start',
validator: function (script) {
try {
fs.statSync(path.join(dir, script));
return true;
}
catch (ex) {
return false;
}
},
warning: 'Start script was not found in ' + dir.magenta,
default: 'server.js'
},
{
name: 'version',
validator: /[\w|\-|\.]+/,
default: '0.0.0'
}
];
};
|