jspm
Version:
Registry and format agnostic JavaScript package manager
618 lines (531 loc) • 19.5 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.
*/
require('core-js/es6/string');
var ui = require('./lib/ui');
var chalk = require('chalk');
var config = require('./lib/config');
var globalConfig = require('./lib/global-config');
var core = require('./lib/core');
var bundle = require('./lib/bundle');
var registry = require('./lib/registry');
var install = require('./lib/install');
var fs = require('graceful-fs');
var Promise = require('rsvp').Promise;
var link = require('./lib/link');
var build = require('./lib/build');
require('rsvp').on('error', function(reason) {
ui.log('warn', 'Unhandled promise rejection.\n' + reason && reason.stack || reason || '' + '\n');
});
process.on('uncaughtException', function(err) {
ui.log('err', err.stack || err);
});
/* jshint laxbreak: true */
(function() {
function showHeader() {
ui.log('\n'
+ ' ' + chalk.bgWhite(' ') + '\n'
+ ' \033[47m\033[93m\033[1m jspm \033[0m\033[90m ' + chalk.grey('Browser Package Management') + '\n'
+ ' ' + chalk.bgWhite(' ') + '\n'
);
}
function showInstructions() {
showHeader();
ui.log('\n'
+ 'jspm run main Run a jspm module in Node\n'
+ '\n'
+ 'jspm init [basepath] [--prompts] Create / validate project configuration file\n'
+ '\n'
+ 'jspm install <name[=target]+> [--force skips cache] [--latest] [--dev]\n'
+ ' install jquery Install a package looked up in the jspm registry\n'
+ ' install react=npm:react Install a package from a registry to latest\n'
+ ' install jquery=2 react Install a package to a version or range\n'
+ '\n'
+ ' install Reproducible / shrinkwrap install package.json\n'
+ '\n'
+ ' install react --lock Stable install, locking existing dependencies\n'
+ '\n'
+ ' install react=npm:react --edge Install a package from a registry to latest unstable\n'
+ '\n'
+ ' install ts --dev Install a package as devDependency\n'
+ '\n'
+ ' install dep -o override.json Install with the given custom override\n'
+ ' install dep -o "{override json}" useful for testing package overrides\n'
+ '\n'
+ 'jspm update Update all packages from package.json\n'
+ 'jspm uninstall name Uninstall a package and clean dependencies\n'
+ 'jspm clean Clear unused and orphaned dependencies\n'
+ '\n'
+ 'jspm inspect [--forks] View all installed package versions\n'
+ 'jspm inspect npm:source-map View the versions and ranges of a package\n'
+ '\n'
+ 'jspm inject <name[=target]> [--force] [--latest] [--lock] [-o]\n'
+ ' inject jquery Identical to install, but injects config\n'
+ ' only instead of downloading the package\n'
+ '\n'
+ 'jspm link registry:pkg@version Link a local folder as an installable package\n'
+ 'jspm install --link registry:name Install a linked package\n'
+ '\n'
+ 'jspm dl-loader [--edge --latest] Download the browser loader files\n'
+ 'jspm dl-loader [babel|traceur|typescript]\n'
+ '\n'
+ 'jspm resolve --only registry:package@version\n'
+ ' resolve --only npm:jquery@2.1.1 Resolve all versions of a package to the given version\n'
+ '\n'
+ 'jspm setmode <mode>\n'
+ ' setmode local Switch to locally downloaded libraries\n'
+ ' setmode remote Switch to CDN external package sources\n'
+ '\n'
+ 'jspm bundle moduleA + module/b [outfile] [--minify] [--no-mangle] [--inject] [--skip-source-maps] [--source-map-contents]\n'
+ 'jspm bundle-sfx app/main [outfile] [--format <amd|cjs|global>] [--minify]\n'
+ 'jspm unbundle Remove injected bundle configuration\n'
+ 'jspm depcache moduleName Stores dep cache in config for flat pipelining\n'
+ '\n'
+ 'jspm registry <command> Manage registries\n'
+ ' registry config <name> Configure an existing registry\n'
+ ' registry create <name> <pkg> Create a new custom registry instance\n'
// + ' registry export <registry-name> Export an registry programatically\n'
+ '\n'
+ 'jspm config <option> <setting> Configure jspm global options\n'
+ ' Stored in ~/.jspm/config\n'
+ '\n'
+ 'jspm cache-clear Clear global caches, not recommended\n'
+ '\n'
+ 'Global Flags\n'
+ ' --yes | -y Skip prompts / use default inputs\n'
+ ' --log <ok|warn|err> Set log level\n'
);
}
function showVersion() {
// deprecate localJspm
ui.log(require('./package.json').version + '\n'
+ (process.env.globalJspm === 'true' || process.env.localJspm === 'false' ? 'Running against global jspm install.' : 'Running against local jspm install.'));
}
function dwalk(obj, visitor, pname) {
for (var p in obj) {
if (!obj.hasOwnProperty(p))
continue;
if (typeof obj[p] === 'object')
dwalk(obj[p], visitor, (pname ? pname + '.' : '') + p);
else
visitor((pname ? pname + '.' : '') + p, obj[p]);
}
}
// takes commandline args, space-separated
// flags is array of flag names
// optFlags is array of flags that have option values
// optFlags suck up arguments until next flag
// returns { [flag]: true / false, ..., [optFlag]: value, ..., args: [all non-flag args] }
function readOptions(inArgs, flags, optFlags) {
// output options object
var options = { args: [] };
flags = flags || [];
optFlags = optFlags || [];
var curOptionFlag;
function getFlagMatch(arg, flags) {
var index;
if (arg.startsWith('--')) {
index = flags.indexOf(arg.substr(2));
if (index !== -1)
return flags[index];
}
else if (arg.startsWith('-')) {
return flags.filter(function(f) {
return f.substr(0, 1) === arg.substr(1, 1);
})[0];
}
}
// de-sugar any coupled single-letter flags
// -abc -> -a -b -c
var args = [];
inArgs.forEach(function(arg) {
if (arg[0] == '-' && arg.length > 1 && arg[1] != '-') {
for (var i = 1; i < arg.length; i++)
args.push('-' + arg[i]);
}
else {
args.push(arg);
}
});
args.forEach(function(arg) {
var flag = getFlagMatch(arg, flags);
var optFlag = getFlagMatch(arg, optFlags);
// option flag -> suck up args
if (optFlag) {
curOptionFlag = optFlag;
options[curOptionFlag] = [];
}
// normal boolean flag
else if (flag) {
options[flag] = true;
}
// value argument
else {
if (curOptionFlag)
options[curOptionFlag].push(arg);
else
options.args.push(arg);
}
});
// flag values are strings
optFlags.forEach(function(flag) {
options[flag] = (options[flag] || []).join(' ');
});
return options;
}
// this will get a value in its true type from the CLI
function readValue(val) {
val = val.trim();
if (val === 'true' || val === 'false')
return eval(val);
else if (parseInt(val).toString() == val)
return parseInt(val);
else
return val;
}
// [].concat() to avoid mutating the given process.argv
var args = process.argv.slice(2),
options;
var logArgIndex = args.indexOf('--log');
if (logArgIndex > -1) {
ui.setLogLevel(args[logArgIndex + 1]);
args.splice(logArgIndex, 2);
}
switch(args[0]) {
case 'run':
core.run(args[1]);
break;
case 'inject':
var inject = true;
case 'update':
var doUpdate = !inject;
case 'i':
case 'isntall':
case 'install':
options = readOptions(args, ['force', 'link', 'yes', 'lock', 'latest',
'unlink', 'quick', 'dev', 'edge', 'production'], ['override']);
options.inject = inject;
options.update = doUpdate;
args = options.args;
var depMap;
for (var i = 1; i < args.length; i++) {
depMap = depMap || {};
var name, target;
var arg = args[i];
name = arg.split('=')[0];
target = arg.split('=')[1];
if (!target) {
target = name;
if (name.indexOf(':') !== -1)
name = name.substr(name.indexOf(':') + 1);
if (name.indexOf('@') > 0)
name = name.substr(0, name.lastIndexOf('@'));
}
if (target.indexOf(':') === -1)
target = globalConfig.config.defaultRegistry + ':' + target;
depMap[name] = target || '';
}
var override = options.override;
if (override) {
if (!override.startsWith('{')) {
try {
options.override = fs.readFileSync(override);
}
catch(e) {
return ui.log('err', 'Unable to read override file %' + override + '%.');
}
try {
options.override = JSON.parse(options.override);
}
catch(e) {
return ui.log('err', 'Invalid JSON in override file %' + override + '%.');
}
}
else {
options.override = eval('(' + override + ')');
}
}
if (options.yes)
ui.useDefaults();
// jspm install with no arguments is locked
if (!depMap && !doUpdate)
options.lock = true;
// no install package -> install from package.json dependencies
(depMap ? install.install(depMap, options) : install.install(true, options))
.then(function() {
return core.checkDlLoader();
})
.then(function() {
return core.setMode(inject ? 'remote' : 'local');
})
.then(function() {
ui.log('');
ui.log('ok', 'Install complete.');
process.exit();
}, function(err) {
// something happened (cancel / err)
if (err)
ui.log('err', err.stack || err);
ui.log('warn', 'Installation changes not saved.');
process.exit(1);
});
break;
case 'r':
case 'remove':
case 'uninstall':
options = readOptions(args, ['yes']);
if (options.yes)
ui.useDefaults();
install.uninstall(options.args.splice(1))
.then(function() {
ui.log('');
ui.log('ok', 'Uninstall complete.');
}, function(err) {
ui.log('err', err.stack || err);
ui.log('warn', 'Uninstall changes not saved.');
process.exit(1);
});
break;
case 'resolve':
options = readOptions(args, null, ['only']);
if (!options.only)
return ui.log('warn', 'Use %jspm resolve --only registry:pkg@version%');
install.resolveOnly(options.only)
.catch(function(err) {
if (!err)
ui.log('err', 'Resolve operation not performed.');
else
ui.log('err', err.stack || err);
process.exit(1);
});
break;
case 'clean':
options = readOptions(args, ['yes']);
args = options.args;
if (options.yes)
ui.useDefaults();
install.clean()
.then(function() {
ui.log('');
ui.log('ok', 'Project cleaned successfully.');
}, function(err) {
ui.log('err', err.stack || err);
process.exit(1);
});
break;
case 'inspect':
options = readOptions(args, ['forks']);
args = options.args;
config.load()
.then(function() {
if (!args[1])
return install.showVersions(options.forks);
if (!args[1].includes(':'))
return ui.log('warn', 'Enter a full package name of the format `registry:repo`.');
return install.showInstallGraph(args[1]);
})
.catch(function(e) {
ui.log('err', e.stack || e);
});
break;
case 'init':
options = readOptions(args, ['yes', 'prompts']);
if (options.yes)
ui.useDefaults();
core.init(options.args[1], options.prompts);
break;
case 'dl-loader':
options = readOptions(args, ['source', 'latest', 'edge', 'yes', 'traceur', 'babel', 'typescript']);
if (options.yes)
ui.useDefaults();
core.dlLoader(options.args[1] || options.traceur && 'traceur' || options.babel && 'babel' || options.typescript && 'typescript', options.source, options.edge, options.latest);
break;
case 'setmode':
options = readOptions(args, ['yes']);
if (options.yes)
ui.useDefaults();
core.setMode(args.splice(1))
.then(function(msg) {
ui.log('ok', msg);
}, function(err) {
ui.log('err', err.stack || err);
});
break;
case 'depcache':
options = readOptions(args, ['yes']);
if (options.yes)
ui.useDefaults();
if (!args[1])
ui.log('warn', 'depCache requires a module name to trace.');
else
bundle.depCache(args[1]);
break;
case 'b':
case 'bundle-sfx':
var sfxBundle = true;
case 'bundle':
options = readOptions(args, ['inject', 'yes', 'skip-source-maps', 'minify',
'no-mangle', 'hires-source-maps', 'no-runtime', 'inline-source-maps', 'source-map-contents'], ['format', 'global-name', 'globals', 'global-defs']);
if (options.yes)
ui.useDefaults();
options.sourceMaps = !options['skip-source-maps'];
options.lowResSourceMaps = !options['hires-source-maps'];
options.mangle = !options['no-mangle'];
options.sourceMapContents = !!options['source-map-contents'];
if (options['inline-source-maps'])
options.sourceMaps = 'inline';
if (options['global-name'])
options.globalName = options['global-name'];
if (options.inject)
options.injectConfig = true;
options.format = options.format;
if (options.globals)
options.globalDeps = eval('(' + options.globals + ')');
if (options['global-defs'])
options.globalDefs = eval('(' + options['global-defs'] + ')');
var bArgs = options.args.splice(1);
if (bArgs.length === 0)
return ui.log('warn', 'You must provide at least one module as the starting point for bundling');
if (bArgs.length < 2) {
(sfxBundle ? bundle.bundleSFX : bundle.bundle)(bArgs[0], undefined, options)
.catch(function() {
process.exit(1);
});
}
else {
var secondLastArg = bArgs[bArgs.length - 2].trim();
var signChar = secondLastArg.substr(secondLastArg.length - 1, 1);
var expression = '';
var fileName;
// we can write: jspm bundle app + other
if (['+', '-'].indexOf(signChar) !== -1) {
expression = bArgs.join(' ');
}
// or we can write: jspm bundle app + other out.js
else {
expression = bArgs.splice(0, bArgs.length - 1).join(' ');
fileName = bArgs[bArgs.length - 1];
}
(sfxBundle ? bundle.bundleSFX : bundle.bundle)(expression, fileName, options)
.catch(function() {
process.exit(1);
});
}
break;
case 'unbundle':
bundle.unbundle()
.catch(function(e) {
ui.log('err', e.stack || e);
process.exit(1);
});
break;
case 'build':
options = readOptions(args, ['yes']);
if (options.yes)
ui.useDefaults();
core.build();
break;
case 'compile':
options = readOptions(args, ['transpile', 'minify', 'removeJSExtensions', 'yes'], ['map', 'format']);
if (options.yes)
ui.useDefaults();
if (options.map) {
var mapParts = options.map.split('=');
options.map = {};
options.map[mapParts[0]] = mapParts[1];
}
build.compileDir(args[1], options)
.then(function() {
ui.log('ok', 'Compilation complete');
}, function(e) {
ui.log('err', e.stack || e);
});
break;
case 'link':
options = readOptions(args, ['force', 'yes']);
if (options.yes)
ui.useDefaults();
args = options.args;
var linkname = args[2] || args[1] || '';
var linkpath = args[2] || '.';
link.link(linkname, linkpath, options.force);
break;
case 'registry':
options = readOptions(args, ['yes']);
if (options.yes)
ui.useDefaults();
var action = args[1];
if (action === 'config') {
if (!args[2])
return ui.log('warn', 'You must provide an registry name to configure.');
return Promise.resolve(registry.configure(args[2]))
.then(function() {
ui.log('ok', 'Registry %' + args[2] + '% configured successfully.');
}, function(err) {
ui.log('err', err.stack || err);
});
}
else if (action === 'create') {
if (!args[2])
return ui.log('warn', 'You must provide an registry name to create.');
if (!args[3])
return ui.log('warn', 'You must provide the registry module name to generate from.');
return Promise.resolve(registry.create(args[2], args[3]))
.then(function(created) {
if (created)
ui.log('ok', 'Enpoint %' + args[2] + '% created successfully.');
}, function(err) {
ui.log('err', err.stack || err);
});
}
else if (action === 'export') {
if (!args[2])
return ui.log('warn', 'You must provide an registry name to export.');
if (!globalConfig.config.registries[args[2]])
return ui.log('warn', 'Registry %' + args[2] + '% does not exist.');
var registryConfig = globalConfig.config.registries[args[2]];
dwalk(registryConfig, function(p, value) {
process.stdout.write('jspm config registries.' + args[2] + '.' + p + ' ' + value + '\n');
});
}
else {
showInstructions();
ui.log('warn', 'Invalid registry argument %' + args[1] + '%.');
}
break;
case 'c':
case 'config':
var property = args[1];
var value = readValue(args.splice(2).join(' '));
globalConfig.set(property, value);
break;
case 'cc':
case 'cache-clear':
core.cacheClear();
break;
case '--help':
case '-h':
showInstructions();
break;
case '--version':
case '-v':
showVersion();
break;
default:
showInstructions();
if (args[0])
ui.log('warn', 'Invalid argument %' + args[0] + '%.');
}
})();