jspm
Version:
Registry and format agnostic JavaScript package manager
293 lines (245 loc) • 9.51 kB
JavaScript
/*
* Copyright 2014-2015 Guy Bedford (http://guybedford.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var config = require('../config');
var path = require('path');
var extend = require('../common').extend;
var hasProperties = require('../common').hasProperties;
var Promise = require('rsvp').Promise;
var ui = require('../ui');
var fs = require('graceful-fs');
var asp = require('rsvp').denodeify;
var readJSONSync = require('../common').readJSONSync;
var alphabetize = require('../common').alphabetize;
var processDeps = require('../package').processDeps;
var stringify = require('../common').stringify;
var isWindows = process.platform.match(/^win/);
var winRegEx = /\\/g;
function winPath(path) {
if (!isWindows)
return path;
return path.replace(winRegEx, '/');
}
/*
* Package.json Configuration Class
*
* main
* registry
* dependencies
* devDependencies
* configFile
* baseURL
* packages
* format
* map
* buildConfig
* overrides
*
*/
function PackageJSON(fileName) {
this.fileName = fileName;
}
PackageJSON.prototype.read = function(prompts, sync) {
if (this.__read)
throw 'Package.json file already read';
this.__read = true;
var self = this;
var pjson;
var _pjson = readJSONSync(this.fileName);
self.originalPjson = _pjson;
self.jspmPrefix = true;
if ((_pjson.registry || _pjson.jspm === true) && typeof _pjson.jspm != 'object')
self.jspmPrefix = false;
// if we have dependencies in `jspm` prefix, assume devDependencies are too
if (_pjson.jspm && _pjson.jspm.dependencies) {
if (_pjson.devDependencies)
_pjson.jspm.devDependencies = _pjson.jspm.devDependencies || {};
}
// and vice versa
if (_pjson.jspm && _pjson.jspm.devDependencies) {
if (_pjson.dependencies)
_pjson.jspm.dependencies = _pjson.jspm.dependencies || {};
}
// derive the jspm config from the 'jspm' property in the package.json
// also sets the registry property if dependencies are jspm-prefixed
pjson = config.derivePackageConfig(_pjson);
self.pjson = pjson;
// not already jspm-configured
if (!_pjson.jspm && !_pjson.registry) {
prompts = true;
// dont read dependencies or devDependencies in this case
delete pjson.dependencies;
delete pjson.devDependencies;
// if dependencies already existing, override with empty dependencies
if (self.originalPjson.dependencies)
pjson.dependencies = {};
if (self.originalPjson.devDependencies)
pjson.devDependencies = {};
}
prompts = prompts || (!_pjson.jspm && !_pjson.registry);
if (prompts) {
if (sync)
throw 'Package.json file has not been initialized by jspm before. Run jspm init first.';
return doInitPrompts(pjson, self.fileName, self.jspmPrefix)
.then(function(prefixed) {
self.jspmPrefix = prefixed;
setDefaults();
return true;
});
}
setDefaults();
function setDefaults() {
self.dir = path.dirname(self.fileName);
// populate defaults as we go
var defaults = self.defaults = {};
self.registry = pjson.registry;
self.dependencies = {};
self.devDependencies = {};
// only read dependencies when combined with a registry property
self.dependencies = processDeps(pjson.dependencies, self.registry);
self.devDependencies = processDeps(pjson.devDependencies, self.registry);
defaults.baseURL = self.dir;
self.baseURL = pjson.directories && pjson.directories.baseURL && path.resolve(self.dir, pjson.directories.baseURL) || defaults.baseURL;
if (self.baseURL === path.resolve('/')) {
ui.log('warn', 'Server baseURL should be a relative file path, reverting to `.`');
self.baseURL = self.dir;
}
defaults.packages = path.resolve(self.baseURL, 'jspm_packages');
defaults.main = path.join(self.baseURL, 'index');
self.main = pjson.main || defaults.main;
// NB can remove jspmPackages suport in time
self.packages = pjson.directories && pjson.directories.packages && path.resolve(self.dir, pjson.directories.packages) ||
pjson.directories && pjson.directories.jspmPackages && path.resolve(self.dir, pjson.directories.jspmPackages) || defaults.packages;
defaults.configFile = path.resolve(self.baseURL, 'config.js');
self.configFile = pjson.configFile && path.resolve(self.dir, pjson.configFile) || defaults.configFile;
self.format = pjson.format;
self.map = extend({}, pjson.map || {});
self.buildConfig = extend({}, pjson.buildConfig || {});
self.overrides = extend({}, pjson.overrides || {});
}
// warn on invalid configs
if (path.relative(self.baseURL, self.packages)[0] == '.')
ui.log('warn', 'jspm_packages must be specified in the package.json within the baseURL for paths to resolve correctly.');
};
PackageJSON.prototype.write = function() {
var self = this;
var pjson = this.jspmPrefix ? {} : this.originalPjson;
if (!this.jspmPrefix && self.registry !== 'jspm')
pjson.jspm = true;
// set the registry property
if (this.jspmPrefix && this.registry !== 'jspm' || this.originalPjson.jspm && this.originalPjson.jspm.registry === 'jspm')
pjson.registry = this.registry;
var defaults = this.defaults;
function set(property, value, setValue) {
if (property in defaults && defaults[property] === value)
delete pjson[property];
else if (setValue !== undefined)
pjson[property] = setValue;
else if (value !== undefined)
pjson[property] = value;
}
// only set properties that differ from the defaults
// we set what we want the "derived" pjson to be first
set('main', this.main);
set('format', this.format);
var directories = extend({}, this.pjson.directories);
if (this.baseURL !== defaults.baseURL)
directories.baseURL = winPath(path.relative(this.dir, this.baseURL)) || '.';
else
delete directories.baseURL;
if (this.packages !== defaults.packages)
directories.packages = winPath(path.relative(this.dir, this.packages)) || '.';
else
delete directories.packages;
pjson.directories = alphabetize(directories);
set('configFile', this.configFile, winPath(path.relative(this.dir, this.configFile)));
pjson.map = alphabetize(this.map);
pjson.dependencies = writeDependencies(this.dependencies, this.registry);
pjson.devDependencies = writeDependencies(this.devDependencies, this.registry);
pjson.buildConfig = this.buildConfig;
pjson.overrides = this.overrides;
// remove any empty object properties that aren't nulling a base property
Object.keys(pjson).forEach(function(key) {
var val = pjson[key];
if (typeof val !== 'object' || hasProperties(val))
return;
if (!self.jspmPrefix || !(key in self.originalPjson))
delete pjson[key];
});
// for jspm prefixing, work out what we need to get desired package.json
if (this.jspmPrefix) {
for (var p in pjson) {
if (this.originalPjson[p] === pjson[p])
delete pjson[p];
}
this.originalPjson.jspm = pjson;
}
// NB check that the file hasn't changed since we opened it and if so, prompt
return asp(fs.writeFile)(this.fileName, stringify(this.originalPjson) + config.newLine);
};
function writeDependencies(dependencies, registry) {
var outDependencies = {};
var depValue;
for (var dkey in dependencies) {
if (!dependencies.hasOwnProperty(dkey))
continue;
var dep = dependencies[dkey];
if (!dep)
continue;
if (dep.registry === registry) {
if (dkey === dep.package)
depValue = dep.version || '*';
else
depValue = dep.exactPackage;
}
else
depValue = dep.exactName;
outDependencies[dkey] = depValue;
}
return alphabetize(outDependencies);
}
// can take an existing non-jspm package.json
function doInitPrompts(pjson, pjsonPath, prefixed) {
var baseDir = path.dirname(pjsonPath);
var base;
pjson.directories = pjson.directories || {};
return Promise.resolve()
.then(function() {
return ui.confirm('Would you like jspm to prefix the jspm package.json properties under %jspm%?', prefixed);
})
.then(function(prefix) {
prefixed = prefix;
return ui.input('Enter server baseURL (public folder path)', pjson.directories.baseURL || './');
})
.then(function(baseURL) {
base = path.relative(process.cwd(), path.resolve(baseURL));
baseURL = path.relative(baseDir, path.resolve(baseURL));
if (!base)
base = '.';
base += path.sep;
if (baseURL)
pjson.directories.baseURL = baseURL;
return ui.input('Enter jspm packages folder', pjson.directories.packages || base + 'jspm_packages');
})
.then(function(packages) {
pjson.directories.packages = path.relative(baseDir, path.resolve(packages));
return ui.input('Enter config file path', pjson.configFile || base + 'config.js');
})
.then(function(configFile) {
pjson.configFile = path.relative(baseDir, path.resolve(configFile));
return prefixed;
});
}
module.exports = PackageJSON;